Zapachy kodu (code smells)

Czy zastanawiałeś/aś się jaki zapach ma Twój kod? Przyjemny, z nutą świeżo mielonej kawy i skoszonego o poranku trawnika? A może wyczujesz zapach pleśni czy nieświeżości? Dzisiaj dowiesz się, jak ten zapach sprawdzić i co zrobić jeśli jest brzydki.

Czeeej… To kod ma zapach?

Zapachy kodu, z ang. code smells, to termin odnoszący się do fragmentów w naszym kodzie, które mogą zostać zaimplementowane lepiej i dają nam jasne sygnały, że powinny zostać zrefaktoryzowane. Pojęcie code smell zostało zdefiniowane przez Kenta Becka, a do szerszego grona odbiorców dostało się dzięki książce o refaktoryzacji, autorstwa Martina Fowlera. Skąd wiadomo, że mój kod śmierdzi? Istnieją dwa główne sposoby podziału zapachów, jednak najbardziej popularnym jest ten zaproponowany przez Mika Mäntylä. Dzieli on zapachy na pięć kategorii, w których znaleźć możemy opisy fragmentów kodu, które mają tzw. „zapach”. Jeżeli więc widzisz, w swoim projekcie któryś z nich, to znaczy, że Twój projekt ma nieświeży oddech.

Czy wiesz, że: członkowie newslettera mogli przeczytać ten wpis, jako prezent świąteczny, 25. grudnia? Cztery miesiące przed publikacją! Zapisz się do listy mailingowej już dziś i nie przegap kolejnych bonusów.

The Bloaters (wzdęcia)

Pierwszą kategorią code smells są tzw. „wzdęcia” – bloasters. Określają one fragmenty kodu, które urosły do dużych rozmiarów i nie jesteśmy już w stanie zręcznie się nimi posługiwać. Może to dotyczyć klas, funkcji czy nawet całych komponentów i modułów. W skład tej grupy wchodzą następujące zapachy:

  • Long Method – za długa metoda. Bardzo ogólny smell, jednak chodzi w nim o to, aby nasze funkcje i metody były małe. Nie ma konkretnej, dozwolonej długości funkcji, jednak przyjmuje się, że metody, których ciało jest dłuższe niż 10-30 linii zahaczają o ten zapach.
  • Large Class – smell związany ze zbyt długą, ale klasą. Dokładnie tam sam problem, co w przypadku long method. Jeżeli nasza klasa jest zbyt duża lub ma zbyt wiele pól / metod, to pachnie, jak large class.
  • Long Parameter List – zbyt wiele argumentów. Przyjmuje się, że funkcja, bądź metoda, powinna przyjmować maksymalnie trzy/cztery argumenty. Jeżeli lista argumentów jest dłuższa, to Twój kod posiada zapach Long Parameter List. Można się przed nim ustrzec redukując ilość argumentów funkcji w jeden, poprzez użycie obiektu transferowego: DTO.
  • Data Clumps – skupiska danych. Na pewno niejednokrotnie spotkałeś/spotkałaś się z sytuacją, że grupa pewnych zmiennych była powielona w kilku miejscach. Np. zmienne przechowujące adres url, api key i port do wykonania zapytania na zewnętrznej usłudze, po protokole http. Powielanie tych zmiennych, np. w każdym serwisie, to code smell. Dla tego typu danych powinna zostać zdefiniowana dedykowana klasa.
  • Primitive Obsession – ten smell jest powiązany z data clumps, ponieważ chodzi w nim o luźnie istniejące dane, które tak naprawdę powinny być jednością. Opakowywanie prymitywów w dodatkowe klasy, zamiast powielać logikę wszędzie tam, gdzie jakiś prymityw ma zostać użyty, to lekarstwo na ten smell. Wyobraźmy sobie, że w dwóch osobnych polach zapisujemy numer telefonu: numberPrefix oraz number. Prefiks trzyma kierunkowy danego kraju, zaś numer, to przykładowo, dla polski, 9. cyfr. Teraz wyobraźmy sobie obiekty: user, company, client, contractor itp. W każdym z nich powielamy obydwa pola i logikę z nimi związaną! Zamiast tego stwórzmy pole phone i klasę PhoneNumber, która będzie go reprezentować i uspójniać całość.

The Object-Orientation Abusers (łamańce OOP)

