Skip to content

Witaj w Świecie Jutra!

  • Technologie jutra
  • Sprzęt jutra
  • Aplikacje jutra
  • Programowanie
  • Księga Drogi
  • Renowacja
  • Różności
  • Archiwum
  • Autor
  • Home
  • Wszystko
  • SOLIDne programowanie
  • Programowanie
  • Wszystko

SOLIDne programowanie

Jakub Raczkowski 14 stycznia 2024

W czasie gdy zajmowałem się swoim pierwszym projektem równolegle pochłaniałem kolejne strony książki Czysty Kod od Roberta Martina. Zawiera ona całą masę praktycznych wskazówek jak pisać oprogramowanie – częścią z nich się dzisiaj z wami podzielę, za przykład biorąc kawałki mojego, dopiero co ukończonego programu.

Ogólnie rzecz biorąc esencja płynąca z lektury Czystego Kodu wygląda następująco:

  • Jeśli twój program działa wolno – nie szkodzi, zoptymalizuje się.
  • Jeśli twój program zawiera bugi – nie szkodzi, przetestuje się go i znajdzie.
  • Jeśli kod twojego programu jest nieczytelny – wyrzuć to gówno i napisz od początku!

To co z początku wydaje się wyjątkowo nieintuicyjne, jest prawdziwe:

Ważniejsze od tego aby twoja aplikacja działała, jest to aby inni programiści mogli zrozumieć jak działa.

Z dokładnie takim podejściem siadałem do mojego pierwszego samodzielnego projektu. Nie chodziło mi, aby gra wyglądała cudownie, w głębokim poważaniu miałem też zastosowanie nowoczesnych frameworków. Najważniejsze dla mnie było to, aby kod był schludny, czytelny i w dobrym stylu. Tutaj warto wspomnieć o czymś takim jak „zasada najmniejszego zaskoczenia” – czyli po prostu aby wszystko było tam gdzie się tego spodziewamy, że będzie. W uczynieniu kodu właśnie takim pomogły mi popularne zasady programowania: KISS, YAGNI, DRY i SOLID.

KISS

Keep It Simple Stupid

Moja ulubiona zasada na początek. KISS mówi nam aby nie „przeinżynierzyć” (ang. overengineering) rozwiązania danego problemu. Czasami programiści chcą zamanifestować swoje umiejętności i gwiazdorzą tworząc długaśne metody, korzystając z mało znanych bibliotek. A przecież chodzi o jak najprostsze i jak najszybsze rozwiązanie zadania.

Na stworzenie gry Clean Them All (CTA) wystarczyło mi dwa tygodnie pracy. To w sumie nie tak dużo, ale poszedłem po linii najmniejszego oporu – skorzystałem ze starej ale wciąż użytecznej biblioteki Swing do warstwy wizualnej. Mogłem też zrobić pełnoprawny front z użyciem JavaScriptu, HTML i pewnie jeszcze Reacta, ale zanim bym się tego nauczył minąłby co najmniej miesiąc. A najgorsze, że byłaby to wiedza chwilowo bezużyteczna, bo szukając pracy w backendzie nikt by mnie nie pytał o front.

YAGNI

You Ain’t Gonna Need It

„Nie będziesz tego potrzebować”. Kolejna fajna reguła łącząca się z KISS oraz często powtarzanym w Czystym Kodzie stwierdzeniem, że „przedwczesna optymalizacja jest źródłem wszelkiego zła”. W trakcie prac nad CTA chciałem dodać tam znacznie więcej elementów: animacje znikania kwadratów, obrazki zamiast kolorów czy górny panel z dodatkowymi przyciskami. I tak sobie próbowałem różnych opcji, a czas leciał. A ja marnowałem go na rzeczy, których poza tym projektem nie będę już nigdy potrzebował. A książka „Wzorce projektowe” leżąca tuż obok zbierała kurz. A kurs Springa na Udemy już 3 razy był na promocji i znowu wrócił do starej ceny.

Cytując myśl przewodnią filmu Interstellar: „Time is the resource”. Czas jest naszym zasobem i musimy nauczyć się nim mądrze dysponować. Róbmy to co mamy do zrobienia i nic więcej (chociaż naturalnie ciekawymi pomysłami warto się podzielić z drużyną).

DRY

Don’t Repeat Yourself

Świetna zasada mówiąca nam aby w kodzie unikać powtórzeń, z którą mam niestety najwięcej problemów. Nie wiem, może to skaza nauczyciela, która we mnie siedzi? W końcu powtarzanie jest esencją nauki. Chociaż gdzie tylko mogę staram się wyodrębniać elementy wspólne do pojedynczych klas czy metod, to czasem idzie coś przegapić. Na przykład zamiast tworzenia 16 kopii (dla każdego użytego koloru, sic!) takiej metody:

