Nazwy metod, atrybutów i zmiennych na poważnie
Czasami spotykamy się z problemami nazewnictwa poszczególnych elementów naszej aplikacji. Mogą to być gettery, settery, klasy z wieloma rzeczownikami w nazwie — to nie najlepsze praktyki, które moglibyśmy wykorzystać do pisania naszego kodu. Powinniśmy traktować obiekty jak istoty żywe. Na przykład obiekt Doctor
. Do pobrania jego imienia nie powinniśmy wykorzystywać czegoś w stylu readName()
— to wymuszenie na obiekcie sposobu zachowania, a to on powinien o tym zdecydować. Zapytajmy obiekt o imię za pomocą name()
— to czytelniejsza forma wyciągania wartości. Co w momencie, gdy zechcemy zmienić imię naszego Doctora
? Powinniśmy wykonać na nim metodę o nazwie idealnie odpowiadającej zachowaniu, które zaistnieje — zmiana nazwy czyli rename($name)
. Nie możemy setować imienia żywych istot — wykorzystajmy manipulacje, których nazwy określą nam to, co się z nimi stanie. Sławomir Sobótka (Bottega IT) dał genialny przykład, w którym wyśmiał podejście get/set
w sposób, który zaprezentowałem poniżej. Wkładamy kiełbasę do brzucha człowieka zamiast go nakarmić. 😂 — Czyli proceduralne podejście do struktur danych zamiast zachowań obiektów.
Dlaczego poprawne nazewnictwo metod jest tak istotne? Dzięki niemu możemy przeczytać nasz kod klas, czy poszczególnych metod jak książkę. Zwiększa to naszą produktywność, a przez to firma więcej zarabia. Pamiętajmy, że czytamy kod duuużo częściej niż go piszemy. Pielęgnujmy nawet takie drobnostki jak konwencje nazewnictw i myślmy obiektowo zamiast proceduralnie jak w Pascalu tworząc piramidy getterów i setterów.
Nazewnictwo metod pytających
Metody pytające to tak zwane queriesy, buildery czy readery danych. Pozwalają one nam odczytywać, znajdywać, wyciągać dane z różnych miejsc. Nie powinniśmy wykorzystywać w nich prefixów w stylu readX()
, findX()
, getX()
czy innych podobnych. To wymuszanie na obiekcie konkretnych zachowań sposobu, w który dobierzemy się do danych. Najlepszą metodą nazewnictwa tego typu metod jest wykorzystywanie rzeczowników. Określają one w bardzo przejrzysty sposób to, co się znajduje w tego typu metodach. Jeżeli nazwa metody nie zawiera czasowników, to nic nam nie śmierdzi w kodzie i nie kusi do złamania podstawowych zasad CQRS. Za przykład możemy wziąć ponownie klasę lekarza — Doctor
, który ma różne typy relacji do adresów w których obsługuje pacjentów. Metoda taka jak address()
idąc wyżej opisaną drogą umożliwi nam wyciągnięcie pojedynczego adresu (gdy relacja jest 1:1), a zaś w momencie, gdy użyjemy addresses()
uzyskamy dostęp do całej kolekcji adresów lekarza. Logiczne?
Kolejną często spotykaną, złą praktyką jest nazywanie metod z wykorzystaniem suffixów, w których to deklarujemy formę zwracanego typu. Przykładowo addressesArray()
czy addressesJson()
— ponownie zmuszamy obiekt do zwrócenia konkretnych danych, fu! Wystarczy nam przecież do tego statyczna deklaracja zwracanego typu i… tyle.
Nazewnictwo metod pytających o wartości logiczne
Logiczne wartości mogłyby być częścią wyżej opisanego rozdziału dotyczącego queriesów, aczkolwiek stwierdziłem, że czytelniej będzie — gdy przeniosę to do następnego akapitu.
W sytuacji, gdy pytamy obiekt o coś, co jest reprezentowane przez wartość logiczną, powinniśmy to oznajmić czytelnikowi kodu. Korzystając z naszego przykładu klasy Doctor
— możemy zapytać go o dostępność przez isAvailable()
— zaś jeżeli chcemy sprawdzić czy jest on aktywnym profilem, to wystarczy metoda isActive()
— nic trudnego, a zachowanie spójnego nazewnictwa tak bardzo ułatwia pracę. Nie musimy upewniać się, czy metoda na pewno zwraca boolean
— sama konwencja mówi nam o tym przez pytanie logiczne. Bardzo fajną możliwość posiada język Ruby
, w którym możemy użyć metod typu active?
— a to wszystko dzięki wsparciu znaków zapytania i wykrzykników.
Nazewnictwo metod modyfikujących
Metody modyfikujące to nic innego jak commandy, manipulatory, które stosowane są wszędzie — nie tylko w momencie, gdy świadomie wykorzystujemy podejście Command Query Responsibility Segregation. Często są to nazwy metod serwisów aplikacyjnych, metody w modelach — znajdziemy je wszędzie! Wracając szybko do przykładu z lekarzem — możemy na nim przeprowadzić aktywację konta za pomocą activate()
lub przeprowadzić jego banicję przez zablokowanie block()
. Czyż te metody nie brzmią lepiej od setActive(true)
czy setBlock(true)
i tym podobnych? Dodatkowo tego typu przykład, to metody biznesowe — nie przekazujmy parametrów tylko zbudujmy deactivate()
, która z powodzeniem zastąpi nam setowanie atrybutu active
na wartość false
. Największym piekłem są miejsca, w których mamy setStatus(1)
dla aktywnego konta, setStatus(2)
dla nieaktywnego, a jeszcze więcej podobnych dla każdego innego przypadku stanu w jakim znajduje się obiekt. Tego typu metody NIE określają zachowania, które w nich przebiegają.
Oczywiście obowiązkową praktyką jest unikanie zwracania czegokolwiek z metod typu mutator. Jedyną dopuszczalną możliwością jest return $this
, gdy już naprawdę chcemy zbudować sobie łańcuszek z użyć naszych metod.
Zachowania manipulujące powinny opisywać za pomocą czasowników to, co obiekt potrafić zrobić. Oczywiście warto wystrzegać się czegoś takiego jak sendGuzzleRequest()
— to już z góry jest przegrana nazwa, gdyż sugeruje nam implementację i narzuca rozwiązanie zachowania. Metody zaczynają uwalniać swój smród, gdy ich nazwy to połączenia wielu słów w stylu changeEmailAndSendToPatients()
— na pierwszy rzut oka widzimy, że zasada Single Responsibility jest złamana. Zmiana i wysyłka maila do pacjentów — czy to nie czasami dwie całkowicie różne odpowiedzialności? :)
Nazewnictwo zmiennych lokalnych i atrybutów klas
Podobnie jak metody pytające — tutaj także powinniśmy stosować rzeczowniki. Nie chcemy tutaj ani namiastki czasowników, bo zaburzą nasze właściwości. Zmienne takie jak $htmlContent
są problematyczne, gdyż pokazują nasze detale implementacyjne. Jeżeli musimy dodawać prefix html
oznacza to najczęściej, że nie mamy domkniętego kontekstu i klasa/metoda posiada całkowicie różne odpowiedzialności. Budowa HTML-a oraz budowa XML-a to dwa osobne zachowania. Jeżeli mamy możliwość odpuszczenia prefixu — zróbmy to, gdyż może on sugerować inne możliwe zmienne w naszej metodzie zawierające jakąś wartość.
Często zmienne, które prefixujemy jakimś kontekstem zdradzają nam moment, w którym powinniśmy zastanowić się nad enkapsulacją do osobnej klasy. Przykładem mogą być zmienne addressStreet
, addressCity
, addressPostcode
— może warto byłoby wynieść to wszystko jako obiekt Address
? Dzięki temu wyniesiemy informacje adresowe w jedno miejsce, a w naszej metodzie będziemy mieli jedynie zmienną $address
, która to przechowuje obiekt adresu ze wszelkimi detalami możliwymi do wyciągnięcia na przykład poprzez $address->street()
. Dużo łatwiej będzie nam też operować na jednej zmiennej zamiast grupy zmiennych z odpowiednim prefixem. Dodatkowo w momencie, gdy wyjdzie nam potrzeba przekazania ich dalej — wystarczy jeden parametr zamiast X. 👊
Co możemy osiągnąć aplikując powyższe zasady?
Pierwszą korzyścią ze stosowania poprawnego nazewnictwa obiektowego jest fakt, że kod taki jest łatwy do przeczytaniu. Czasami spotykamy różne konwencje nazewnictwa: find()
, get()
, take()
, read()
, print()
, które służą do wyciągania wartości — szczęście, gdy wykorzystywane są jednolicie i spójnie. Używanie powyższych konwencji zamiennie powoduje chaos w kodzie i jest to nieczytelne w momencie, gdy nowy czytelnik kodu zabiera się do pracy. Obiekty typu Query
zwracają różne dane, Command
umożliwia nam modyfikację — nie mogą one nigdy zajmować się obiema funkcjonalnościami jednocześnie. Zwracamy, albo modyfikujemy — tak jak nauczył nas Greg Young. Jeżeli zżyjemy się z tą zasadą, to staniemy się lepszymi developerami i wprowadzimy zasadę Single Responsibility z SOLIDa do naszych aplikacji. Pamiętajmy, że prawidłowe obiekty, to takie, które są apatyczne i introwertyczne. :) Ułatwmy im to!