Czysty kod: przyczyny i skutki

7 maja
Viktor Svirskiy, Senior Python Developer
Czysty kod: przyczyny i skutki
Każdy programista ma swoją definicję czystego kodu. Podczas rozmów z kandydatami często słyszę, że jakościowy kod, to taki, który dobrze się czyta. Zgadzam się, ale to tylko czubek góry lodowej.

Pierwszym sygnałem, który zwraca uwagę na to, że kod przestał być czysty jest wydłużony czas rozwoju nowych funkcjonalności i zwiększony zakres regresji dla najmniejszych zmian w systemie. To rezultat gromadzenia się długu technologicznego, zbyt bliskiego powiązania komponentów systemu i braku testowania automatycznego. Prawdopodobne przyczyny takiej sytuacji mogą być:

  • zewnętrzne - takie jak zwiększona presja ze strony biznesu w kwestii szybkiego wprowadzania nowych funkcjonalności,
  • wewnętrzne - słabo zdefiniowane procesy i niewielka interakcja wewnątrz zespołu.

Nie odkładaj pracy mającej na celu zabezpieczenie właściwej organizacji kodu i jakości architektury. W czasie sprintu ważne jest, by oddzielać zadania o bezpośredniej wartości dla biznesu (związanej z nową funkcjonalnością) i zadaniami technicznymi, które wpływają na biznes pośrednio. Sposób na odseparowanie od siebie typów zadań zależy od obecnego stanu projektu, ograniczeń czasowych i obciążenia zespołu.

Czysty kod, czyli jaki?

Okazuje się, że sama czytelność kodu jest niewystarczająca, by stwierdzić, że jest on czysty i że system jest właściwie zaprojektowany. Kod powinien posiadać również inne cechy:

  • być modyfikowalny - właściwie zaprojektowany kod i architektura powinny sprawiać, że rozszerzanie systemu nie będzie czasochłonne, a poniesiony koszt technologiczny będzie niewielki. Części systemu nie powinny być ze sobą zbyć mocno powiązane, a kod powinien być abstrakcyjny i samowystarczalny. Każda część systemu, nad którą pracujesz powinna być odpowiedzialna jedynie za swoją część funkcjonalności;
  • być stabilny, przewidywalny, bezpieczny i niezawodny. Niezależnie od tego, jak czytelny jest kod, powinien być on pokryty testami. Dobry kod i testy powinny być nierozłączne, a jakość testów jest równie ważna, co ich ilość. Taki kod nie powinien powodować żadnych problemów w czasie wykonywania i debugowania oraz nie powodować żadnych zmian w środowisku;
  • być bezpieczny - pisząc kod, nie zapominaj o ogólnym bezpieczeństwie produktu. Rekomenduję pogłębienie wiedzy na temat zasad bezpieczeństwa i trzymanie się ich. Dla projektów webowych rekomenduję OWASP.

Dobry kod właściwie nie istnieje. Nie znaczy to, że cały kod powinien zawierać się w jednej linii i że delikatne metody to powód do dumy. Znaczy to natomiast, że kod nie powinien być duplikowany i niektóre wspólne cechy powinny być zachowane na poziomie abstrakcji. Teoretycznie, upraszczanie kodu powinno prowadzić do zmniejszenia liczby defektów.

Czytelność kodu również jest istotna. Każdy programista ma swój własny styl, a czytelność zależy od doświadczenia - w końcu wszyscy dążymy do tego, by kod był prosty, stylowy i możliwie zwięzły.

Code smell (pl. zapach kodu) to termin, który został użyty po raz pierwszy w 1961 r., by uniknąć subiektywnej ewaluacji jakości kodu. Jest to zbiór reguł i rekomendacji, które pokazują, czy przyszedł czas na refaktoryzację. “Brzydkie zapachy” prowadzą do awarii i powinno się je eliminować. Według tego podejścia, refaktoryzacja zawsze jest konsekwencją “brzydkiego zapachu”, a eliminując jego przyczynę, eliminujesz konsekwencje. Więcej szczegółów na temat sygnałów zapowiadających potrzebę refaktoryzacji można znaleźć w książce Martina Fowlera “Refactoring: Improving the Design of Existing Code”.

