Code Smells: (Zbyt) Duże klasy

Klasy
Klasy

Problem ze zbyt rozbudowanymi klasami

występuje powszechnie w kodzie zastanym. Rzadko kiedy klasa tuż po commicie jest ogromna w sensie liczby linii kodu. Często wskutek zmian robionych już później, w ‚utrzymaniu’, przybiera na wadze. Nie znaczy to, że nie można od początku napisać klasy źle bo wciąż zdarzają się programiści którzy całą logikę aplikacji zamykają w kodzie formatki. Dlaczego i po co? Znów na pierwszy plan (podobnie jak w przypadku zbyt długich metod) wychodzi nie respektowanie zasady pojedynczej odpowiedzialności. Duże klasy na 85% (strzelam ;)) mają zbyt duży zakres obowiązków.

Zbyt wiele odpowiedzialności w klasie łatwo rozpoznać przyglądając się składowym (zwłaszcza publicznym). Spójrzmy na poniższy fragment interfejsu. Załóżmy, że implementacja klasy Tap ma 1000+ linijek:

 

Jako pierwszą powinniśmy poddać ocenie spójność jaką reprezentują składowe publiczne klasy. Nawet bez, choćby względnego, pojęcia na temat kontekstu w jakim działa powyższa klasa można podzielić jej składowe na kilka grup. Spróbujmy to zrobić pamiętając, że Tap jest jak sama nazwa wskazuje abstrakcją kranu.

Pierwsze cztery metody są moim zdaniem jak najbardziej poprawne. Faktycznie w kranie możemy zakręcić i odkręcić, ciepłą lub zimną wodę. Dalej mamy metodę, z której nazwy możemy wnioskować że pozwala zmienić ciśnienie wody – średnio mi ona pasuje, ale z drugiej strony ciśnienie wody lecącej z krany zależy częściowo od tego jak bardzo odkręcimy kurek, więc nie będę się czepiał.

Dalej mamy GetWaterMeasurment()  zwracającą coś w postaci łańcucha znaków. Nazwa mi nie wiele mówi, ale na pewno wiem, że kranu o żadne miary wody poprosić nie mogę. Podobnie jest z kolejną metodą PrintCurrentReportToFile() jestem przekonany, że kran to nie drukarka.

Ostatnie trzy metody służą, wnioskuję po nazwach, do komunikacji „kranu” z bazą danych. Warte zauważenia jest to, że prezentują one różne poziomy abstrakcji tzn. posługujemy się wyższym poziomem: zapisujemy coś do bazy, by w pewnym momencie zejść do poziomu połączenia z db.

Teraz, gdy już wiemy które metody są ze sobą spójne a które nie, możemy zabrać się do refaktoryzacji. Klasę Tap kastrujemy do takiego stopnia:

Pozostałe metody publiczne przenosimy do poniższych klas:

 

WaterReportsRepository byłaby przypuszczalnie klasą czysto abstrakcyjną po której dziedziczyłyby klasy takie jak np. WaterReportsFileRepo czy WaterReportsSqlRepo itd. Mogłyby one być wstrzykiwane przez konstruktor do WaterReportSaver, a nad wszystkim czuwałby WaterSystemClient :).

Jak widać jeżeli chcemy (zwykle powinniśmy) podzielić dużą klasę na mniejsze, to skutkuje to pewnym wzrostem złożoności na poziomie projektu niosąc wiele mniejszych klas – nie wydaje mi się jednak by była to zbyt wysoka cena za łatwiejszą czytelność na poziomie klasy.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *