Community Edition SOLID w służbie czystej architektury

utworzone przez | 09/02/2020 | Architektura, SOLID w czystej architekturze

Pierwsza część kursu wprowadzająca do tematyki SOLID i świata czystej architektury. Idealna do rozpoczęcia przygody. Podstawy podstaw. Szczypta teorii teoretycznej do bólu. Ale ale… uzasadnienie praktyczne – czemu w ogóle ten SOLID, to tak ważny jest.

Rozmówki o kodzie

Janek: Co to jest programowanie obiektowe?
Seba: Można pisać klasy.
Janek: Jakie znasz wzorce?
Seba: Singleton, Obserwator, MVC
Janek: Wykorzystujesz w praktyce programowanie obiektowe?
Seba: Nie, rzadko. Przecież mam framework.

Taki dialog może być częścią rozmowy rekrutacyjnej. W lekko zmienionej formie, może mieć miejsce przy kanapce w firmowej kuchni. Może też dopaść cię przy kuflu zimnego piwa, podczas wieczornej eskapady społeczności programistycznej.

Kim jest Janek?

Janek Koderek to doświadczony programista i świeżo upieczony Tech Lead. Popełnił wiele złych kodów, ale zdecydowanie więcej napisał tych dobrych. Naczytał się dużo o przeczystym kodzie. Z zamiłowania jest architektem oprogramowania i fanem metodyk zwinnych.

Janek przy pracy.

Na wczoraj? OK. To co z tej listy ma dla ciebie (Product Ownerze) największą wartość biznesową? 

Taka kompilacja umiejętności pozwala mu na rzemieślnicze podejście i iteracyjną korektę, nawet jeśli coś w projekcie pójdzie nie tak. Ale o tej historii później.

Co Janek myśli o kodzie i zasadach SOLID? Jak stosuje je w praktyce? Jakiego podstawowego pytania używa, aby torturować Product Ownera, celem wydobycia z niego najbardziej wartościowych informacji?

Zacznij od “dlaczego”

Singleton już nie jest trendy – jest bardzo zły, a w wielowątkowych kodach bywa zabójczy. Bezpieczniej zastosować dependency injection. Obserwator – piszesz go sam? Masz do tego biblioteki. MVC – przecież tylko wypełniasz treścią ciała metod nazwane i narzucone przez framework.

Takie myśli latały po głowie Janka podczas rozmowy, której fragment widziałeś na początku artykułu.

Coraz to nowsze, czasem irracjonalne pomysły zleceniodawcy, zalewają twój kod. To one generują złożoność i prawdziwe wyzwania. Znajomość frameworka to za mało, aby sobie z nimi poradzić.

Jeśli marzysz o tym, aby wypłynąć na ocean głębokiej domeny, to musisz nauczyć się podstaw nawigacji. Aby nie utonąć w nawale klas legacy, musisz zrozumieć i wiedzieć dlaczego tamta metoda jest prywatna, a tu strzelamy interfejsik.

Simon Sinek występując na TED wytłumaczył dokładniej zalety zaczynania od “dlaczego?” https://www.ted.com/talks/simon_sinek…
Zerknij w ramach przerywnika.

Teoria i definicje

… na szczęście w praktyce. Spójrz co zawierają pierwsze notatki Janka ze studiów nad SOLID-em.

Notatki ze studiów

Jak na studiach, ale trochę inaczej bo: zakuj, zalicz i tym razem nie zapomnij 🤓.

  • Single Responsibility Principle (SRP) – zasada pojedynczej odpowiedzialności.
  • Open-Closed Principle (OCP) – zasada otwarte-zamknięte.
  • Liskov Substitution Principle (LSP) – zasada podstawiania Liskov (polimorfizm)
  • Interface Segregation Principle (ISP)  – zasada segregacji interfejsu. 
  • Dependency Inversion Principle (DIP) – zasada odwracania zależności.

Postaraj się nauczyć skrótów. Neurony podpowiedzą ci resztę. 

Single Responsibility Principle (SRP)

Najpopularniejsza definicja: klasa powinna mieć tylko jedną odpowiedzialność. Definicja alternatywna i najbardziej poprawna: powinien istnieć tylko jeden powód, dla którego klasa może się zmienić. 