Czy warto tworzyć czysty kod?

Tak! Ale nie zawsze i nie wszędzie. Nie możesz zapominać o celowości i długowieczności twojego kodu. Jeśli, na przykład, przydzielono ci zadanie rozwinięcia PoC i twierdzisz, że wybrany stos technologiczny jest w stanie wypełnić ten cel, twój kod przeterminuje się po tygodniu lub dwóch. Nie warto wkładać wysiłku w poprawianie tej funkcjonalności.

Istnieje pogląd, że nie trzeba monitorować jakości kodu lub części systemu, która będzie wymieniona w niedalekiej przyszłości. Nie jest to prawdą z kilku powodów. Wysoka jakość wykonania sprawi, że przeniesienie lub integracja z nowymi częściami systemu będzie łatwiejsza i szybsza. Na pewno ułatwi to zespołowi życie w momencie, kiedy wiele wersji kodu będzie wymagać jednoczesnego wsparcia. Czysty kod zmniejszy również kilkukrotnie liczbę błędów regresji. Nie zapominaj też o złotej zasadzie: nic nie jest robione bardziej na stałe od rozwiązań tymczasowych. Zadania związane z poprawą pewnych części kodu prawdopodobnie będą spychane na koniec listy przez kilka dobrych miesięcy.

Co poprawi jakość twojego kodu?

Większość programistów koduje szybko i najbardziej elegancko jak potrafi, więc wszystko działa niezawodnie od samego początku. Ale nie każdemu udaje się pisać nie tylko czytelny, ale również zrozumiały kod. Jak więc tworzyć czysty kod? Są na to tylko 2 sposoby: samoorganizacja i praca zespołowa.

img

Samoorganizacja

Rozważmy kilka sposobów na poprawę jakości kodu. Te rekomendacje przydadzą się programistom na każdym poziomie rozwoju kariery.

1. Dostosuj swoje narzędzia

Zwracaj uwagę na to, jakich używasz narzędzi, a przede wszystkim na środowisko. Połowa sukcesu tkwi we właściwych narzędziach. IDE (Integrated Development Environment) nie tylko uzupełnia kod na bieżąco, ale również identyfikuje code smells, które wymagają refaktoringu. Rozszerzając IDE o odpowiednie wtyczki wyposażysz się w prawdziwy szwajcarski scyzoryk! Większość rzeczy, którymi się zajmujesz jest robiona przy użyciu tego narzędzia, staraj się więc, by było tak elastyczne jak to możliwe.

Warto inwestować w licencjonowany produkt. Używanie pirackich wersji oprogramowania to strata czasu i zmarnowany wysiłek.  Poza tym możliwość skorzystania z oficjalnego wsparcia zawsze może się przydać.

2. Bierz udział w projektach open source

Pracując w zespole, trzeba potrafić radzić sobie z czyimś kodem. Czasami nie jest łatwo zrozumieć, odczytać i stosować się do czyjegoś stylu. Praca z obcym kodem to okazja, by nauczyć się nowych podejść do rozwiązywania popularnych problemów. Czytaj i badaj kod!

3. Narzuć sobie sztywne limity

Zacznij rozwijać mały projekt, który ma rozwiązać konkretny problem. Zaprojektuj i zaimplementuj architekturę własnoręcznie. W takim przypadku możesz samodzielnie narzucić sobie techniczne limity - na przykład: użycie tylko OOP, wzięcie pod uwagę wszystkich rekomendacji dotyczących rozwoju oprogramowania w wybranym języku, rozsądne użycie szablonów, etc. Wyrób sobie nawyk pracy w ramach ograniczeń - nie zawsze będziesz mieć pod ręką kogoś, kto będzie pilnował jakość twojego kodu.

4. Popracuj nad abstrakcyjnym myśleniem

