W tym artykule postaram się przybliżyć dwie zasady dobrego programowania, a mianowicie SOLID i DRY. W Internecie można znaleźć wiele technicznych opisów i przykładów użycia SOLID oraz DRY. Przedstawię te pojęcia od strony mniej technicznej tak, aby były zrozumiałe dla osób, które dopiero, co zaczynają przygodę z programowaniem, albo zastanawiają się czy uczyć się programować, a w szczególności w języku Java.

Dlaczego tak ważne są zasady dobrego programowania? Czy zintegrowane środowisko programistyczne IDE np. IntelliJ IDEA nie powinno robić tego automatycznie za programistę? Niestety, a może na szczęście tak nie jest, podczas pisania kodu programista musi trzymać się wyznaczonych reguł i standardów, a jednocześnie być kreatywny. Reguły dobrego programowania pozwalają tworzyć dobry, czytelny i łatwy w modyfikacji kod.

Kod napisany w dowolnym języku programowania powinien czytać się jak dobra książka. Osoba czytająca książkę nie chciałby przecież być zanudzana dokładną kopią fragmentów książki umieszczaną w wielu miejscach tej samej książki (DRY – Don’t Repeat Yourself). Ciężko byłoby czytać książkę, która ma pomieszane rozdziały, akcja nie jest spójna i ma się wrażenie, że autor chyba nie wiedział co pisze (SOLID).

Nie chodzi tu o kreatywną formę prezentacji książki, ani nowy typ powieści. Załóżmy, że książka ma pięć rozdziałów i każdy z nich czytany po kolei tworzy ciąg przyczynowo skutkowy. Teraz wyobraźmy sobie sytuację, że wydawca książki pomieszał rozdziały i złożył książkę z rozdziałami w kolejności losowej. Czytanie takiej książki nie będzie niemożliwe, ale na pewno nie będzie należało do przyjemności.

Tak samo jest z kodem źródłowym, programista czytając kod kogoś innego musi najpierw poukładać sobie w głowie chronologicznie kod, który właśnie czyta (sekcje kodu), następnie znaleźć bohaterów książki (zmienne) i zrozumieć w jaki sposób wchodzą w interakcję (metody) ze sobą (zależności). Poprawnie skonstruowana klasa języka Java według Oracle powinna mieć elementy składowe w następującej kolejności:
– stałe,
– zmienne,
– konstruktory,
– metody,
– najpierw stałe, zmienne i metod publiczne, później prywatne.

public class GoodBook {
    public static final int pages = 120;

    private int currentPage = 1;
    private int progress = 0;

    public GoodBook() {
    }

    public void nextPage() {
        currentPage++;
    }

    private void saveProgress() {
        // calculate reading progress...
    }
}
public class BadBook {

    // prywatna metoda
    private void saveProgress() {
        // calculate reading progress...
    }
 
   // publiczna stała
    public static final int pages = 120;

    // konstruktor
    public GoodBook() {
    }

    // prywatne zmienne
    private int currentPage = 1;
    private int progress = 0;

    // publiczna metoda
    public void nextPage() {
        currentPage++;
    }
}

DRY, czyli ang. Don’t Repeat Yourself, pol. Nie powtarzaj się, dotyczy zarówno samego kodu jak i czynności wykonywanych przez programistów. Wiele czynności wykonywanych przez programistę można zautomatyzować, czyli np. napisać niewielki program, skrypt, który wykona automatycznie powtarzalne czynności. Skrypty dla Windows to batch file, a dla Linux shell script. Jeżeli chodzi o sam kod, to podstawowym sposobem na uniknięcie duplikowania kodu jest wydzielanie powtarzających się fragmentów kodu do metod, przez metodę rozumiemy blok kodu, który dla wprowadzanych danych zwraca określony wynik.

SOLID określa podstawowe założenia programowania obiektowego, gdzie każda litera oznacza konkretną zasadę programowania. Tak jak pisałem w Internecie można znaleźć bardzo dużo przykładów zastosowania SOLID, np. w języku Java – Baeldung.com, java2blog.com, howtodoinjava.com. Ja skupię się na wyjaśnieniu tych reguł dla osób zaczynających przygodę z programowaniem w języku Java.
S – Single responsibility principle,
O – Open-closed principle,
L – Liskov substitution principle,
I – Interface segregation principle,
D – Dependency inversion principle.