Kolejną grupą code smell-i są: OOP abusers czyli zapachy, które łamią zasady programowania obiektowego. Jeżeli już programujemy w paradygmacie obiektowym, to spełniajmy jego zasady i założenia. Wyróżnić możemy cztery smell-e w tej kategorii:

  • Alternative Classes with Different Interfaces – sytuacja, kiedy dwie klasy wykonują w swoich metodach te same operacje, jednak ich nazwy się różnią. Przykładowo, wyobraźmy sobie, że mamy klasę BusinessUserRepository, z metodą findOne oraz UserRepository z metodą findOneById. Obie te metody zwracają rekord z tabeli users, na podstawie identyfikatora. Jaki jest więc sens posiadania obydwu?
  • Temporary Field – pola tymczasowe. Z tym zapachem mamy do czynienia, kiedy klasa zawiera pole, które posiada wartość jedynie w pewnych warunkach. Przykładem może być wspólna klasa dla użytkownika indywidualnego i firmy. Mamy wtedy do obsłużenia pole NIP, które jest puste jeśli użytkownikiem nie jest firma.
  • Switch Statements – za dużo ifologii i switch-y. Jeżeli Twój kod zawiera bardzo długie bloki instrukcji warunkowych lub case’ów w switch-u, to znaczy, że pachnie tym zapachem. Należy wtedy rozważyć wprowadzenie wzorca strategii
  • Refused Bequest – zapach, który występuje, jeżeli klasa, która dziedziczy inną klasę używa tylko części z jej metod i właściwości. W takich przypadkach, często metody z rodzica są nieużywane lub implementujemy je tylko po to, aby rzucić wyjątek w stylu: not implemented

The Change Preventers (utrudniają zmiany)

Change preventers, to zapachy kodu, które dotyczą zamykania się na rozbudowę oprogramowania. Utrudniają dodawanie nowych funkcjonalności, a także zmiany tych już istniejących. W ich skład wchodzą następujące smell-e:

  • Divergent Change – jest to zapach, który istnieje, jeśli chcemy dodać np. nową właściwość do klasy, a w efekcie czego musimy zmodyfikować jej wszystkie metody. Wyobraźmy sobie, że w klasie Player istnieje pole state. W zależności czy gracz żyje, umarł czy jest sparaliżowany, zmienia się logika w metodach movesayattack. Wtedy, jeśli dodamy nowy stan, to musimy wszystkie te metody zaktualizować o nowe zachowania. Chęć dodania nowej funkcjonalności wywołała lawinę zmian w obrębie klasy.
  • Parallel Inheritance Hierarchies – konieczność równoległego dziedziczenia. Zapach ten występuje, kiedy chcąc rozszerzyć jedną klasę, musimy zrobić to samo z inną. Wyobraźmy sobie, że mamy klasy: Party oraz PartyPlan. Po Party dziedziczą np. IndividualClient i BusinessClient. Mamy także IndividualPlan oraz BusinessPlan, które rozszerzają PartyPlan. Za każdym razem, jak dojdzie nowy typ klienta np. AbroadClient, który rozszerzy Party, to musimy stworzyć także AbroadPlan, dziedziczący po PartyPlan.
  • Shotgun Surgery – lawina zmian w systemie. Na pewno każdy z nas spotkał się kiedyś z sytuacją, że przy próbie dodania funkcjonalności w jednym miejscu, konieczna była zmiana np. dziesięciu innych. To właśnie jest zapach shotgun surgery, który ma miejsce wtedy, kiedy zmieniając jedno miejsce, musimy zmodyfikować inne, często lawinowo.

Shotgun Surgery od Divergent Change różni się tym, że w przypadku tego pierwszego, konieczna jest zmiana wielu miejsc – np. kilku różnych plików i funkcji. Zaś przy Divergent Change, zmieniamy np. tylko jedną klasę.

The Dispensables (rzeczy zbędne)

Zapachy kodu z kategorii Dispensables pokrywają wszelkiego rodzaju duplikacje czy też nieużywany kod. Jeżeli nie chcesz, aby Twój projekt nimi pachniał, to pamiętaj o jego sprzątaniu od czasu do czasu. W tej kategorii wyróżnić możemy:

  • Data class – klasa zawierająca jedynie pola. Ten zapach występuje, jeśli klasy mają w sobie jedynie właściwości oraz getter-y i setter-y, a mogłyby posiadać także inną logikę. Z tym problemem często borykają się modele anemiczne. Wyjątkiem dla tego zapachu są obiekty transferowe, tzw. DTO (data transfer objects), które właśnie w ten sposób wyglądają. 
  • Lazy Class – leniwe klasy. Zbyt duża granulacja odpowiedzialności pomiędzy klasami i komponentami, może powodować powstawanie tzw. klas leniwych. Jeżeli w Twoim kodzie istnieją tego typu byty, które mogłyby być z powodzeniem połączone z innymi klasami, to kod Twój pachnie tym zapachem.
  • Duplicate Code – duplikacje kodu. W tym przypadku nie ma zbyt wiele do tłumaczenia. Smell ten występuje, jeśli w naszym kodzie są duplikacje, bądź kolejne fragmenty kodu wyglądają podobnie.
  • Dead Code – zapach, który zazwyczaj powstaje wraz z wiekiem projektu. Dochodzi nam coraz więcej nieużywanego kodu, który pozostał w repozytorium, ale jest nieużywany. Wlicza się w to także zakomentowany kod.
  • Speculative Generality – Twój kod posiada ten zapach, jeśli w klasie istnieją nieużywane właściwości, parametry, bądź metody. Włącza się w to także nieużywane elementy, pochodzące z abstrakcji, np. innych klas. W takich sytuacjach należy uszczuplić naszą klasę tak, aby zawierała tylko potrzebne elementy.