Czytaj i stosuj wzorce! Nie są przypisane konkretnemu językowi i pomagają efektywnie rozwiązywać problemy. Zrozumiesz w ten sposób, jak działają zewnętrzne narzędzia i biblioteki. Rozwiązuj również zagadki programistyczne. To przyjemny sposób na rozwinięcie umiejętności i poznanie niuansów wybranego języka.

5. Analizuj

Zanim zaczniesz poszukiwać gotowego rozwiązania, zadaj pytanie sobie i doświadczonym osobom z twojego zespołu. Zawsze lepiej jest rozumieć ciąg przyczynowo skutkowy - głęboka znajomość problemu pozwala przenieść jego rozwiązanie na inne zadania w przyszłości. Dobry programista to nie rzemieślnik zajmujący się wyłącznie pisaniem kodu, to inżynier, który bada, planuje i projektuje.

6. Czytaj dokumentację

Dokładne zapoznanie się z dokumentacją jest równie ważne, co czytanie kodu! Kolejnym krokiem jest nauka pisania dokumentacji.

7. Ćwicz, ćwicz, ćwicz

Każde doświadczenie jest lepsze niż żadne. Pamiętaj o tym.

Praca w zespole

Większość zadań jest rozwiązywana w ramach zespołu, ważne jest zatem, by odpowiedzialność za jakość była wspólna dla wszystkich członków teamu. Im większy zespół, tym trudniej utrzymać produkt w dobrej kondycji. Rozważmy kilka podejść do rozwoju przyzwoitego kodu:

1. Code review

To metoda, którą łatwo zrozumieć i stosować: przynajmniej dwie osoby, w tym autor kodu, sprawdzają go jednocześnie.

Podczas code review warto pamiętać o kilku sprawach:

  • sprawdź, czy kod podlega standardom; ten proces można i powinno się zautomatyzować z użyciem analizatorów statycznych w Continuous Integration (CI);
  • pamiętaj, by sprawdzić kod pod kątem późniejszego utrzymania i naprawiania błędów - procesów, których nie da się zautomatyzować;
  • kod powinien być także sprawdzony pod kątem kompletności (upewnij się, że dany fragment obejmuje daną funkcję w całości).

2. Continuous integration

Continuous Integration pozwala na uzyskanie jasnego feedbacku na temat obecnego stanu kodu w krótkim czasie. Zadziała poprawnie, jeśli zastosujesz się do 2 prostych zasad:

  • produkt powinien być składany szybko; opóźnienia nie mają sensu, ponieważ ciągła integracja wpływa na jakość kodu dzięki możliwości szybkiego uzyskania feedbacku; jeśli testy nie przejdą, od razu otrzymasz powiadomienie;
  • analizatory statyczne powinny być dodane do skryptu w celu sprawdzenia kodu pod kątem standardów, jakości i bezpieczeństwa.

3. Coding conventions

Warto mieć listę standardów programistycznych, ale zanim ją stworzycie ją w zespole, upewnijcie się, że wszyscy członkowie teamu zdają sobie sprawę z ich znaczenia. Nie oczekuj, że lista powstanie od razu - zapewne czeka was wiele dyskusji.

Lista standardów systematyzujących nazwy zmiennych, nazewnictwo na ogólnym poziomie i wiele innych kwestii nie zostanie stworzona od razu i nie będzie skończona. Przyjmijcie zasady, które odpowiadają wszystkim i przygotujcie się na rozszerzanie listy (i usuwanie punktów, które okażą się zbędne). Lista jest po to, by jej się trzymać i najłatwiej osiągnąć to z użyciem analizatorów statycznych i Continuous Integration - nie wymaga to czynności manualnych.

Jakościowy kod może przyspieszyć proces rozwoju oprogramowania w dłuższej perspektywie. Taki kod może być używany ponownie, pomaga również uniknąć tracenia czasu na naprawianie starych bugów, a z drugiej strony, nowym osobom łatwiej jest dołączyć do zespołu w trakcie trwania projektu.

4. Testy