public static List<Square> createYellowSquares(int amount) {
        List<Square> yellowSquares = new ArrayList<>();
        for (int i = 0; i < amount; i++) {
            String name = "yellow" + i;
            Square square = new Square(SquareColor.YELLOW, name);
            yellowSquares.add(square);
        }
        return yellowSquares;
    }

Mogłem dodać drugi parametr z kolorem i załatwić problem pojedynczą funkcją:

public static List<Square> createColoredSquares(int amount, SquareColor color) {
        List<Square> squares = new ArrayList<>();
        for (int i = 0; i < amount; i++) {
            String name = color.getName + i;
            Square square = new Square(color, name);
            squares.add(square);
        }
        return squares;
    }

Z drugiej strony zbytnie odchudzanie klas może przynosić czasem problemy z czytelnością dlatego z całkowitą premedytacją zamiast napisać:

int[][] array = new int[19][25];
int[6][11] = 1; 
int[6][13] = 1; 
int[13][6] = 1; 
int[13][18] = 1; 

wolałem wrzucić tutaj pełną tabelę gdzie dobrze widać co i jak:

I tak 27 razy. 🙂

SOLID

W odróżnieniu od powyższych, SOLID to nie jedna ale aż 5 reguł, po jednej dla każdej litery:

S

Single responsibility

Zasada pojedynczej odpowiedzialności – jedna klasa powinna mieć tylko jedną funkcjonalność. Kierując się tym credo udało mi się stworzyć aplikację na którą składają się 24 klasy (nie licząc klas testujących). Dla przykładu pojedynczy kwadrat ma aż dwie klasy – jedna w modelu (która przechowuje jego nazwę, kolor oraz położenie), a druga w widoku – odpowiedzialna za narysowanie go i przechwycenie klików.

O

Open/Closed

“Otwarty na rozbudowę, zamknięty na modyfikacje”. Z początku brzmi jak sprzeczność, ale nic bardziej mylnego. Klasa raz stworzona powinna być zostawiona w spokoju, bowiem nieopatrzna zmiana z pozoru błahego drobiazgu może zdestabilizować cały program. Nie znaczy to jednak, że nie można do aplikacji dodawać nowych wodotrysków. Możemy, ale należy w tym celu korzystać z dziedziczenia i/albo interfejsów. Ale, by to było możliwe, pierwotny projekt musi zakładać taką rozbudowę i od początku ją umożliwiać poprzez np. stworzenie odpowiednich klas abstrakcyjnych. Jeśli chodzi o CTA to nie kierowałem się zbytnio zasadą O/C, gdyż projekt nie przewiduje rozbudowy (a co najwyżej kompletną przebudowę z użyciem frameworków i porządnego frontu) aczkolwiek użyty przeze mnie wzorzec strategii dobrze wpisuje się w w/w regułę – mogę dodawać nowe klasy z różnymi poziomami trudności bez modyfikacji tych już istniejących.

Spójrzcie tu i poniżej. Jaka jest różnica?
Po co to zrobiłem?

L

Liskov Substitution Principle

IMO najtrudniejsza zasada z wszystkich tu przedstawionych. Polega to na tym, że klasa dziedziczona musi być zamienna we WSZYSTKICH przypadkach z klasami po niej dziedziczącymi. Jeśli mamy klasę Animal.. wróć! Nigdy nie lubiłem tych wszechobecnych przykładów ze zwierzakami, zamiast tego weźmy klasy postaci z gier fantasy RPG.

Wyobraźcie sobie, że macie czarodzieja – niska siła, wysoka inteligencja, płaszczyk, kijaszek, szpiczasty kapelutek – te sprawy. Dopiero zaczyna przygodę, więc posiada on tylko dwa czary – kula ognia i lodowy pocisk (to wasz interfejs pod nazwą Spellbook). Na levelu dziesiątym macie możliwość specjalizacji – idziecie albo w magię ognia albo wody. Dostajecie podklasę DZIEDZICZĄCĄ po klasie Czarodzieja. Waszym bazowym interfejsem jest wciąż Spellbook, czyli nie może być tak, że mag ognia nagle zapomina jak się strzela lodowym pociskiem. Za to jak najbardziej możemy użyć bazowej kuli ognia, ale np. zwiększyć jej obrażenia dwukrotnie.

Zatem, bez względu na to czy jesteście zwykłym czarodziejem, czy też bardziej prestiżowym magiem ognia, wciąż macie te same zaklęcia (choć zmodyfikowane). Teraz, kiedy w karczmie formuje się nowa drużyna i szukają czarodzieja do polowania na żywiołaki ognia (magik musi znać magię lodu!) możecie się tam zgłosić nawet będąc magiem ognia. W końcu wciąż jesteście czarodziejem. Zasada Liskov jest zachowana.

