Zasada pojedynczej odpowiedzialności

Single responsibility principle to podstawowa zasada dobrych praktyk w programowaniu obiektowym. Wszyscy ją znają. To ona jest naszym S w SOLID. Wydaje się, że wszystko o niej zostało już powiedziane. Czy na pewno? Przyjrzyjmy się bliżej.

Zasada pojedynczej odpowiedzialności mówi, że każda klasa powinna odpowiadać tylko za jedną rzecz. Dokładniej Robert C. Martin proponuje taką definicję:

Nigdy nie powinno być więcej niż jednego powodu do modyfikacji klasy.

Proste i łatwe do zapamiętania. Chodzi oczywiście o to, aby nie tworzyć klas typu spaghetti – długich na wiele ekranów robiących wszystko i trudnych do zarządzania. Klasy powinny być małe i proste. Kod powinien być przejrzysty.

Zasada ta zachęca do korzystania z wzorców projektowych. Zamiast rozwiązywać problem pisząc kod strukturalny korzystajmy z mocy obiektów. Każdy z nas widział klasy, które klasami były jedynie z nazwy. Tak naprawdę były to biblioteki funkcji tylko zamknięte w formie klasy. A gdzie dziedziczenie, enkapsulacja, ponowne używanie?

Polemika z jedną odpowiedzialnością.

Robert C. Martin z pewnością świetnie zna się na programowaniu. Ale popatrzmy na cytat innego geniusza:

Wszystko powinno być tak proste jak się da ale nie bardziej.

Tak, to powiedział Albert Einstein. Dla nas najważniejszy jest zwrot “ale nie bardziej”. Okazuje się, że i z tą zasadą można przedobrzyć.

Zarzut pierwszy – czym jest zmiana?

Zasada jednej odpowiedzialności jest mocno niejasna. Skoro możemy mieć tylko jeden powód do zmiany – co będzie tym powodem? Naprawa błędów? Refaktoring? Zmiana logiki biznesowej? Jeśli pozwolimy sobie tylko na jeden powód do zmian to albo utkniemy w klasie, w której jedynie poprawiamy błędy albo zmiana działania nie pozwoli nam na inne poprawki w kodzie?

To oczywiście ekstremalny przykład. Ale czyż jako informatycy nie chcemy jasnych i precyzyjnych określeń? Może powinniśmy więc dodać w naszej regule dopisku “w miarę rozsądku”? Wtedy będzie jeszcze bardzie niekonkretna.

Zarzut drugi – przewidywanie przyszłości

Skąd możemy wiedzieć jakie w przyszłości pojawią się powody do zmiany kodu? Czy będzie jeden kod czy dwa? A może zechcemy zintegrować naszą klasę z innym systemem i będzie trzeba zmienić jej nazwę aby uniknąć konfliktów? To techniczna zmiana ale też zmiana.

Okazuje się, że zasada powoduje, że prawo działa wstecz. Dowiadujemy się czy ją spełniliśmy dopiero gdy nadeszła potrzeba zmian.

Zarzut trzeci – czym jest odpowiedzialność?

Zasada nie definiuje poziomu abstrakcji. Weźmy sobie np. klasę do obsługi plików. Czy powinniśmy mieć osobne klasy do czytania i zapisywania? W końcu to dwie różne operacje. A może potraktujemy “obsługę” plików jako jedną odpowiedzialność?

Można sobie wyobrazić dwóch programistów. Jeden stosuje tą zasadę na bardzo szczegółowym poziomie. Drugi traktuje ją ogólnie. W efekcie pierwszy oskarża drugiego o łamanie zasady a drugi mówi, że pierwszy przesadza z jej stosowaniem.

Przestrzeganie pojedynczej odpowiedzialności na ślepo może doprowadzić do takich konfliktów. Może podzielić nam kod na wiele niepotrzebnych drobnych klas. Trzeba znać umiar.

Rozwiązanie – lepsza wersja zasady

Spróbujmy przedefiniować naszą zasadę pojedynczej odpowiedzialności aby miała większy sens:

Klasa, funkcja, metoda itp powinna odpowiadać za pojedynczą logikę biznesową na jednym poziomie abstrakcji.

Co nam to daje? Bardzo proste i praktyczne reguły:

  • Nie tylko klasy ale również funkcje, metody, interfejsy i inne konstrukcje powinny być spójne. Powinnismy unikać nadmiernego przeładowania ich funkcjonalnością. Sama klasa może mieć wiele metod ale każda z nich również powinna realizować tą zasadę.
  • Logika biznesowa czyli esencja, mięsko, sens istnienia naszej klasy. Zmianą nazywamy modyfikację, która zmienia sens jej działania. Dzięki temu możemy bezkarnie poprawiać błędy, refaktoryzować itp.
  • Pojedynczy poziom abstrakcji mówi nam, żeby nie mieszać operacji wyższego i niższego rzędu. Gdybyśmy mieli metodę ZapiszJakoPDF() to mieszamy dwa poziomy abstrakcji. Wprawdzie to jedna logika biznesowa, która daje nam zapisany plik ale sama konwersja PDF jest operacją na wyższym poziomie niż zapisanie. Nie mieszajmy poziomów abstrakcji.

Marco Cecconi podaje nam jeszcze ciekawszy zamiennik zasady jednej odpowiedzialności:

Sensem istnienia klas jest taka organizacja kodu aby był jak najmniej skomplikowany. Dlatego klasy powinny być:

  1. Wystarczająco małe aby zmniejszyć zazębianie się problemów
  2. Wystarczająco duże aby spójnie obsługiwać zagadnienie

Nadal nie jest to bardzo konkretna definicja, ale przynajmniej daje nam poczucie równowagi. Nie dzielimy już kodu niepotrzebnie na małe fragmenty ani nie piszemy wielkich klas do wszystkiego.

Na koniec

Warto poszukać w internecie komentarzy do tej zasady. W wielu dyskusjach jest ona szeroko krytykowana. W zamian proponowane są inne, bardziej praktyczne.

Dzięki takim dyskusjom możemy dostrzec zdrowy rozsądek, którym powinniśmy się kierować przede wszyskim, przed skorzystaniem z którejkolwiek z dobrych praktyk programowania.

Dodaj komentarz

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