Im mniej błędów w kodzie, tym lepsza jego jakość. Testowanie filtruje krytyczne błędy i pomaga upewnić się, że kod działa tak jak powinien. Jasna strategia związana z testowaniem jest istotna w momencie, kiedy masz udowodnić, że kod jest jakościowy - testowanie powinno być, co najmniej, modułowe. Jeszcze lepiej, jeśli użyjesz innych metod, np. testów integracyjnych lub testowania regresji.

Znakomita większość testów w projekcie to testy jednostkowe - są one tanie i szybkie. Istnieje wiele narzędzi, które pomogą ci stworzyć testy modułowe i raporty pokrycia kodu. Uruchomienie pakietu testowego i stworzenie raportu pokrycia kodu może być zrobione automatycznie poprzez ciągłą integrację. Niedostateczne pokrycie kodu może nawet być powodem wydłużenia określonego etapu w projekcie.

5. Analiza błędów

Błędy w kodzie są prawdopodobnie nieuniknione, właśnie dlatego analiza jest tak ważnym narzędziem w radzeniu sobie z nimi. Jeśli chcesz poprawić swoje umiejętności, musisz wyciągać wnioski z własnych błędów.

Kiedy zdarza się błąd, przeanalizuj go, odpowiadając na kilka pytań:

  • Czy to błąd o niskim czy wysokim priorytecie?

Jeśli to błąd o wysokim priorytecie, powinien niezwłocznie zostać naprawiony. Jeśli jest drobny, możesz naprawić go w kolejnych iteracjach.

  • Co poszło nie tak?
  • Dlaczego tego nie sprawdziliśmy (we właściwy sposób)?
  • Co jeszcze może się stać w konsekwencji tego błędu?
  • W jaki sposób unikać podobnych błędów w przyszłości?

Oczywiście istnieje wiele narzędzi, które pomagają identyfikować błędy w kodzie. Znajdź takie, które w pełni odpowiada na twoje potrzeby.

6. Zbieranie wskaźników

Istnieją różne wskaźniki, które pomogą ci skwantyfikować jakość kodu. SonarQube radzi sobie z tym zadaniem doskonale - pozwala na zebranie wszystkich kluczowych wskaźników:

  • Potencjalne błędy

Ilość defektów i to, na ile są poważne to kluczowe wskaźniki ogólnej jakości. Znajdowanie błędów powinno być zautomatyzowane, ale tylko częściowo. Kod powinien być monitorowany pod kątem głębszych błędów w logice. Każda część wiedzy powinna być w systemie reprezentowana autorytatywnie, według zasady DRY (“Don’t Repeat Yourself”).

  • Pomiar złożoności

Złożoność jest często mierzona jako złożoność cyklomatyczna. Wskazuje ona liczbę linearnie niezależnych ścieżek w kodzie aplikacji. Istnieje korelacja pomiędzy złożonością cyklomatyczną a częstotliwością występowania defektów. Teoretycznie, uproszczenie kodu powinno prowadzić do zmniejszenia liczby defektów.

  • Niezbędne komentarze

Zaledwie kilka właściwie oddzielonych linii zawierających komentarze na temat modułu, klasy lub metody sprawi, że kod będzie zdecydowanie czystszy.

  • Próg pokrycia testami

Używany w testowaniu, pokazuje procent kodu źródłowego wykonanego podczas procesu testowania. Ustal próg procentowy dla twoich testów i konsekwentnie się go trzymaj.

Błędy w kodzie są jak ślad węglowy. Nie unikniesz ich całkowicie, ale odrobina “spalin” nie zabiją ludzkości i natury wokół. Redukowanie szkodliwego wpływu na planetę jest jednak dziś koniecznością. W ten sam sposób działa pisanie czystego kodu - konieczność każdego programisty. Nie ważne, jaką ścieżkę wybierzesz, zawsze trzeba dbać o jakość i styl rozwiązań.

Warto jednak starać się, by czystość kodu nie stała się obsesją. Zawsze myśl o ludziach: użytkownikach, którzy będą zawiedzeni w przypadku awarii małej części systemu i specjalistach, którzy będą ten system utrzymywać.