Co jeśli nie mogę znaleźć nazwy?

Co jeśli nie możesz znaleźć nazwy dla funkcji? (ew. dla zmiennej, klasy itp.).

Zadaj sobie pytania - co funkcja robi? Jakie jest jej przeznaczenie? Jaka jest metafora, która by pasowała (wszystko jest pewną metaforą)?

Czasem też sprawdza się taka metoda, żeby najpierw pomyśleć o metaforach/koncepcjach, tzn. najpierw wymyślić nazwę dla funkcji, jej argumenty, sposób wywołania, a dopiero potem zabierać się za implementację.

Ja jak mam zrobić "publiczne" API  (przez publiczne mam na myśli wszystko to, z czego się używa w wielu miejscach - np. klasa, która będzie używana przez 20 różnych modułów w projekcie) - to najpierw staram się wymyślić ładne i wygodne API, wtedy myślę o abstrakcjach, nazwach itp. Otwieram sobie oddzielny plik i po prostu "piszę na sucho" przypadki użycia danej funkcji (w momencie kiedy jeszcze implementacja nie jest napisana). A potem staram się dostosować implementację do nazw i do API, które sobie wymyśliłem.

(zauważ, że im bardziej "prywatna" jest dana rzecz, tym mniejsze znaczenie ma nazwa. Np. powiedzmy, że mamy taką funkcję używaną w 50 różnych miejscach w projekcie:
```
function distanceFromTwoPoints(x1, y1, x2, y2) {
   let foo = (x2 - x1);
   let bar = (y2 - y1);
   return Math.sqrt(foo * foo + bar * bar);
}
```

nawet jak nie chodziliśmy do szkoły i nie znamy wzoru Pitagorasa, to po samej nazwie wiemy, co funkcja robi (liczy "distance from two points"). Czyli nazwa tej funkcji (oraz nazwy jej parametrów) jest bardzo ważna. Jakby nazwać tę funkcję `dupaBlada` to w 50 miejscach w projekcie mielibyśmy ciężkiego WTFa, dlaczego ktoś nazwał funkcję `dupaBlada`.

Jednak już zmienne lokalne tej funkcji mają małe znaczenie (w tym konkretnym przypadku). Fakt, że różnicę między współrzędną x jednego punktu nazwaliśmy `foo` a różnice między wartościami y nazwaliśmy `bar` nie ma (w tym przypadku) wielkiego znaczenia - bo są to małe lokalne zmienne, używane w jednym miejscu.

Nie znaczy, że powinniśmy nazywać zmienne `foo` czy `pomidor` bo to zła praktyka  (szczególnie, że funkcje się często rozrastają - 2 linijkowa funkcja z nazwą pomidor użytą 2 razy, nie jest szkodliwa - 20 linijkowa funkcja ze słowem pomidor w co drugiej linijce? To już nie jest fajne). - chodzi mi tylko o to, że szkody mogą być czasem niewielkie, jeśli te nazwy są dość lokalne (min. dlatego warto przestrzegać zasady enkapsulacji - żeby chaos siedzący w jednej funkcji czy w jednej klasie nie rozlał się na cały projekt).

Tak samo funkcje/klasy itp. - źle nazwana funkcja/klasa użyta raz w jednym miejscu nie będzie tak szkodliwa jak źle nazwana funkcja czy klasa używana w 50 miejscach.

Co jeśli już kompletnie nie mamy nazwy? Jeśli refaktorowaliśmy projekt i wydzieliliśmy sobie funkcję, która robi ileś rzeczy naraz.. Wiemy, że tak się nie powinno robić, że to pewnie łamie zasadę SRP - ale cóż, zrobiliśmy, tak nam pasowało. I co teraz?

Myślę, że najpierw powinno się pomyśleć nad samym designem (brak umiejętności znalezienia nazwy to często syndrom złej architektury. Może po prostu źle to zrefaktorowaliśmy).

Ale czasem trzeba się pogodzić z nieidealnym designem.

Czasem jest tak choćby, że funkcja robi kilka rzeczy naraz i ciężko znaleźć nazwę. Wtedy można ją od biedy nazwać w sposób verbose, po prostu opisując wszystko to, co robi

```
drinkCoffeeAndStandAndWalkAndPutWordsIntoSentence();
```

Brzydko to wygląda, ale wtedy nazwa funkcji przynajmniej będzie odzwierciedlać twój nieidealny design (być może nawet pogwałcenie SRP) i może potem odnajdziesz lepszą nazwę, albo podzielisz funkcję na kilka mniejszych.

I tak czasem lepsze verbosity niż nazywanie funkcji w sposób generyczny np.
```
prepareData()
performOperations()
doThings();
```
(czasem oczywiście takie nazwy mają sens, ale czasem to taka zasłona dymna - nie wiemy, jak coś nazwać, więc nazywamy to byle jak, i wprowadzamy nazwą w błąd).

No i jak masz nazwę typu drinkCoffeeAndStandAndWalkAndPutWordsIntoSentence(), to przynajmniej widzisz, że coś jest nie halo i że należałoby to zrefaktorować, może podzielić na kilka mniejszych? I myślisz o refaktorze za każdym razem, kiedy używasz tej funkcji. Jak masz nazwę generyczną, to potem możesz nawet zapomnić, że coś było nie tak. Czasem nawet taka generyczna nazwa może wprowadzać w błąd (przegadana nazwa też może, ale łatwiej to zauważyć).

Nie znaczy, że lubię takie długie nazwy, raczej preferuję proste krótkie nazwy i dobrą metaforę. Taką, która się sprawdzi nawet po czasie.

Czasem jednak znalezienie odpowiedniej metafory nie jest to takie proste. Np. w swojej grze nazwałem obiekty postaci za pomocą klasy `Character`.

Wszystko było fajnie, dopóki faktycznie były to postacie. Jednak potem uznałem, że mogę użyć tej samej klasy dla innych obiektów (np. do interaktywnych elementów nieożywionych w świecie gry). Więc będę musiał zmienić nazwę. Na jaką? Myślałem o GameObject, ale wydaje mi się to dość słabą nazwą. W mojej grze będą różne obiekty, ale nie wszystkie zawierają logikę. Obiekt planszy czy teksturę też można by określić "obiekt gry", ale w tym przypadku nie potrzebują być instancjami klasy GameObject. Do tej klasy będą należeć tylko te obiekty gry, które zawierają logikę i mogą być interaktywne.

W końcu szykuję się do tego, żeby w nowej implementacji gry nazwać te interaktywne obiekty mianem "aktorów" (słowo "actor" brzmi, jakby chodziło o postać/człowieka, ale w rzeczywistości metafora aktora jest już zakorzeniona w programowaniu od kilkudziesięciu lat - mamy "actor model" chociażby)

Anyway - czy żałuję, że użyłem wcześniej nazwy Character? Nie. Bo to się wydawało odpowiednią metaforą na tamtym etapie kodzenia. Teraz mam lepszą.

Ogólnie myślę, że warto przemyśleć tego typu rzeczy z dala od kompa. Popatrzeć w całości na nasz system (albo kawałek systemu, nad którym aktualnie pracujemy) i popatrzeć nie tylko na jedną funkcję, ale na cały kontekst (czasem okazuje się, że dana nazwa nie pasuje do kontekstu. Np. koliduje z innymi nazwami). Pomyśleć też, czy dana nazwa będzie odpowiednia jeśli zmienią się okoliczności (np. fakt, że nazwałem klasę Character świadczy o tym, że nie wyszedłem jednak wystarczająco w przód i nie pomyślałem "co jeśli będę chciał w przyszłości mieć interaktywny element, który nie jest postacią?").

Zauważcie też siłę metafory.

Czyli proste Actor zamiast np. InteractiveGameObject, na które mógłbym wpaść, gdybym był fanem dziedziczenia (i tworzenia hierarchii obiektów typu GameObject na górze, potem potomkowie InteractiveGameObject, VisualGameObject itp.). Na szczęście nie jestem. Dziedziczenie, poza innymi swoimi wadami, często pcha nas prosto w objęcia złych nazw.

Dobra, to tyle na dzisiaj, może znowu coś o tym napiszę.





Komentarze

Popularne posty z tego bloga

Ściemy z ogłoszeń o pracę

Zostać programistą (2) - czy się nadajesz?

Czego się nauczyłem w 2021?