Solid…ne programowanie!

Po co mi zasady?

Jako początkujący programista, często zderzałem się ze ścianą komentarzy w moich MR’ach. Nie powiem, trochę mnie irytowało to, że bardziej doświadczeni koledzy są tacy “czepialscy”. Kod przecież działał, testy przeszły. A tam w komentarzach same instrukcje:
“przenieś ten kawałek kodu tam, a tamten usuń, i zmień nazwy w 40 miejscach”. Wyboru za bardzo nie było i mozolnie zmieniałem wszystkie miejsca zgodnie z tym, co zostało mi przedstawione. Teraz rozumiem, dlaczego tak się działo i dlaczego wychodzące mi czasami “bokiem” słowo SOLID jest, aż tak istotne. Różnice zauważycie, dopiero pisząc większe programy. Im bardziej zagmatwane są procesy i logika biznesowa, tym bardziej staje się jasne, że w procesie zwinnego wytwarzania oprogramowania, NALEŻY trzymać się pewnych zasad.

SOLID

Jak wiadomo SOLID wywodzi się z pierwszych liter 5 zasad:

  • Single responsibility
  • Open/Closed
  • Liskov substitution
  • Interface segregation
  • Dependency Inversion

W tym wpisie zajmiemy się pierwszą zasadą. Kolejne wpisy będą dotyczyć kolejnych zasad.

Single responsibility – czyli krótka historia pralko-suszarki

Co to w ogóle znaczy, że nasz kawałek kodu ma mieć pojedynczą odpowiedzialność? Dlaczego jest dobrze jak, nie mieszamy odpowiedzialności?
Najlepiej przejdźmy do jakiegoś “życiowego” przykładu. Pewnie każdy słyszał o takim urządzeniu AGD, jakim jest pralko-suszarka. Takie rozwiązanie prania i suszenia w jednym urządzeniu to oczywiście oszczędność miejsca i pieniędzy. Jest jedno “ale”. Jeżeli zepsuje nam się urządzenie sterujące suszarką, to chcąc oddać nasz sprzęt do naprawy, będziemy musieli na ten czas zrezygnować też z prania. Mamy tutaj tylko jedną zależność. Mimo to przykład pokazuje, jak działa złamanie zasady “S”.

Przykład

Teraz wracając do kodu. Załóżmy, że piszemy program do obsługi konta bankowego. Tworzymy klasę Account, która ma odpowiednie pola (patrz przykład). Do rozwoju programu dostaliśmy następujące wymagania: 

  • ma reprezentować konto użytkownika
  • musi mieć właściciela
  • posiadać informacje o balansie
  • posiadać identyfikator
  • musimy mieć możliwość pobrania aktualnego balansu
  • musimy mieć możliwość zmieniać balans

Stworzyliśmy piękną klasę z polami i metodami wg potrzeb klienta.Po jakimś czasie przyszedł do nas klient i przekazał nowe wymagania (tak, tak, wymagania w projekcie IT zmieniają się jak szkiełka w kalejdoskopie). 

Nowe, dodatkowe wymagania:

  • musimy obsłużyć możliwość wpłacania środków na inne konta
  • stwórzmy nowe typ konta, “biznesowe”
  • potrzebujemy wprowadzić możliwość przeliczania środków (oprocentowanie konta)
  • persystencja obiektu w bazie danych

I pojawia się problem

Mozolnie dodaliśmy do klasy nowe metody, rozwiązując całą logikę biznesową. Dodaliśmy też wszystkie zależności. Uff działa!… STOP! Uwierz mi bądź nie, za jakiś czas (względnie, krótki) klient przyjdzie z kolejnymi wymaganiami i zmianami. Klasa spuchnie do bardzo dużych rozmiarów, a każda duża zmiana spowoduje ciąg powiązanych zmian drugorzędnych.

„Spuchnięta” klasa ACCOUNT

Wydzielmy tutaj odpowiedzialności.

  1. Tworzenie obiektu i dostęp do informacji przechowywanych w polach
  2. Persystencja obiektu do, oraz możliwość jego odczytu z repozytorium
  3. Przelewy między kontami
  4. Zmiana balansu na podstawie oprocentowania konta
  5. Definicja typu konta

No i mamy rozwiązanie

Po wyodrębnieniu odpowiedzialności powstaje nam 5 elementów programu, które poprzez interakcję ze sobą spełniają założenia zdefiniowane prze klienta.

Rozbicie na klasy z pojedyńczą odpowiedzialnością

Klasa account dodaje swoją zależność do trzech innych klas (InterestProvider, AccountRepository, PaymentService). AccountType (enum) pozwala na określenie typu konta i na jego podstawie zmienić oprocentowanie konta. Jeżeli okaże się, że klient będzie potrzebował dodać nowy typ kont lub dodać nowy parametr do klasy Account, to nie będzie już to tak kłopotliwe jak wcześniej.

Przykład pokazuje rozbicie odpowiedzialności w klasach. Zasada „S” obowiązuje jednak wszędzie. Mam na myśli całą drabinkę abstrakcji… metody, klasy, microserwisy.

Za tydzień zasada „O”. Do zobaczenia!
P.S. Schemat klas pokazany powyzej jest bardoz poglądowy i nie jest w pełni zgodny z zapisem UML.

Dodaj komentarz

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