The Couplers (zwiększają coupling)

Ostatnią kategorią code smells są couple-ry. Są to zapachy, które zwiększają coupling w naszym projekcie. O samym coupling-u powiem więcej już niebawem, niemniej jednak, w tym przypadku mowa jest o tym niechcianym, niekontrolowanym coupling-u elementów. W skład tej grupy zapachów wchodzą:

  • Feature Envy – zazdrość o implementację. O tym zapachu wspominałem już kilkukrotnie, chociażby we wpisie o modelach bogatych i anemicznych. Występuje on wtedy, gdy np. serwis wyciąga z modelu dane (np. getter-em), realizuje na nich pewną logikę, a następnie zapisuje jej wynik na powrót do modelu (np. setter-em). Ta logika powinna zostać umieszczona bezpośrednio w modelu, a serwis powinien wywołać tylko jego odpowiednią metodę. To model powinien być jedynym źródłem prawdy, w jaki sposób, w tym przypadku, coś ma zostać przetworzone.
  • Inappropriate Intimacy – niepoprawna granulacja. Smell, który ma miejsce, jeśli klasy za bardzo przenikają się na wzajem. Przykładowo, jeśli klasa User używa wewnętrznych metod i właściwości klasy Company. Sztywne powiązania pomiędzy klasami powinny być zawsze dokładnie analizowane.
  • Message Chains – łańcuchy wywołań. Zapach występujący, kiedy w naszym kodzie pojawiają się wywołania łańcuchowe, np. User.create().notes.push(new Note()). Takie fragmenty kodu są trudne w testowaniu i debugowaniu. 
  • Middle Man – niepotrzebny pośrednik. Za każdym razem, jeśli istnieje klasa, która jedynie deleguje zadanie do innej, to posiada ona zapach Middle Man. Częstym przykładem, gdzie występuje ten smell są klasy serwisowe, z metodami w stylu findOne, których jedynym zadaniem jest wywołanie metody na repozytorium i zwrócenie jej wyniku.

Jak ustrzec się przed zapachami?

Jeżeli nie chcesz, aby Twój projekt brzydko pachniał, to musisz pamiętać, co powoduje konkretny zapach i przestrzegać reguł clean code. Możesz też zastosować narzędzia do statycznej analizy kodu, które systematycznie (np. przy nowym commit-cie) będą sprawdzały to, co napisałeś/napisałaś. Poinformują Cię one, który fragment wymaga Twojej uwagi, bo jego zapach jest nieprzyjemny. Jednym z takich narzędzi jest SonarQube.

Podsumowanie

  • Zapachy, to fragmenty kodu, które powinny zostać zrefaktoryzowane.
  • Wyróżniamy pięć kategorii code smells.
  • Do wykrywania zapachów służą narzędzia do statycznej analizy kodu.
Autor wpisu

blog@orbisbit.com

Komentarze

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Sprawdź również
  • CQRS – Command Query Responsibility Segregation

    Command Query Responsibility Segregation czyli CQRS. Jest to wzorzec projektowy, który rozdziela zadania odczytu i zapisu do osobnych modeli. Sprawdź ten wpis, aby dowiedzieć się kiedy i jak z niego skorzystać.

    Zobacz wpis

  • GRASP – kolejny zbiór zasad Clean Code do zapamiętania

    Pewnie większość z Was słyszała o zasadach SOLID. Są one bardzo rozpowszechnione i dosyć często stosowane, ale czy słyszeliście o GRASP? General Responsibility Assignment Software Patterns, to kolejna dawka zasad czystego kodu do zapamiętania.

    Zobacz wpis

  • Wzorzec strategia (strategy pattern)

    Jeżeli masz dość if-ologii w swoim kodzie, to konieczne sprawdź czym jest czynnościowy wzorzec projektowy strategia. Pozwala on mądrze obsługiwać różne scenariusze w procesie i jednocześnie być fancy pod względem zasad SOLID.

    Zobacz wpis