Może temat wydaje się banalny i wyświechtany, bo któż nie wie, że trzeba tworzyć jednoznaczne, czytelne nazwy. Natomiast ciągle jest obszar bardzo zaniedbany. Ciągle zespoły są dalekie od zrozumienia, że to właśnie od nazewnictwa zależy najwięcej. Żadna refaktoryzacja nie ma takiej mocy, jak zmiana nazwy. To przede wszystkim nazwy, jeśli stosowane odpowiednio, tworzą tzw. samodokumentujący się kod, kreują jednoznaczny język w źródłach systemu, który tworzysz.
Temat opisany wprawdzie w Clean Code, ale nie tak konkretnie jak ja to widzę, nieco po wierzchu i z innej perspektywy.
Dzisiaj kilka słów o anatomii metody (funkcji). Każda metoda ma tzw. sygnaturę czyli nazwę, parametry oraz zwracany typ, kluczowe składowe z punktu widzenia refaktoryzacji, gdyż na czytelność składają się wszystkie trzy elementy – nie tylko nazwa.
Ponadto można powiedzieć, że są dwa rodzaje metod:
• zwracające wynik;
• niezwracające wynik.
Metody zwracające wynik
(Uwaga! Poniższe zasady nie dotyczą tzw. interfejsów typu fluent)
double computeSomeResult(int parameter1, double parameter2, int parameter3 …)• nazwa metody zawsze konkretnie i jednoznacznie musi określać, czym dana metoda się zajmuje i musi z niej wynikać, co będzie jej wynikiem;
• sygnaturę metody twórz zawsze w kontekście klasy, w której się znajduje (nie powtarzaj nazwy klasy w nazwie metody);
• tym samym metody zwracające informacje o błędach powstałych podczas jej wykonania są antywzorcem (choć oczywiście jest śladowa liczba przypadków, kiedy taka strategia może być uzasadniona – zazwyczaj gdy pracujemy z kodem odziedziczonym); tej chorobie ulega wiele frameworków;
• wysoce karalne jest traktowanie parametrów wejściowych jako sposób na zwracanie wyniku, co najczęściej dzieje się w przypadku opisanym w punkcie powyżej;
• parametry metody współtworzą znaczenie z nazwą – muszą ją uzupełniać i być z nią zgodne.
Przykłady i komentarze
1)
List<Content> retrieveChildren(Content parentContent)
Ok! Nazwa sugeruje, że będą wyszukiwane dzieci parametru i taki też jest zwrócony typ.
Bardziej fluentowe wersje (TBD wyjaśnić co to):
List<Content> retrieveChildrenOf(Content parentContent) List<Content> childrenOf(Content parentContent)Lub jeśli byłoby to adekwatne do kontekstu, metoda childrenOf mogłaby być składową klasy Content i wtedy jej sygnatura wyglądałaby:
class Content { List<Content> retrieveChildren() // .. // lub bardziej fluentowo List<Content> children() // .. }2)
interface ContentProcessorResolver { //… /* @return zwraca listę treści dostosowaną do danego resolwera */ public List<? extends Content> resolve(Content content); //… }Zła nazwa – sugerowałaby (w kontekście interfejsu, w którym się znajduje), że wynikiem będzie obiekt klasy ContentProcessor.
TBC…