I

Interface Segregation

O ile klasa dziedziczona mówi nam przede wszystkim CZYM JEST klasa po niej dziedzicząca, o tyle implementowany interfejs stawia na relację CO MA. Co ma klasa implementująca Runnable? Ma metodę run() do utworzenia nowego wątku i start() do jego rozpoczęcia. Co ma klasa implementująca Serializable? Nie ma żadnych nowych metod, ale ma przyzwolenie od JVM na spakowanie swojego obiektu do postaci pliku. Jak dobrze przyjrzymy się standardowym interfejsom to zobaczymy, że mają po 0-2 metod i tak właśnie należy to robić w naszych projektach. NIE DAWAJMY KLASOM METOD Z KTÓRYCH NIE KORZYSTAJĄ!

W powyższym przykładzie z czarodziejami nic nie stoi na przeszkodzie aby mag ognia implementował drugi interfejs: FireSpells z nowymi czarami ognia. Nie powinniśmy za to dać ich już zwykłemu czarodziejowi z warunkami aby z nich nie korzystał np. przed uzyskaniem poziomu dziesiątego.

D

Dependency Inversion

Zasada odwrócenia zależności. Nie zliczę ile razy R. Martin w Czystym Kodzie użył sformułowania „poziom abstrakcji”, ale czasem było to nawet po kilka razy w jednym zdaniu. Musi to być coś ważnego – pomyślałem i sprawdziłem. Faktycznie.

Całe „nowożytne” projektowanie obiektowe obiera się na abstrakcji – klasach abstrakcyjnych i (w szczególności) interfejsach. Ma to służyć zmniejszaniu zależności pomiędzy klasami. Klasa, która jest parametrem w jakiejś metodzie uzależnia tę metodę od siebie. Dla przykładu:

public void addSquareToPocket(Square square){

        if (noFreeSlots()) model.gameLost();

        for (Map.Entry<PocketSlots, Square> slot : squaresInPocket.entrySet()) {
            square.setPocketSlot(slot.getKey());
            if (slot.getValue() == null) {
                slot.setValue(square);
                break;
            }
        }
        removeSquareFromGameBoard(square);
    }

Ta metoda przyjmuje tylko jeden prametr: obiekt klasy Square. Taki kwadrat nie jest niczym szczególnym, ma tylko kolor, koordynaty na planszy i metodę onClick(), która mówi co się z nim dzieje po kliknięciu. Równie dobrze mógłby być kółkiem, trójkątem czy gwiazdką. Ale wtedy musiałbym dołożyć kolejną metodę dla każdego z tych kształtów. Głupota.

A wystarczy, że wyposażę te kwadraty, kółka, kwiatki czy cokolwiek wymyślę, w interfejs Shape z metodą onClick() i w/w zmiennymi, a następnie wszędzie tam gdzie w parametrze widać ’Square’ zamienię na ’Shape’. Sortujemy po interfejsie, a nie po klasie.

Ja tego nie zrobiłem, bo projekt jest krótki i dobrze wiem, że żadnych gwiazdek tam nie będzie. Ale jeśli siedzimy nad wieloletnim zadaniem nigdy nie mamy pewności gdzie nas poprowadzi i lepiej nie uzależniać metod od pojedynczych klas.


TDD

Wpis już jest zatrważająco długi, ale zanim go zamknę jeszcze kilka słów o Test Driven Development.

Napisałeś program, działa tak jak chcesz. Przetestowałeś go manualnie – podczas użytkowania i wszystko się zgadza. Po co ci jeszcze jakieś testy?

Jeśli projekt już jest skończony to testowanie jednostkowe może być tylko taką sztuką dla sztuki, co najwyżej aby nabrać trochę wprawy. Tak było w przypadku Clean Them All. Nie miałem tutaj zbytnio czego testować, bo aplikacja jest dosyć zamknięta, a jedyną formą interakcji są kliki i to tylko na obiekty Square. Wielokrotnie przeszedłem grę samemu aby wykryć wszelkie nieprawidłowości.

Mimo to douczyłem się w międzyczasie co nieco o testowaniu, a CTA stało się moim poligonem doświadczalnym. Dobrze przetestowana klasa nie boi się refaktoryzacji i ulepszeń. Być może n-ty raz przeglądając swój kod będziemy chcieli przerobić go nieco, np. aby lepiej zgrywał się z regułami SOLID. Choćby zamienić ten obiekt Square na interfejs Shape. Niby wszystko wtedy powinno działać jak wcześniej, ale czy mamy pewność? Porządnie zrobione testy nam tę pewność zagwarantują.