A w praktyce? Co to jest jedna odpowiedzialność? Czy postępowanie w duchu SRP oznacza, że muszę dzielić kod na setki banalnych klas? Tylko w jakim celu? Istotny jest powód zmiany.

Dawno temu Janek miał w projekcie taki programistyczny, niezdobyty ośmiotysięcznik o nazwie CatalogModule. Robił wszystko. Wyświetlał listę produktów, pojedynczy item, tabelki ze specyfikacjami, ceny. Przykład idealnie złego kodu, niepokryty choćby minimalną warstwą testów. Zdecydowanie powinien być podzielony i zrefaktoryzowany.

Ostateczny kierunek cięcia był jednak inny, niż zespołowi Janka się pierwotnie wydawało. Wystarczyło wydzielić i opisać interfejsem niskopoziomowe operacje na bazie danych. CatalogModule pozostał nadal jedną klasą z kilkunastoma metodami. Różnica polegała na tym, że mieścił się już w granicach pojedynczej warstwy architektonicznej. Czy spełnił założenia SRP? Tak. Będzie dokładnie jeden powód do zmiany. Chęć wprowadzenia innego sposobu prezentacji produktów.

Podział wyglądałby inaczej, gdybyś projektował klasę, przeznaczoną do ponownego wykorzystania. O tym w kolejnym pryncypium.

Open-Closed Principle (OCP)

Otwarte na rozbudowę, a zamknięte na zmianę. Takie twoje klasy być powinny. 

A co to znaczy, że klasa jest otwarta na rozbudowę? Przede wszystkim to, że po klasie daje się dziedziczyć. Czyli od strony technicznej, nie jest opatrzona paskudnym słowem kluczowym final. Nie powinna też być nieprzewidywalnym singletonem ze statyczną metodą getInstance. 

Klasa koniecznie musi być zgodna z zasadą pojedynczej odpowiedzialności. Dzięki temu nie prowokuje tego, aby dotykać jej wewnętrzny kod.

Klasa może też zachęcać do rozbudowy. Ale już nie przez dziedziczenie, tylko wskutek wewnętrznej elastyczności.Zrealizujesz to przyjmując w parametrach funkcji interfejsy i abstrakcje. I cyk, dzięki polimorfizmowi funkcjonalnie rozszerzyłeś klasę, nawet po niej nie dziedzicząc. 👷

Jak jeszcze zamknąć kod na bezpośrednią zmianę? Chroniąc swoją klasę i kodując defensywnie. Nie rozdawaj zbyt hojnie modyfikatorów public, preferuj raczej private.

Pamiętaj, aby zamknąć się też na nieprzewidziane zmiany w trakcie działania. Buduj stan klasy z parametrów konstruktura, zamiast wystawiać settery. Np. taka metoda setLogger w klasie Application – to proszenie się o problemy. 

Liskov Substitution Principle (LSP) 

Polimorfizm. Jeśli pies szczeka, to owczarek niemiecki, który jest psem, również szczeka. Tylko inaczej. A jeśli nie szczeka, to niech rzuci wyjątkiem, bo się popsuł. 🐕

Analogicznie sprawa wygląda w przypadku obiektów. Jeśli klasa deklaruje, że rozszerza inną, to tym samym zobowiązuje się spełnić odziedziczony kontrakt.

Akademicki przykład czy Kwadrat jest Prostokątem? Matematyk powie, że tak. Programista odpowie – to zależy. Jeśli zaimplementuje Prostokąt jako obiekt z metodami ustawDługośćustawSzerokość, to nie może rozszerzyć takiej klasy nazywając ją KwadratKwadrat co prawda będzie miał metodę ustawDługość, ale co z odziedziczonym długiem – metodą ustawSzerokość?

Pozostawienie metod naruszy zasady matematyki. Nadpisanie i wzajemne ich wywoływanie naruszy zasadę LSP – ustawSzerokość, zgodnie z nazwą, nie powinno dotykać parametru długość.

W kolejnym artykule zobaczysz dwa nieoczywiste, ale powszechnie pojawiające się naruszenia zasady podstawiania.

Interface Segregation Principle (ISP) 

Kiedy interfejs jest dobry? Gdy mówi prawdę, zgodną ze stanem faktycznym. Nie dopuszczaj, aby nazywał się IMessageSender, a przy okazji zapisywał coś do pliku. To by było kłamstwo. 

