Tekst: Jakub Bigora

* akronim w poniższej grafice stworzyłem na potrzeby tekstu i nie ma on żadnego związku z nazwą popularnej sieci handlowej

Dobry programista wie, że oprócz kompilatora kod czytają ludzie. Kod, który piszecie, to też list do przyszłości, a ona znajdzie czas, by Was ocenić.

Programowanie jest podobne do budowy domu. Im dalej zagłębiamy się w oba procesy, tym częściej zauważamy, że pewne rzeczy moglibyśmy zrobić inaczej, ale fundament czyli to, co zrobiliśmy dotychczas, teraz ogranicza nasze możliwości. Nie da się przecież niczego zmienić bez poważnej przebudowy, poświęcenia dodatkowego czasu, a więc podniesienia kosztów. Podobnie jest z poprawkami kodu. Poświęcenie procesowi projektowania dużej ilości czasu nigdy nie gwarantuje jakości. Realnie bowiem każda aplikacja otrzymuje kilka dodanych „szybciej niż inne”, słabszych jakościowo poprawek i zmian. To, że wymagania klienta zostały szybko spełnione, zadowala kierownictwo, ale wykonawcy, znajdując ślady przeszłości, muszą w przyszłości tłumaczyć, dlaczego pewne, działające już rozwiązania zrefaktoryzowali lub, co gorsza, zaprogramowali od podstaw. Przed taką sytuacją chroni nas code review i kilka akronimów będących stałymi bywalcami TOP10 pytań dla seniorów. Zaczynajmy!

Zacznijmy od SOLID czyli 5 zasad solidnego fundamentu Wujka Boba bo taki pseudonim miał Robert Cecil Martin – programista formułujący ten akronim. Postaram się rozwinąć go podając najprostsze, najbardziej zrozumiałe wytłumaczenia.

S: Single Responsibility Principle (SRP) – Zasada pojedynczej odpowiedzialności

Każda, zaprojektowana przez nas klasa powinna brać odpowiedzialność np. tylko za jedną składową procesu obróbki informacji czyli mówiąc najprościej ‘robić jedną rzecz’. Jeżeli w naszym kodzie zajmujemy się analizowaniem, sortowaniem i drukowaniem dokumentów to w klasie drukującej – nie liczymy i nie sortujemy. Jeżeli występuje błąd w module drukowania naszego systemu, poprawiając go powinniśmy poruszać się tylko w obrębie klasy związanej z drukowaniem.

Przykład naruszenia:

Poprawiony kod:

W ten sposób jeśli występuje błąd w module drukowania, poprawiamy tylko klasę DocumentPrinter, bez ingerencji w inne klasy.

O: Open/Closed Principle (OCP) – Zasada otwartości na rozszerzenia

Kod powinien być otwarty na rozszerzenia, ale zamknięty na modyfikacje. Oznacza to, że rozszerzać funkcjonalność powinniśmy poprzez dziedziczenie lub kompozycję, zamiast modyfikować istniejący kod.

Przykład naruszenia:

Poprawiony kod:

Dzięki takiemu podejściu dodanie nowej metody płatności nie wymaga modyfikacji istniejącego kodu.

L: Liskov Substitution Principle (LSP) – Zasada podstawienia Liskov

Litera L pochodzi od nazwiska Pani Barabry, która sformułowała zasadę podstawienia dobrze znaną każdemu programiście posługującego się listą. Deklarując listę List możemy podstawić pod nią ArrayList ponieważ wskaźnik do obiektu podstawowego może także pokazywać na obiekt pochodny. Pamięć o tej zasadzie pozwala nam np. na dostarczanie szerszej funkcjonalności przy użyciu tego samego gdyż metoda przyjmująca typ Collection może przyjmować dowolną kolekcję (listę, set, kolejkę) i wykonywać na niej działania.

Przykład:

Jeśli metoda akceptuje obiekt klasy bazowej, powinna działać również dla obiektów klas pochodnych.

Penguin nie może być traktowany jako typ Bird, bo narusza kontrakt metody fly. Lepszym rozwiązaniem byłoby wydzielenie interfejsu:

I: Interface Segregation Principle (ISP) – Zasada segregacji interfejsów

Interfejsy powinny być małe i konkretne. Duże interfejsy są trudne w utrzymaniu i zmuszają klasy do implementacji metod, których nie potrzebują.

Przykład:

Poprawiony kod:

D: Dependency Inversion Principle (DIP) – Zasada odwrócenia zależności

Moduły wysokopoziomowe nie powinny być zależne od modułów niskopoziomowych. Oba powinny zależeć od abstrakcji.

Przykład naruszenia:

Poprawiony kod:

Kolejnymi, równie ważnymi akronimami dotyczącymi dobrego kodu są:

DRY (Don’t Repeat Yourself) – powtarzanie podobnych fragmentów kodu to często podstawowy błąd, który w późniejszym rozwoju generuje koszty – refaktoryzujemy jeden fragment, pozostawiając drugi, bliźniaczy, w innym miejscu programu. Zamiast kopiować należy pamiętać by wydzielać wspólną logikę do funkcji lub metody używając dziedziczenia, interfejsów lub kompozycji. Warto także pamiętać, że wzorce, takie jak Template Method, Strategy czy Decorator, także pomagają wyeliminować powtarzającą się logikę oraz – o czym zapomina większość – pisanie takich samych funkcji testowych także narusza zasadę DRY.