Podsumowując i wyciągając sedno z moich dzisiejszych wywodów, posłużę się jeszcze raz Czystym Kodem, a dokładniej ilustracją ze wstępniaka:

Tags: java programowanie

Continue Reading

Previous: Pierwszy samodzielny projekt
Next: Wzorce projektowe – Strategy

Related Stories

Mageege Moon104 – test niskoprofilowego mechanika
  • Sprzęt

Mageege Moon104 – test niskoprofilowego mechanika

11 marca 2025
Przebranżowienie cz.4
  • Programowanie

Przebranżowienie cz.4

27 lutego 2025
Smartfon Jutra
  • Sprzęt

Smartfon Jutra

15 lutego 2025

Ze świata

  • Antyweb
  • Kwantowo
  • Dwóch po dwóch
Netflix odpala nową funkcję, która da użytkownikom więcej władzy
Tanie iPhone'y i Samsungi w nowym sklepie Orange
Fani Androida mają powód do radości. Wraca wsparcie uwielbianej funkcji
Wielkie nieporozumienie wokół nowego iPhone’a. Zobaczcie to, zanim zaczniecie krytykować
Chiny przejmują legendarny oddział Sony. Koniec ery
Tak chcemy wyjaśnić napięcie Hubble'a. Lepszej metody dotąd nie było
Netflix odkrywa karty o premierach. Wiemy, co pokaże na dniach
A co powiesz na ładowanie... laserem? Badacze już to testują
ChatGPT zabierze nasze pieniądze? Nowy pomysł jeszcze gorszy niż reklamy
To najmniejsza myszka na świecie. Użyjesz jej wszędzie
Ta aplikacja to „centrum dowodzenia” każdego miłośnika podróży
Kaufland zaszalał. Rozchwytywany sprzęt Parkside trafił na półki
O tych nowościach w Windows 11 jest dziwnie cicho. A szkoda
Microsoft szykuje rewolucję. Skorzystasz, jeśli obejrzysz reklamy
Te zestawy LEGO za moment znikną z rynku. Wiecie, co to oznacza?
To już ostatni dzwonek! Skorzystaj za darmo, zanim Apple to wyłączy
Masz uczulenie na sierść psa? Z tym nic ci nie grozi
Prawdziwa bomba od T-Mobile - klienci Heyah będą zachwyceni!
Garmin, Samsung i Apple na celowniku. Te zegarki mogą zniknąć z rynku
Łatwiej zmienisz przeglądarkę na iPhonie. Wystarczy jedna opcja
To by było na tyle, jeśli chodzi o możliwość ugody
Ocalić od zapomnienia
Ostatni kwant
ALH 84001 – meteoryt, o którym mówiono nawet w Białym Domu
HESS zarejestrował kosmiczny elektron o niespotykanej energii [Phys. Rev. Lett.]
Matka ciemnej materii – recenzja biografii “Vera Rubin. Życie”
Satelita, który zerwał się ze smyczy
Wiadomość od Carla Sagana do przyszłych eksploratorów Marsa
Ile najdłużej może trwać zaćmienie Słońca?
Nowa największa liczba pierwsza ma ponad 41 milionów cyfr [GIMPS]
30 lat konsoli PlayStation – Odcinek #130
Omawiamy serię The Walking Dead (gość: Stary Gracz)
Nikt nie potrzebuje cienkich smartfonów – Odcinek #129
To ostatni dzwonek na kolekcjonowanie gier i filmów
Najlepsza relacja z PGA 2025 (Poznań Game Arena)
Bumblebee wśród klawiatur. Marvo Meqa 80W – recenzja
Pierwsze spotkanie z Omoda 7 Super Hybrid
Logitech MX Master 4, Wednesday, 1670 sezon 2 – Odcinek #128
Tani pad, który chciał być jak DualSense. Test Monka Contra GT-96
Secret Service i prasa komputerowa w Polsce – Odcinek #127

To może cię zainteresować:

Mageege Moon104 – test niskoprofilowego mechanika
  • Sprzęt

Mageege Moon104 – test niskoprofilowego mechanika

11 marca 2025
Przebranżowienie cz.4
  • Programowanie

Przebranżowienie cz.4

27 lutego 2025
Smartfon Jutra
  • Sprzęt

Smartfon Jutra

15 lutego 2025
Czym jest Swagger?
  • Programowanie

Czym jest Swagger?

22 lipca 2024
  • Technologie jutra
  • Sprzęt jutra
  • Aplikacje jutra
  • Programowanie
  • Księga Drogi
  • Renowacja
  • Różności
  • Archiwum
  • Autor
Copyright © All rights reserved. | DarkNews by AF themes.