Dobry interfejs musi być przydatny. Nie może się nazywać IDoAnything, to czyni go bezużytecznym. Powinien być maksymalnie specjalizowany.

W kuchni, podczas rozmowy w nowym developerem, Janek do wytłumaczenia idei interfejsów użył poniższej analogii.

Opisując nawyki żywieniowe człowieka możesz machnąć interfejsik IOmnivore (ang. wszystkożerny). To by była prawda. Ale tylko dla części obiektów. Elastyczniej będzie posiadać dwa interfejsy IMeatEater 🥩 i IVegeLover 🍎🥬. Przecież nie zmusisz wszystkich instancji klasy człowiek do jedzenia mięsa.

Dzieląc zachowania na deskryptywne interfejsy, uwalniasz klasy dziedziczące od implementowania zbędnych metod, które wewnątrz będą miały komentarz /* do nothing */ albo instrukcję pass. Tym samym zwiększasz prawdopodobieństwo ponownego użycia, czyli po prostu przydatność twojego kodu.

Dependency Inversion Principle (DIP) 

Czyli zasada mówiąca, że kierunek zależności w kodzie powinien być odwrotny do kierunku przepływu sterowania w aplikacji. ↩️

Przepływy z prądem i pod prąd

W kodzie każdej aplikacji zauważysz dwa przepływy.

  • Przepływ sterowania czyli: Aplikacja → Metoda kontrolera → Fabryka  → Usługa
  • Przepływ zależności, to z kolei relacje: Interfejs usługi ← Konkretna implementacja usługi oraz Fabryka abstrakcyjna ← Konkretna implementacja fabryki

Przypomnę standardowe pytania Janka: dlaczego? W jakim celu zależności w kodzie muszą mieć przeciwny zwrot niż kierunek przepływu sterowania?

Twój wymarzony dom

Planujesz budowę domu 🏠. W myślach rysujesz rozkład pokojów. Wyobrażasz sobie fotel obok kominka i stół przy oknie. Na tym etapie abstrakcji nie interesuje cię, jaki to będzie fotel. Ma być po prostu wygodny. Stół – ma po prostu być stołem.

Spróbuj zamodelować w kodzie zależności opisujące dom. WykańczanieDomu to twoja aplikacja, umeblujSalon to metoda kontrolera. Jakaś fabryka wyprodukowała fotel, zdolny z twoją specyfikacją. Fotel pozwala na nim wygodnie usiąść. Tak! To przepływ sterowania.

Czy dostrzegasz przepływ zależności w kodzie? Fabryka IDEA zgodnie z abstrakcją opisującą fabrykę produkuje fotel. Model fotela Grubbo dostarcza usługę siedzenia, zgodną z interfejsem wygodneSiedzenieNaFotelu.

Co zyskujesz? Nie jesteś uzależniony od konkretnego producenta i modelu (usługi). W dowolnym momencie, nie naruszając konceptu twojego domu (przepływu sterowania), możesz wymienić elementy składowe (zależności w kodzie). Tym samym osiągasz projekt zgodny z zasadą od ogółu do szczegółu.

Obrazkowe podsumowanie zasady DIP

Na poniższym schemacie klas zobaczysz wszystkie elementy zasady DIP:

R.C.Martin Czysta architektura. Zasada DIP
R.C.Martin Czysta architektura. Zasada DIP
  • granicę, która oddziela abstrakcje od konkretnych implementacji (bladoczerwona linia)
  • kierunek przepływu sterowania od aplikacji do konkretnych implementacji (strzałki wychodzące od komponentu Application)
  • niezależność aplikacji od konkretnej implementacji (fabryki)
  • zależności konkretnych implementacji od interfejsów (zamknięte niewypełnione strzałki)

Co zrobić z tak potężną wiedzą?

