Zasady SOLID

SOLID – Zasada jednej odpowiedzialności

Zasada jednej odpowiedzialności czyli Single Responsibility Principle (SRP).

Wikipedia mówi, że

Klasa powinna mieć tylko jedną odpowiedzialność (nigdy nie powinien istnieć więcej niż jeden powód do modyfikacji klasy).

zaś na innej stronie Wikipedii czytamy, że

Martin utożsamia odpowiedzialność klasy z powodem do jej modyfikacji. Możemy rozważyć moduł, który generuje i drukuje raport. Odpowiada on za dwa procesy, a tym samym mogą wystąpić dwa powody do jego modyfikacji. Po pierwsze, może zmienić się treść generowanego raportu, po drugie – format, w jakim jest on drukowany. Zasada pojedynczej odpowiedzialności mówi, że oba te procesy powinny być niezależne i zaimplementowane w postaci dwóch oddzielnych klas lub modułów, które komunikują się ze sobą za pomocą publicznych interfejsów.

Przykład

Poniżej interfejs, w którym mamy dwie odpowiedzialności w klasach dziedziczących po interfejsie IReportGenerator. Jest to podejście niezgodne z zasadą jednej odpowiedzialności. Powodem do zmiany implementacji może być zarówno sposób generowania raportu jak i wysyłania go do odbiorcy.

public interface IReportGenerator
{
   object Generate();
   void SendReport(Recipient recipient);
}

Proponowałbym „wyjęcie” SendReport z istniejącego interfejsu i utworzenie nowego:

public interface IReportGenerator
{
   object Generate();
}

public interface IReportSender
{
   void SendReport(Recipient recipient);
}

oraz implementację tych interfejsów w dwóch różnych klasach.

Wątpliwości

No dobra, ale załóżmy, że generowanie raportu można rozbić na kolejne etapy:

  1. pobranie danych z serwera,
  2. przeprocesowanie czyli wyliczenie jakichś wartości,
  3. przygotowanie/sformatowanie danych wyjściowych.

Czy znowu powinienem tworzyć trzy klasy, aby odseparować różne odpowiedzialności, które tutaj widzę? Gdzie jest ta cienka, subiektywna granica, której przekroczyć się nie powinno, a która nie jest oczywista?

Jak coś jest do wszystkiego to jest do niczego.

To pierwsze skojarzenie, które zawsze przychodzi mi na myśl o SRP, ale ślepemu przyjęciu tej zasady znowu przeszkadza mi trudność w ustaleniu „grubości ziarna”. Czy powodem zmiany jest dodanie/zmiana źródła danych, czy może usunięcie jednej kolumny z wydruku? Czy może potraktować te wszystkie czynności jako integralną część generowania raportu?

Moja odpowiedź na to pytanie i jemu podobne brzmi: MAM TO GDZIEŚ!!! To nie znaczy, że nie respektuję zasady SRP wręcz przeciwnie, jestem jej gorącym wyznawcą i to nie tylko w programowaniu. Moje MAM TO GDZIEŚ to raczej manifestacja tego, że nie zamierzam się nad tym zbyt długo zastanawiać. Jeżeli zrobię dobrze, to kiedyś na to konto obejrzę sobie film, bo będę miał czas. Jeżeli podejmę złą decyzję to wyryję sobie na ścianie kolejny przypadek, w którym trzeba robić tak, a nie inaczej i wrzucę sobie to w worek pod tytułem DOŚWIADCZENIE, z którego skorzystam w przyszłości. Umiejętność korzystania z SRP to tysiące linii napisanego kodu, a nie akademicka dyskusja. Tak mam. Do rzeczy.

Jak mam w aplikacji trzy raporty na krzyż to mam jedną klasę je generującą, a w jakimś kontrolerze czy code behind strony zaciągam dane w zależności od wybranego raportu i wyrzucam na stronę np:. nieśmiertelny CSV, który generowany jest w oddzielnej klasie do generowania CSV’ów. Dla każdego raportu mam oddzielną klasę i to w tej klasie odbywa się zarówno pobranie jak i przygotowanie danych, ale mam zawsze jeden powód do zmiany w tej klasie: ZMIANA LOGIKI GENEROWANIA RAPORTU.

A więc powód zmiany zależy od treści postawionego pytania w kontekście danej klasy i tutaj kieruję się zawsze rozsądkiem, aby przypadkiem nie ulec accidental complexity i nie wprowadzać niepotrzebnej złożoności kodu poprzez zbytnie rozczłonkowanie funkcjonalności na kawałki o minimalnej odpowiedzialności czy też nie wpaść w pułapkę antywzorca poltergeists, w którym takie małe subfunkcjonalności zaczynają przesyłać między sobą parametry dla samego przesyłania. Debuggowanie tego to koszmar.

Z drugiej strony należy pamiętać o unikaniu Boskich obiektach (God object), w których umieszcza się zbyt wielu funkcji w jednym komponencie (klasie). Obarczenie ich nadmierną odpowiedzialnością powoduje problemy w utrzymaniu kodu i wyodrębnieniu funkcjonalności. W moim odczuciu jest to główny antywzorzec w kontekście SRP, a SRP jest głównym narzędziem w walce z boskimi obiektami.

Co uzyskujemy przez stosowanie zasady jednej odpowiedzialności?

  1. Przejrzysty kod.
  2. Łatwość testowania małych klas.
  3. Łatwość i skuteczność debugowania i wykrywania błędów.
  4. Łatwość zarządzania, w tym także podejmowania kolejnych decyzji projektowych. Ileż to razy już słyszałem lub … hmmm … zdarzało się … musiałem mówić: NIE DA SIĘ, BO SYSTEM NA TO NIE POZWALA, lub DODANIE KOLEJNEGO POLA ZAJMIE 2 DNI.
  5. Reużywalność kodu, chociaż tutaj zalecana jest ostrożność czego przykładem może być post Gutka: może być tylko JEDEN.
  6. Wdzięczność wszystkich, którzy będą z nami lub po nas w projekcie uczestniczyć.

Leave a Reply

Your email address will not be published. Required fields are marked *