Oskar Dudycz

Pragmatic about programming

Powiedz, nie pytaj. Czyli co podgrzewanie mleka ma do programowania?

2020-12-07 oskar dudyczArchitektura

cover

Cześć!

Kiedyś miałem w planach napisać książkę kucharską dla facetów. Zestaw podstawowych porad jak przeżyć w kuchni. Poczynając od takich tematów jak zaparzyć herbatę i kawę, jak podgrzać mleko, żeby nie wykipiało, jak zrobić jajko na miękko. Takie klimaty.

Nie wiem jak Ty, ale ja muszę w tych kwestiach muszę zachować skrajną cierpliwość i czujność. Dajmy na to takie mleko. To jest mega złośliwy osobnik. Przed podgrzaniem należy zaopatrzyć się w zapałki. Nie masz w domu kuchenki gazowej? Nie szkodzi, te zapałki nie są do zapalania gazu. One są od potrzymywania opadających powiek. Mleko zwykle parę minut się gotuje, trudna sprawa zachować tyle samokontroli, żeby nie mrugać. A wystarczy mrugnięcie i… cyk! Mleko wykipiało.

Patrzysz raz - jeszcze luz. Wkładasz palec - zimne. Patrzysz kolejny, jeszcze letnie. Nagle kichasz, oczy Ci się zamykają i cyk. Garnek i kuchenka w wygotowanym mleku.

Z jajkiem na miękko jest podobnie. Przepis jest prosty. Nalej do garnka wodę i włóż do niej jajka. Czekaj aż woda się zagotuje. Od momentu zagotowania się wody, gotuj jajka dokładnie: S – 3,5 minuty, M – 4 minuty, L – 5,5 minuty. No i tutaj pojawia się kwestia, że jakoś te jajko nigdy się nie daje wpasować w S, M czy L. Mija chwilunia i już mamy jajko za twarde na miękkie i za miękkie na twarde.

Jak już napiszę te książkę to drugi tom będzie o programistach i ich ifach, które czekają i weryfikują czy można pozwolić zapisać rekord czy nie. Na pewno znasz taki kod:

if (invoiceService.Exists(invoiceNumber))
{
    return false;
}
invoiceService.Add(new Invoice { InvoiceNumber = invoiceNumber });

Gdy widzę taki kod, z pamięci mogę zarecytować jak będzie przebiegał dialog:

Ja: Po co Ci ten if? Interlokutor: No bo nie może być dodana faktura z tym samym numerem, czego nie rozumiesz? Ja: I ten if Ci to zapewni? Interlokutor: No tak. Ja: A co jak w międzyczasie jak przejdzie if to ktoś doda fakturę z tym samym numerem? Interlokutor: To się nie wydarzy. Ja: No jak to nie? Przecież może się wydarzyć technicznie. Interlokutor: No dobra, ale bardzo rzadko.

Kurtyna.

Bardzo często się okazuje, że programist(k)a tak naprawdę to dodaje to na wszelki wypadek, bo i tak jest unique constrain na bazie i baza to obsłuży. A dodaje tego ifa albo na wszelki wypadek albo żeby mieć ładny błąd.

Gorzej jak delikwent po prostu brzydzi się dodawaniem kluczy. Kto w dzisiejszych czasach definiuje klucze lub indeksy? SQL w 21 wieku? Tfu!

Wtedy mamy do czynienia z lekką schizofrenią. Bo niby biznes mówi, że jest to wymagane, programista też tak uważa - ale tak naprawdę to tylko trochę, na tyle ile mu wygodnie.

Pisząc taki kod właśnie się zachowujemy jak przy podgrzewaniu mleka. Tym razem na pewno się uda! Przecież teraz nie mrugnę i nic się nie stanie. Nawet spróbuję nie oddychać! Mleko nie może wykipieć w ułamku sekundy.

Problem nie dotyczy jednak samych “constraintów”. Dotyczy tez kształtowania samego API. Myślę, że Tobie też zdarzyło się widzieć sytuacje gdzie pisząc kod musieliśmy wywołać “Validate”, “CheckPermissions”, “Exist”. Sprawdź czy mleko jeszcze nie wykipiało, włóż palec i sprawdź temperaturę, jak jako jest średnie to gotuj 4m jak duże to 5,5.

Im więcej nasze API wymaga pamiętania o tym, że zanim wywołamy jakąś metodę to musimy wywołać odpowiednią kombinację innych metod - tym większa szansa, że ktoś o tym zapomni. Dlatego właśnie nasze API powinno być zbudowane zgodnie z zasadą “powiedz, nie pytaj”. Czyli powiedzieć co ma nasze API zrobić i ewentualnie zareagować jeśli operacja się nie powiodła niż zawsze na wszelki wypadek za każdym razem się pytać “czy na pewno mogę?“. W naszym przykładzie to metoda Add powinna zweryfikować czy nie istnieje już inna faktura o tym tym samym numerze. Nie powinniśmy od użytkownika serwisu wymagać, że będzie pamiętał o wywołaniu Exists.

Robiąc w ten sposób zyskujemy:

  • czytelniejsze i bardziej przewidywalne API - szczególnie jeśli zmapujemy np. wyjątek bazodanowy do jakiegoś czytelniejszego błędu domenowego, lub zwrócimy wynik z błędem,
  • mniej zawodne API - w końcu sami je napisaliśmy, nie? Nie musimy się stresować, że ktoś zapomni użyć czegoś,
  • wydajniejsze API - jeśli nie trzeba robić zawsze dodatkowego zapytania “a czy istnieje taki rekord” to zyskujemy sporo na wydajności. Co najmniej na czasie zapytania, a możemy również zyskać na czasie otwarcia połączenia. Mniejszej liczbie deadlocków czy też mniejszej liczbie wiszących wątków.

“Win win!”

Co o tym sądzisz?

Na koniec parę linków:

Pozdrawiam! Oskar

  • © Oskar Dudycz 2019-2020