S jak zasada jednej odpowiedzialności – wyobraźmy sobie sytuację, w której jedna osoba (klasa Java) prowadzi biuro rachunkowe, ale jednocześnie jest jego właścicielem. Czynności które wykonuje (metody w klasie Java), to m.in. obsługa księgowa firm, zatrudnianie pracowników, marketing i pozyskiwanie nowych klientów. Ciężko jest zweryfikować, co i kiedy tak na prawdę robi (testy jednostkowe dla klasy Java, np. JUnit) ta osoba. W sytuacji, gdy właściciel zrezygnuje z prowadzenia swojego biura rachunkowego (zmienia kod metody w klasie Java), zmiany należy wprowadzić we wszystkich firmach (klasach Java), które korzystały z jego usług, czyli znaleźć nowe biuro rachunkowe. Podobnie jest z klasami w kodzie Java, jeżeli jedna klasa robi za dużo, to zmiana jednej jej funkcji wpływa na wszystkie klasy korzystające z tej funkcji.

O jak zasada otwarte-zamknięte – bardzo dobrze obrazują to elementy, które można modyfikować, rozszerzać bez zmiany samego elementu. Dla przykładu firma obsługująca paczkomaty (klasa języka Java) chce obsługiwać nowe większe paczki (metoda w klasie Java), dla których nie ma miejsca w obecnych paczkomatach (algorytm obsługi paczek w klasie Java). Zmiana wszystkich istniejących paczkomatów byłaby bardzo kosztowna, a czasem niemożliwa. Dlatego dobrym rozwiązanie jest takie zaprojektowanie paczkomatu (klasy języka Java), aby można było bez zmiany samego paczkomatu dostawić kolejny element, moduł, który będzie obsługiwał większe paczki (algorytm obsługi paczek w klasie Java pozostanie ten sam). Tak samo jest z klasami języka Java, projektujemy je tak, aby można było je wykorzystywać dla nowo pojawiających się elementów, bez konieczności modyfikacji kodu samej klasy.

L jak zasada podstawienia Liskov – przykładem może być koło rowerowe (klasa języka Java), które jeżeli ma odpowiednie wymiary (atrybuty w klasie języka Java), średnicę, grubość i wysokość bieżnika można zamontować w dowolnym rowerze, do którego koło będzie pasować. Oczywiście koła mogą mieć inne dodatkowe cechy (atrybuty w klasie języka Java) takie jak, np. rodzaj i kolor bieżnika, dodatkowe szprychy, odblaski. Ale nadal będą spełniać swoją funkcję jako koła. Podobnie jest z klasami języka Java dobrze przemyślana klasa może wymieniać jeden element (atrybuty w klasie języka Java) na inny pochodzący (dziedziczący) z tej samej grupy elementów.

I jak zasada segregacji interfejsów – krawiec (interfejs w języku Java) biorąc miarę na marynarkę dla mężczyzny lub na żakiet dla kobiety w inny sposób obsługuje (różne metody w różnych interfejsach w języku Java) każdą osobę. Mężczyzna (klasa implementująca interfejs języka Java) nie musi wiedzieć jakie czynności wykonuje krawiec dla kobiety (klasa implementująca intrfejs języka Java) i odwrotnie, ich interesuje usługa (metody w interfejsie w języku Java) jaką wykonuje krawiec. To samo dotyczy segregacji interfejsów. Każdy interfejs musi być uszyty na miarę konkretnego klienta, czyli klasy, która będzie go obsługiwać.

D jak zasada odwrócenia zależności – zasadę tę dobrze obrazuje roczny przegląd samochodu i sprawy z tym związane. Jadąc na przegląd do serwisu (framework z Inversion of Control, IoC) nie musimy znać wszystkich czynności, które wchodzą w skład przeglądu oraz wiedzieć która osoba (zależności między klasami języka Java), co i kiedy będzie wykonywać. Takie rzeczy wie serwis (IoC), do którego jedziemy na przegląd, to w serwisie wiedzą jakie czynności, w jakiej kolejności należy wykonać i kto ma wykonać (klasy w języku Java). My tylko przyjeżdżamy do serwisu (korzystamy z framework’u z Inversion of Control, IoC) i podajemy numery rejestracyjne, markę, model oraz przebieg samochodu. W kodzie języka Java sprawa wygląd tak samo, korzystamy z framewokr’a np. Spring Framework, który implementuje mechanizm odwrócenia zależności (ang. Inversion of Control) i to on za nas wie kiedy i jak stworzyć obiekty, przekazać innym obiektom oraz kiedy je usunąć.