Zasada najmniejszego zaskoczenia

Principle Of Least Astonishment (POLA) to jedno z podstawowych praw w programowaniu (i inżynierii wogóle). Nigdy przenigdy nie należy jej łamać.

Niedawno trafiłem na tą zasadę i byłem zaskoczony. Coś, co było dla mnie oczywiste. Tak oczywiste, że nie myślałem o formalizowaniu tego okazało się mieć swoją formalną nazwę. Tą zasadę ilustruje obrazek z książki Roberta Martina:

wtfperminute

Przeglądamy czyiś kod. Natrafiamy na funkcję, metodę, rozwiązanie. Jaka może być nasza reakcja? Jedna z dwóch.

  1. WTF!? Co to ma być? Po cholerę to jest tutaj? Od czego to zależy? Co to ma robić? Jak to zmienić? Czy ta funkcja robi coś jeszcze? Co za idiota to pisał?
  2. Aha, no tak. W sumie to logiczne. A jakbym chciał zmienić to pewnie będzie tu. No i jest. No i działa. Nawet to przewidzieli. Wow, ale to fajne i spójne (taka była moja reakcja gdy po raz pierwszy miałem kontakt z Symfony2).

Kto jest Twoim klientem?

Klient – magiczne słowo. W potocznym rozumieniu to ktoś kto płaci i narzeka. Ale że płaci, to trzeba wytrzymać jego narzekania.

Ale klientem jest nie tylko końcowy odbiorca. Klient to ktoś, kto dostanie naszą pracę. Na podstawie tego co zrobimy ma wykonać swoją robotę. Klientem jest więc tester, kolega programista, który skorzysta z naszej funkcji. Project manager, wrdożeniowiec czy QC pracujący z nami to też nasi klienci.

Nasze zadanie to nie tylko zaprogramować. Nasza praca ma pomóc im wykonać ich pracę.

Zasada minimalnego zaskoczenia

Sama zasada mówi więc, że:

Element systemu (funkcja, klasa, interfejs, komenda itp) powinny działać tak, jak naturalnie spodziewałby się tego jego użytkownik.

Użytkownikiem funkcji i klas jest nasz kolega, który dostał od nas bibliotekę. Nawet ja sam jestem użytkownikiem (klientem) samego siebie gdy po kilku miesiącach wracam do swojego kodu. Im mniej zaskoczeń tym lepiej.

Złym przykładem jest np. funkcja:

function sendEmail($to, $content) {
   $entityManager = $someFactory->getService('EntityManager');
   $email = new Email();
   $email->setReceipent($to);
   $email->setContent($content);
   return $entityManager->persist($email);
}

Nazwa funkcji twierdzi, że wysyła maile. Ale tak naprawdę zapisuje je tylko do bazy. Być może inny element systemu zbiera te maile i grupowo wysyła. A może to nie maile tylko wewnętrzne wiadomości? Funkcja zwraca zapewne true gdy zapisze do bazy ale my czyli klient pomyślimy, że mail udało się wysłać. Może lepiej nazwać tą funkcję pushMessage()?

Dlaczego łamiemy tą zasadę?

Wydaje się, że przestrzeganie tej zasady jest naturalne. Chcemy pisać dobry kod, damy funkcji logiczną nazwę i wszystko będzie OK. Ale w praktyce jest kilka powodów, dla których ta zasada jest łamana:

Lenistwo to bardzo częsty powód. Zamiast napisać pełną nazwę funkcji piszemy skrót. Gdy zmieniamy funkcję, gdy zmieni się jej przeznaczenie nie zmienimy jej nazwy. Wymyślenie dobrej nazwy na złożoną funkcjonalność nie jest takie łatwe. Po co wymyślać długie nazwy? Przecież komputer i tak zrozumie. Niestety inny programista już nie.

Niedouczenie to drugi powód. Jeśli nie znamy wzorców projektowych, zasad SOLID i innych dobrych praktyk to często z projektowania wychodzą potworki. Klasy są ciasno powiązane. Zależności są skomplikowane i nie wiadomo co za co odpowiada.

Pośpiech i zmiany na szybko. W normalnych warunkach przy zmianie zastanowimy się – czy dodać kod w tej funkcji? Czy stworzyć nową? Czy warto dodać podklasę, nowy interfejs itp? Jak zmienić żeby nic nie napsuć? W praktyce gdy szef stoi za plecami i popędza znajdujemy pierwsze miejsce, gdzie zmiana zadziała i dodajemy kod. Kończymy z funkcją SendMail, która oprócz wysyłania loguje, waliduje, porządkuje, parzy kawę i rozdaje cukierki.

Co więc zrobić?

Prosta rada – pisz jak dla siebie. Rób jak dla siebie. Wyobraź sobie, że to Ty będziesz za rok miał skorzystać z tego kodu. Jeśli nie wiadomo jak zrobić – doczytaj. Google i StackOverflow są nieocenionym źródłem porad.

O samej zasadzie możemy poczytać w angielskiej wikipedii. Warto zapoznać się też z tym artykułem.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *