Tworzenie kodu według najlepszych praktyk
2 godz. 45 min · Full-stack i Programowanie
Rafał PiekaraSoftware KomandosNie zrozumiesz SOLIDa bez teoretycznych podstaw. Przejdziemy przez założenia stojące za każdą z nich. Przeanalizujemy problemy, które możesz spotkać oraz dowiesz się dlaczego ciągle jeszcze tak wielu programistów nie stosuje się do tych zasad.
Ruby i Typescript to technologie, które świetnie odzwierciedlają uniwersalizm SOLIDa. Te złote zasady działają w językach dynamicznie typowanych i interpretowanych jak Ruby oraz przy statycznym typowaniu na przykładzie Typescript.
Zanim zabierzesz się za programowanie warto poćwiczyć SOLIDny sposób myślenia. Zmierzysz się z zadaniami, w ramach których będziesz projektować kod tak, aby wszystkie zasady SOLID znalazły w nim swoje zastosowanie.
Prawda jest taka, że kod, z którym w większości pracujemy ma swoją historię. Czasami historia ta jest tak bolesna, że klasy i metody są zagmatwane, poplątane i trudne w utrzymaniu. Gdy dotrzesz do tego miejsca będziesz mieć w arsenale wszystkie potrzebne narzędzia, żeby zmierzyć się z zadaniami praktycznymi, które przekształcą kod legacy w SOLIDne arcydzieło.
Tak ważny temat, jak testowanie nie może zostać pominięty. Między innymi po to też stosujemy zasady SOLID, żeby nasz kod praktycznie sam się testował. Napiszesz więc testy, których wzorce możesz wykorzystać w dowolnym projekcie, dowolnej technologii, jeśli tylko kod, który testujesz będzie spełniał wymagania zasad SOLID.
Nie ma znaczenia, czy dopiero zaczynasz przygodę z programowaniem, czy też jesteś starym wyjadaczem, który klasami i interfejsami myśli przy zagryzaniu tosta na śniadanie. Ten kurs jest właśnie dla Ciebie. Technologia ani język programowania też nie mają znaczenia. Zasady SOLID to uniwersalny zbiór. Ich znajomość sprawi, że Twój kod będzie lepszy, łatwiejszy w utrzymaniu i odporny na perturbacje i zmiany.
Przejdźmy teraz do praktycznego przykładu zasady segregacji interfejsów.
Zobaczmy w kod.
Mamy tutaj klasę Reports Controller, więc dalej jesteśmy w kontekście
aplikacji webowej.
Ta klasa ma jedną metodę publiczną jedną akcję Generate i ona
generuje jakiś raport.
Korzysta sobie z serwisu, który nazywa się Report Service
i ten Report Service ma w sobie już tę metodę do generowania raportu.
Jak wygląda klasa Report Service Report Service ma jedną metodę
statyczną Generate Report.
I w tej metodzie generuje się jakiś raport.
Ona zwraca po prostu wygenerowany raport.
Co jeśli będziemy potrzebować czegoś więcej?
Oprócz wygenerowania raportu?
Będziemy potrzebować jeszcze zapisać ten raport gdzieś na dysku,
żeby go można było pobrać np.
Albo żeby on po prostu był w jakimś archiwum.
No to często można spotkać taki niechlujny kod, w którym będziemy mieć metodę, która
będzie wykonywała te dwie czynności, bo chcemy spełniać zasady itd.
Więc mamy generowanie raportu i zapisywanie.
I teraz co tutaj dostajemy?
Dostajemy jakiegoś pdf a na podstawie tego PDFa generujemy ten raport
i go zapisujemy na dysku gdzieś lokalnie.
I mamy taką brzydką metodę, brzydką klasę, która gdzieś tam
tworzy sobie wygenerowany raport.
Jeśli chcemy, żeby ten raport był w formacie
PDF, to nam wygeneruje go w formacie PDF, potem go zapisze gdzieś lokalnie.
Jeśli mamy go zapisanego i mamy ten PDF, to wtedy nam zwróci pdf, a jeśli
to ma być PDF to wrzuci wygenerowany raport.
Masa logiki.
Na pierwszy rzut oka widać, że to jest proszenie się o problemy
i będziemy próbowali to teraz rozwiązać.
Rozwiązaniem może być tutaj rozdzielenie interfejsów.
Tutaj zobacz, że mamy zbudowane jeden interfejs, jeden
interfejs dla każdej z tej akcji.
Dla każdej akcji akcji generowanie raportu akcji, zapisywanie akcji podglądu, bo
zapisujemy ten raport na dysku, żeby umożliwić podgląd później np.
w przeglądarce.
I co tu się dzieje?
Mamy metodę Generate i w tej metodzie generate generujemy raport.
Ale w jaki sposób?
Ano przekazujemy mu parametry.
Zaczynamy sterować zachowaniem tej metody Generate and save report za pomocą
parametrów, więc jeśli chcemy wygenerować raport to robimy
mu pdf false, czyli nie rób PDFa i nie zapisuj go, bo sam chcemy tylko
wygenerować bo chcemy coś z tym zrobić później w metodzie save.
Już się domyślasz pewnie co będzie, bo będzie przekazania innych parametrów.
Tutaj musimy wygenerować pdfa, bo go chcemy zapisać, więc
generujemy pdfa i go zapisujemy.
Czyli mamy save thru pdf i mamy metodę preview,
gdzie z kolei potrzebujemy tylko zrobić PDF, ale go nie zapisujemy.
Wrzucamy tego pdfa, więc mamy bardzo złożony interfejs, bo ta
metoda generate and save report ona robi bardzo dużo rzeczy
I jak widzisz mamy metodę, która zapisuje raport i ona musi wiedzieć o tym,
że on musi być zapisany w PDF.
Ja mam metodę, która robi tylko podgląd PDF a, ale ona
wie też, że tam się da zapisać.
Wie za dużo, korzysta ze zbyt dużej ilości funkcji po gwałcona.
Zasada segregacji interfejsów. Jaka jest droga?
Drogą do rozwiązania tego problemu jest uproszczenie
interfejsu, więc możemy sobie tę klasę Report Services podzielić.
Tą dużą metodę podzielić na mniejsze.
Możemy zrobić osobną metodę do generowania raportu, osobną metodę do generowania
raportów w formacie PDF i osobną metodę do zapisywania raportów w formacie PDF.
Więc mamy trzy różne metody.
Dążymy do takiej konfiguracji, w której będziemy mieć specyficzny, mały interfejs.
I co tutaj mamy teraz w klasie Reports Controller?
Otóż metoda Generate korzysta tylko z tej jednej metody Generate Report.
Metoda Save będzie miała tylko Save PDF report i metoda Preview będzie miała tylko
Generate PDF Report i dostarczenie go jako Preview.
Teraz chciałbym się zatrzymać nad jedną rzeczą, mianowicie nad wzorcem
projektowym, który się nazywa Builder, który bardzo ładnie nam pomoże
korzystać z minimalnego interfejsu w zależności od tego, jaki klient
będzie korzystał z obiektu.
Więc nie będziemy do końca musieli definiować jakiegoś master obiektu, który
ma wszystkie te warunki w sobie zawarte.
Definiować nieskończone ilości metod, o której muszą wiedzieć nasi klienci.
Możemy sprawić, że za pomocą wzorca Builder będziemy mieć poszczególne
elementy naszego procesu.
Na tyle elastyczny, że każda klasa, która będzie korzystała z tego jako zależności,
będzie mogła sobie je dowolnie konfigurować, dowolnie układać.
I zobacz jak wygląda nasz reports Builder.
Wykorzystanie w praktyce tego wzorca Builder.
Mamy metodę Generate Report, która tutaj generuje sam raport i we wzorcu Builder
ważne jest to, że on za każdym razem zwraca swoją własną instancję, tak, że na
tej zwróconej instancji zwróconej wartości możemy wywoływać kolejne metody instancji.
Więc mamy tu zwróconą instancja Report Buildera.
Potem mamy Generate PDF Report i tutaj możemy na podstawie tej instancji
Buildera sobie coś wygenerować.
Możemy PDF a i potem możemy go zapisać lokalnie.
Jak wykorzystanie takiego wzorca może ułatwić nam tworzenie kodu?
Ano widzimy to w klasie Reports Controller, gdzie metoda Generate robi
sobie nowy builder, generuje raport i zwraca ten raport do klienta.
Z kolei później metoda Preview buduje sobie buildera, generuje
raport, generuje PDFa i zwraca PDFa.
A metoda Save zrobi wszystkie te rzeczy, czyli wygeneruje raport, wygeneruje PDFa
i zapisze go na dysku.
Więc mamy minimalny interfejs.
Każda z tych akcji z granulek tRNA, a razem połączone mogą dopiero budować
wielki proces biznesowy.
Wiadomo, możemy iść dalej te metody rozbić na poszczególne klasy takich serwisów jak
Generate Report, PDF, Preview, Report, Save, PDF, Report.
I potem w tych klasach NK postulować sobie logikę, którą wcześniej mieliśmy
zawartą w metodach Buildera.
To jest kolejny sposób, żeby Zenka przechować to sobie
całą logikę, której tutaj używamy w poszczególnych klasach, formie
takich service object czy komentów.
Rozwiązań jest sporo.
Zasada segregacji interfejsów jest dobrą zasadą, która pozwala
tworzyć elastyczny kod, tak jak to pokazałem w przypadku wzorca Builder,
gdzie mogliśmy bardzo elastycznie sobie tutaj sterować tym w jaki sposób
dany raport będzie wygenerowany.
Interfejsy są małe, interfejsy są wygodne, to też jest łatwo testowane.
Nic tylko używać.