Notatka podsumowująca

  • Single Responsibility Principle (SRP) – bądź minimalistą, niech napisana przez Ciebie klasa ma dokładnie jeden powód do zmiany. Klasy Misc-Utils-Tools- są złem 👿.
  • Open-Closed Principle (OCP) – programuj defensywnie, używaj prywatnych modyfikatorów dostępu, zrezygnuj z publicznych getterów i setterów. Nie używaj słowa kluczowego final. Pozwól na rozbudowę przyjmując w parametrach metod publicznych abstrakcyjne klasy albo interfejsy.
  • Liskov Substitution Principle (LSP) – daj sobie i innym, możliwość dziedziczenia klas zgodnie z kontraktem definiowanym w klasie bazowej.
  • Interface Segregation Principle (ISP) – znowu minimalizm. Grupuj i specjalizuj kod tak, aby implementacja interfejsu czy dziedziczenie po klasie abstrakcyjnej, nie implikowało pisania metod void, które wewnątrz będą miały komentarz /* do nothing */.
  • Dependency Inversion Principle (DIP) – nie wiąż się z konkretnymi implementacjami, tylko z abstrakcjami i zachowaniami. To droga do architektonicznego jednorożca – idealnie stabilnego, ale łatwo modyfikowalnego komponentu wysokiego poziomu 
O autorze

O autorze

Michał Cisz

Programistycznie od kilkunastu lat związany z ekosystemem PHP/JS/PostgreSQL. Fan czystej do bólu architektury, metodyk agile i programowania ekstremalnego, dbający o przestrzeganie zasad SOLID.

Czysta architektura okiem ekipy migawka.it

Jakie problemy napotkaliśmy na początku? W czym nam pomogła? Kiedy na pewno z niej nie skorzystamy?

Nie przeszkadzaj mi! Jak nie dać się wyrwać z flow

Ile razy ostatnio zostałeś brutalnie oderwany od swojej pracy? Zaszywasz się w biurze i nagle pojawiają się oni – nieoczekiwani goście i ich pytania, które nie niosą ze sobą grozy awarii albo powagi hotfixa. Ale są to pytania, które teraz, dokładnie w tym momencie muszą paść i oderwać cię od bieżącej pracy. Jak sobie z nimi poradzić?

Precyzyjne twoje wiadomości być muszą

Dostajesz wiadomość e-mail i zastanawiasz się, czy to na pewno do ciebie i co właściwie masz zrobić. Wysyłasz pilną informację na komunikatorze i czekasz tydzień na odpowiedź. Dlaczego? Przecież wszystko było jasne. Najwyraźniej jednak nie było. Chwila moment i dowiesz się wszystkiego.

Testowanie wielu pól

Ponoć kiedy kod jest już napisany nie da się stwierdzić, czy był pisany w modelu TDD. Jest jednak kilka śladów zbrodni, które pozostają. Na przykład testy sprawdzające zbyt wiele rzeczy na raz i zakładające, że czytelnik posiada wiedzę tajemną.

Te bezsensowne interakcje przerywające pracę

Ile razy byłeś poirytowanie faktem, że musisz w pięciu mailach wyjaśniać o co chodzi? Ile razy miałeś wrażenie, że banalne tematy ciągną się w nieskończoność, a ty czułeś się jak niezastąpiona jednostka centralna, bez której nic w zespole się nie zadzieje? Oczywiście wszystko kosztem twojej efektywności i nieustannego przerywania własnej pracy. Posłuchaj pewnej historii i naucz się prostej techniki, która wyeliminuje zbędne interakcje.

Praca zdalna – komunikacja na czacie

Jak przenieść codzienne rozmowy z biura do świata online? Co z bezcennymi dyskusjami w kuchni? Najczęściej wrzucamy całą firmę na jakiś komunikator i liczymy, że zadzieje się magia. Nic bardziej mylnego i dziś opowiem ci jak sobie z tym poradzić.

Funkcja trackback/Funkcja pingback

  1. Zasady SOLID w czystej architekturze - migawka.it - […] Klik! […]
  2. Premium SOLID dla praktykujących czystą architekturę - migawka.it - […] jeszcze tego nie zrobiłeś, to zajrzyj na chwilę do artykułu  “Community Edition SOLID w służbie czystej architektury”. Dowiesz się dlaczego w…
  3. Enterprise SOLID dla architekta czystych kodów – grupowanie - migawka.it - […] Community Edition SOLID w służbie czystej architektury […]
  4. Enterprise SOLID dla architekta czystych kodów – łączenie - migawka.it - […] Poznałeś zasady solid i wiesz, dlaczego są tak ważne. […]

Prześlij komentarz

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

Pozostań w kontakcie