Kompleks wielkości metod
Podczas code review różnych projektów spotykam się z problemem, który na pozór nie wydaje się oczywisty. Przecież każdy z nas w czasach, gdy był młodszym, dopiero co rozwijającym się developerem chciał być jak starsi doświadczeniem koledzy z pracy. Co robili więc starsi stażem? Pisali małe metody, funkcje – my zaś chcieliśmy być jak oni i pisać podobne rzeczy. Niestety poszło to często w złą stronę i nie widzieliśmy różnic między sensownym wydzielaniem, a klepaniem małych metod na siłę. Nazwałbym to kompleksem dużych metod. Oczywiście na wszystko jest sposób!
Uncle Bob i „Clean Code”
Nie chciałbym zostać posadzony o podważanie autorytetu wielu z nas czyli Roberta C. Martina znanego wszystkim jako Uncle Bob. W jednej z jego książek, którą uważam za totalny obowiązek w lekturze każdego programisty jest sporo informacji o pisaniu małych metod, tworzenia małych elementów składających się w jedną całość. Ok – to oczywiście ma sens, małe elementy, które są opisane odpowiednimi nazwami, to coś niesamowicie łatwego do analizy, ale… gdy to ma sens. Niestety nie zawsze tak jest, ale o tym dalej...
Kiedy niekończące się dzielenie nie jest najlepszym pomysłem?
Osobiście uważam, że metody prywatne wewnątrz klas, które są w nich wykorzystywane tylko jeden, jedyny raz to nie zawsze najlepszy sposób na zakodowanie funkcjonalności. Pamietajmy o zasadzie SRP, która powinna odnosić się przede wszystkim do klas. – W momencie gdy dzielimy klasę i jej jedyną publiczną metodę stanowiącą jej interfejs i powstaje nam masa metod prywatnych, to warto zastanowić się czy jest to w ogóle w jakikolwiek sposób czytelniejsze od jednej metody. Ba! – Czy to czasami nie jest sugestia złamania wymienionej wcześniej reguły SOLIDa?
Wydzielamy do tych mniejszych metod jakieś funkcjonalności, które nie wchodzą w skład odpowiedzialności głównej, tej publicznej – może czasami być to odpowiedni „zapach” w kodzie, który mógłby nam zasugerować, że z interfejsem klasy jest coś nie tak. Oczywiście nie mam na myśli tutaj małych części klasy, które są wspólne dla paru różnych punktów dostępowych (metod publicznych) – to jak najbardziej słuszne rozwiązanie, by jeżeli istnieje taka możliwość wydzielić jedną, powtarzającą się logikę w obrębie klasy.
Uważam natomiast, że jedna publiczna metoda, która zgodnie z sugestiami Uncle Boba jest mała, spełnia jedną odpowiedzialność klasy i wystawianego kontraktu jest znacznie lepsza od rozbitej na X prywatnych elementów, które tak naprawdę niczego nie dają poza… no właśnie, niczym. Nie, nie jest to czytelniejsze.
Arnold Boczek wśród metod
Co, gdy w naszym IDE napotkamy na metodę, która mogłaby z powodzeniem robić za dublera samego Arnolda Boczka w Świecie Według Kiepskich? Jest to częsty problem, który spotykamy w świecie kodu legacy. Wiele osób na widok czegoś takiego od razu chciałoby zakasać rękawy i za pomocą młotka i dłuta wydzielić tonę małych metod, które będą odpowiadały za funkcjonalności klasy, czy samej metody publicznej. Uważam, że ok – jest to częścią prac, ale zdecydowanie nie jest ostatecznym rozwiązaniem. Po pierwsze – jeżeli metoda aż tak urosła, oznacza to, że jest często edytowana. Po drugie – skoro jest tak skomplikowana – to jej złożoność cyklometryczna pewnie sięga Kilimandżaro. W takim momencie rozbicie ogromnej metody na wiele małych nie pomoże. To w dalszym ciągu będzie rosło, a do tego utrzymanie takiego rozbitego kodu nie będzie jakby mogło się wydawać wcale łatwiejsze od utrzymania jednego, dużego wielolinijkowca. Taka sytuacja nie może obyć się bez refactoringu rozpoczętego od przeprojektowania kontraktu (interfejsu) klasy i bardziej abstrakcyjnego spojrzenia na ten komponent naszego systemu. Poprzednio utworzone małe metody mogą być dobrym obszarem roboczym do dalszych prac, skoro utworzymy je – mamy rozpiskę co tak naprawdę znajduje się wewnątrz metody i jak bardzo miała ona wylane na Single Responsibility Principle.
Na samym początku warto zadbać o testy. Wiem i zdaje sobie sprawę z tego, że wszyscy nieustannie wałkują temat testów, ale w takich sytuacjach to zdecydowanie obowiązek. Przygotuj odpowiedni zestaw jednostkowych testów, które sprawdzą podstawowe obowiązki klasy w różnych przypadkach. Później dopiero możesz rozbić całą funkcjonalność na wspomniane wcześniej prywatne metody – przejrzeć zależności między nowymi bytami i zadecydować, które elementy należałoby wydzielić do zewnętrznych klas. Rozpracowanie zależności i ich kierunku pomiędzy klasami, późniejsza implementacja lub nawet zmiana kontraktów to dopiero prawdziwy refactoring, na który zasługuje Arnold Boczek, no może jeszcze jakiś balet mongolski, ale to już po scommitowaniu. :)
Podsumowując…
Dzielmy, wyodrębniajmy odpowiedzialności, ale… z głową. Zastanówmy się czy aby na pewno zmiana przyniesie zamierzony skutek przykładowo w postaci lepszej czytelności. Im mniejszy procent metod prywatnych w aplikacji tym lepiej (poza Arnoldami!).
Chciałbym dodatkowo w tym miejscu uwrażliwić Was na odwoływanie się z wnętrza klasy z jednej publicznej metody do drugiej, innej publicznej metody. To niefajny „zapach” w kodzie nad którego wyeliminowaniem warto się zastanowić spędzając przy tym parę chwil z dobrą, pachnącą kawą. :)