Oprócz braku powtarzalności warto także pamiętać by nie tworzyć kodu, który nie będzie używany – w myśl zasady YAGNI (You Aint Gonna Need It) czyli Nie potrzebujesz tego == ‘nie dodawaj’ nie pozostawiamy ‘zaczątków’ nowych funkcjonalności, a także – by nie stosować optymalizacji i lepiej wyglądających form (jak np. wyrażeń lambda) w każdym możliwym miejscu, niejako ‘na pokaz’ – stosowanie się do zasady „pozostawiania kodu prostego w odbiorze” KISS (Keep it Simple Stupid) oszczędzi naszym następcom czasu i powiększających się zakoli w kolejnych sprintach.

U zupełnych podstaw leży także:

Odpowiednie nazewnictwo oraz pilnowanie długości metod

Klasy i metody powinny mieć odpowiednie nazwy.

Nazwy klas to zwykle rzeczowniki napisane w formacie PascalCase gdzie każde słowo rozpoczyna się wielką literą, bez spacji ani podkreśleń np. CustomerManager.

Nazwy metod to czasowniki w stylu camelCase gdzie pierwsze słowo piszemy małą literą, kolejne wielkimi np. calculateTotal.

W obu przypadkach używamy nazw, które mówią same za siebie i nie używamy skrótów, które mogą być źle zrozumiałe (dotyczy to także nazw argumentów). Nie obawiajmy się długich nazw – lepiej wiedzieć co dana metoda wykonuje już widząc jej deklaracje. Co do długości i wyglądu samej metody – przyjmuje się że nie powinna ona przekraczać 20 wierszy i 2 wcięć – wynikających ze stosowania instrukcji warunkowych. Nie powinna także przyjmować więcej niż trzech argumentów.

Jednolite formatowanie

Mieszanie różnych styli w zespole programistycznym nie jest wskazane. Zespół powinien ustalić jednolity standard autoformatowania na początku swojej pracy z projektem tak by nie dochodziło do commitowania plików bez realnych zmian.

Ogólne zasady dobrego kodu

Gdyby pomijając przedstawione powyżej, już sformuowane zasady dobry kod można by opisać jako kod, który cechuje:

  • Poprawność: kod powinien działać prawidłowo niezależnie od rodzaju danych wejściowych, zarówno oczekiwanych, jak i nieoczekiwanych.
  • Wydajność: kod powinien być efektywny pod względem zużycia pamięci i czasu, uwzględniając zarówno teorię (np. złożoność obliczeniową w notacji „O”), jak i praktyczne aspekty, takie jak czynniki wpływające na rzeczywiste działanie.
  • Prostota: zadanie powinno być realizowane przy użyciu minimalnej liczby wierszy kodu
  • Czytelność: kod musi być zrozumiały dla innych programistów napisany w sposób przejrzysty, unikając nadmiernie złożonych konstrukcji
  • Łatwość konserwacji: kod powinien być prosty do rozbudowy zarówno dla autora, jak i innych osób w całym cyklu życia produktu.

Pisząc kod nie możemy zapominać o testach.

Pisanie testów zgodnie z zasadą FIRST

Zasada FIRST to akronim opisujący cechy dobrze zaprojektowanych testów jednostkowych. Przypomnijmy znaczenie każdej z liter.

Fast (Szybkie): Testy powinny działać błyskawicznie, aby mogły być uruchamiane regularnie. By to uzystkać należy minimalizować zależności od zewnętrznych systemów.

Independent (Niezależne): Każdy test powinien być samodzielny, a więc nie wpływać na inne (mieć niezależne dane wejściowe oraz nie współdzielić zasobów)

Repeatable (Powtarzalne): Wyniki testów muszą być przewidywalne i identyczne w każdych warunkach.

Self-validating (Samoweryfikujące się): Test powinien sam jednoznacznie wskazywać sukces lub porażkę.

Timely (Na czas): Testy należy pisać odpowiednio wcześnie, najlepiej przed implementacją kodu (TDD).

Pamiętając o powyższym możesz znacząco poprawić jakość swojego kodu i całego oprogramowania.

Zakończenie

Przestrzeganie zasad SOLID, DRY, YAGNI i KISS oraz stosowanie ogólnie przyjętych konwencji nazw czy dodawanie łatwo rozwijalnych testów to nie tylko recepta na lepsze oprogramowanie – to inwestycja w przyszłość projektów i zespołów. Te proste, ale potężne reguły sprawiają, że programowanie przestaje być chaotycznym rzemiosłem, a staje się przemyślaną sztuką – kod staje się czytelniejszy, bardziej niezawodny i gotowy na zmiany.

W świecie, gdzie technologie zmieniają się z dnia na dzień, warto budować na fundamentach, które nigdy nie tracą na wartości. Trzymając się tych zasad tworzysz rozwiązania, które wytrzymają próbę czasu, a to ważne, w dzisiejszym, dynamicznym świecie.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.