Czysty kod

czystykodNatchnieniem do napisania dzisiejszego postu była prezentacja szkoleniowa, którą przygotowałam w firmie. Dotyczyła ona czystego kodu, a główne materiały do jej utworzenia pochodziły z książki „Czysty kod. Podręcznik dobrego programisty” Roberta C. Martina (znanego pod pseudonimem Wujek Bob).

Przykłady użyte w książce są napisane w języku Java i niektórzy mogą mieć problem z analizą trudniejszych kawałków kodów. Jednakże wszystkie poruszane problemy są uniwersalne i niezależne od używanego języka – polecam więc zaopatrzyć się w tę pozycję.

Sprawy organizacyjne:

– posługuję się cytatami z książki: są one zawarte w cudzysłowie, więc podkreślam, że to nie są moje mądrości życiowe;p

– wszelkie przykłady kodu (mniej lub bardziej trafione) są mojego autorstwa, więc proszę o wyrozumiałość 🙂

1) ALE W CZYM JEST PROBLEM?

W tym, że zmiana 1 kawałka kodu powoduje nieprzewidziane błędy w zupełnie innych częściach kodu. Żadna zmiana nie jest łatwa, bo każda ingerencja wymaga brodzenia po pachy w złym kodzie.

Wraz ze wzrostem bałaganu w kodzie może spadać efektywność zespołu. Czasami próby ratowania tej sytuacji to zatrudnianie nowych (niekoniecznie doświadczonych) osób. Czemu to niezbyt dobre rozwiązanie? Bo nowe osoby też nie wiedzą, jak się w tym bagnie poruszać i dodają do niego jeszcze więcej.

notcleancode.pngcleancode.png

Źródło: http://ronjeffries.com/xprog/articles/too-much-of-a-good-thing/

2) DLACZEGO TAK SIĘ DZIEJE?

„Oczywiście, każdy z nas stracił mnóstwo czasu z powodu złego kodu. A zatem – dlaczego go tworzymy?”

Bo nie mamy czasu. Bo klient coś chce na wczoraj. Bo szef nas okrzyczy. Bo chcemy to dzisiaj skończyć. Bo później się poprawi.

„Później znaczy nigdy”

3) JAKIE JEST ROZWIĄZANIE?

„Jedyna prawidłowa miara jakości kodu: Co to jest/minutę”

codequality.png

W teorii powinniśmy dążyć do tego, żeby przegląd kodu zajmował jak najmniej czasu. Obojętnie, z jakiego powodu ten przegląd nastąpił: czy to komuś się nudzi, albo coś nie działa, albo trzeba rozszerzyć aplikację, albo nowa osoba dołączyła do projektu.

Rozwiązanie składa się z 2 elementów:

1 – WIEDZA i 2 – PRACA.

Najpierw potrzebne jest zdobycie odpowiedniej wiedzy na temat zasad i różnych wzorców. Następnie tę wiedzę należy przekształcić na umiejętność poprzez pracę i praktykę.

4) KILKA(NAŚCIE) PROPONOWANYCH ROZWIĄZAŃ

A) Znaczące nazwy

Bardzo ważny jest podstawowy problem: odpowiednie nazywanie zmiennych/metod/klas. Przypomnienie dobrych zasad:

– nazwy klas to przeważne rzeczowniki/wyrażenia rzeczownikowe np. Account,

– nazwy metod to czasowniki/wyrażenia czasownikowe np. GetAccount().

Podczas używania stałych warto nazwać je odpowiednio – żeby było wiadomo, jaką informację przechowują. Na przykład zamiast:

temp.PNG

może warto napisać:

daysofweek.PNG

Dodatkowo liczba dni tygodnia raczej nie ulegnie zmianie na przestrzeni kilku stuleci, więc można pokusić się o to, żeby była ona stałą.

const.PNG

Dużym błędem jest również dezinformacja płynąca z nazwy funkcji. Np. nazwa sugeruje, że zmienna jest listą, a okazuje się czymś zupełnie innym:

itemlist.png

Warto unikać metod/klas o podobnych nazwach. Utrudnia to między innymi wyszukanie tej właściwej.

temporaryGet

Czym te 4 metody różnią się od siebie? Niekoniecznie jest to wiadome po przeczytaniu samych nazw metod.

„Nie należy się obawiać pisania długich nazw. Długa nazwa opisowa jest lepsza, niż krótka enigmatyczna.”

Dla przykładu można skrytykować wbudowaną metodę Assert.Equals(), która jest często wykorzystywana w testach jednostkowych. Jej nazwa niewiele mówi na temat przyjmowanych parametrów i ich kolejności. Przykładowo mogłaby się nazywać AssertExpectedEqualsActual(expected, actual), żeby nie zapomnieć kolejności uzupełnianych parametrów.

Warto też ujednolicić styl nazywania: jeśli najpierw tworzymy metodę GetAccount(), to potem nie nazywajmy kolejnej RetrieveUser(). Stworzy to niepotrzebne zamieszanie  przy wyszukiwaniu.

Korzystajmy też z jednego wzorca PascalCase (UpperCamelCase) lub camelCase.


B) Funkcje

Są 2 zasady odnośnie konstruowania funkcji:

               1) Funkcje powinny być małe

               2) Funkcje powinny być mniejsze, niż są

Kilka rad dotyczących funkcji:

  • w teorii funkcja nie powinna mieć więcej niż 20 wierszy
  • liczba wcięć nie powinna być większa niż 2 (za wcięcie uważamy if/else/while)
  • Jedna funkcja powinna być odpowiedzialna za 1 czynność, np.:

countbmi

można zamienić na:

fixCountBmi

  • w klasie kolejność funkcji powinna trzymać się zasady czytania kodu z góry na dół. Wywoływana funkcja powinna być poniżej funkcji wywołującej (jak w przykładzie powyżej).
  • funkcja powinna przyjmować jak najmniej argumentów – max to 3, przy większej liczbie wykorzystujmy obiekty.
  • Warto też unikać pojedynczego argumentu typu boolean. Świadczy on o tym, że funkcja ma 2 różne zadania, zamiast jednego.
  • jeśli chcemy w danej funkcji obsłużyć błędy (użyć bloku try/catch/finally), to poza tym już nic w tej funkcji nie powinno się znajdować.

trycatchfin

A na koniec chyba najgorsza rzecz, czyli kopiowanie całych metod – to jest bardzo złe.


C) Komentarze

„W rzeczywistości komentarze są w najlepszym przypadku koniecznym złem”

Wymagane komentarze opisujące każdą metodę i jej argument zaciemniają kod. Metoda powinna mieć odpowiednią nazwę i mówić sama za siebie.

Czy jest ktoś, kto zawsze aktualizuje komentarze? No właśnie. Po jakimś czasie są już one nieaktualne.

Zakomentowany kod „na potem” – po to istnieją kontrole wersji, żeby takich rzeczy nie robić. Jeśli nie używamy danego kawałka kodu, należy go usunąć. Jeśli będziemy potrzebowali go znowu, to wystarczy skorzystać z gita.


D) Formatowanie

Każdy może mieć zupełnie inny styl formatowania. Obojętnie, czy jest on dobry czy nie, zmieszanie różnych styli nie jest praktyczne. Chociażby dlatego, że przy wysyłaniu zmian na serwer można zobaczyć, że pomimo zmiany logiki w 1 pliku, tak naprawdę wprowadziliśmy zmiany w wielu plikach. Czemu? Bo np. kod został automatycznie sformatowany w kilku innych plikach – a zmieniła się tylko liczba pustych linii między metodami.  Warto więc ustalić zasady w zespole, żeby kod każdego programisty z zespołu miał ten sam styl formatowania bez względu na to, kto go pisze. Przy wykorzystywaniu narzędzi do autoformatowania (np. Resharper, CodeMaid) w łatwy sposób można ustawić te same reguły.


E) Testy jednostkowe

Testy i kod powinny być pisane razem. Wraz ze zmianą kodu produkcyjnego powinny się zmieniać testy. Dobrze napisane testy zmienia się szybko, łatwo i przyjemnie. Testy powinny być też czytelne – wg mnie np. korzystając ze wzorca 3A (AAA). Akronim AAA to: Arrange (aranżacja), Act (akcja), Assert (asercja). Ważne też, żeby starać się zachować zasadę jednej asercji: jeden test sprawdza sprawdza 1 rzecz.

aaa

FIRST – 5 zasad testów:

Szybkie (Fast) – powinny działać szybko (w przeciwnym razie nikt nie będzie chciał ich zbyt często uruchamiać)

Niezależne (Independent) – nie powinny zależeć od siebie (gdy jeden się wysypie, reszta kaskadowo również)

Powtarzalne (Repeatable) – testy powinny być powtarzalne w każdym środowisku – developerskim, na produkcji

Samokontrolujące (Self-Validating) – powinny mieć 1 wynik: sukces lub porażka

O czasie (Timely) – pisane w odpowiednim momencie, czyli PRZED pisaniem kodu produkcyjnego (dzięki temu mamy gwarancję, że nasz kod jest testowalny)


F) Klasy

I tutaj znowu:

  • pierwsza zasada: powinny być małe,
  • druga zasada: powinny być mniejsze niż są.

Trzymajmy się też ważnej zasady pojedynczej odpowiedzialności (SRP) – jest to jedna z najprostszych zasad ale też jedna z najczęściej łamanych.

Czemu? Kończymy pracę po tym, jak działa, a nie zajmujemy się już zapewnieniem organizacji i czystości kodu.

Niektórzy boją się zbyt wielu małych klas, bo będzie to wymagało przechodzenia z klasy do klasy i może być trudniej zrozumieć cały system. A wcale tak nie jest – system z wieloma małymi klasami nie jest bardziej skomplikowany niż system z kilkoma dużymi. Tutaj wybór jest po prostu taki; czy wolimy przechowywać narzędzia w skrzynkach z wieloma małymi szufladkami zawierającymi określone elementy, czy wolimy kilka szuflad, do których po prostu wrzucamy narzędzia?


G) Powstawanie projektu

     4 zasady prostego projektu (by Kent Beck)

         1 – przechodzi wszystkie testy

potrzeba weryfikacji, czy działa poprawnie. Bez weryfikacji nie powinien być używany. Piszemy kod testowalny, klasy są małe i jednozadaniowe (łatwiej się je testuje)

         2 – nie zawiera powtórzeń

powtórzenia mogą być w wielu formach – wiersze kodu, które są identyczne; wiersze kodu, które są bardzo podobne; powtórzenia implementacji (2 różne metody liczą to samo, ale 1 zwraca liczbę elementów zbioru a druga, czy jest pusty)

         3 – wyraża intencje programisty

sugestywne nazwy, małe klasy i funkcje łatwe do zrozumienia, wzorce projektowe

         4 – minimalizuje liczbę klas i metod

nie należy przesadzać. Jest to najmniej ważna z tych 4 zasad, ale należy o niej pamiętać. Np. nie wszędzie jest potrzebny interfejs.

„Dlatego należy być dumnym ze swojej pracy. Warto spędzić nieco czasu nad każdą funkcją i klasą – wybrać lepsze nazwy, podzielić dużą funkcję na mniejsze i ogólnie przywiązywać większą wagę do tego, co tworzymy. Uwaga jest cennym zasobem.”

H) Udane oczyszczanie kodu

 

„Nie wystarczy napisać działający kod. Działający kod jest często fatalnie napisany. Programiści, którym wystarcza jedynie działający kod, działają nieprofesjonalnie.”

Oczywiście nieudany kod można czyścić. Jest to jednak kosztowne, czasochłonne i żmudne. A zachowanie czystości kodu jest względnie łatwe. Ważne, żeby posprzątać od razu, gdy tylko nabałaganimy.

PODSUMOWANIE

„Zły kod to NASZA wina”.

I chociaż definicji czystego kodu jest multum, wystarczy ustalić jakieś ogólne zasady (w firmie/projekcie) i starać się ich trzymać. Tworzenie kodu zgodnego z takimi zasadami zapewne nie będzie przychodzić od razu z łatwością. Może trzeba będzie poświęcić na to trochę cennego czasu. Ale dzięki temu zaoszczędzi się o wiele więcej czasu i wysiłku na zarządzanie danym projektem w przyszłości. Niekoniecznie najważniejsze jest to, że teraz jest napisane „byle jak”, ale działa. Kiedyś to „byle jak” w końcu sprawi problem i może ktoś inny będzie musiał sobie z tym radzić – a zapewne nie będzie to szybkie, łatwe i przyjemne.


Podoba Ci się to, co tworzę? Chcesz dostawać informacje o:
– wydarzeniach, które organizuję lub wspieram (np. konferencje, meetupy, webinary)
– inicjatywach, które organizuję lub wspieram (np. GeekWeekWro, DevAdventCalendar)
– moich prelekcjach, kursach i szkoleniach
– wyróżnionych artykułach z mojego bloga

0% SPAMu, 100% informacji! Krótko i na temat.

3 uwagi do wpisu “Czysty kod

  1. tazos333

    Ależ się zgadzam. Prawdopodobnie dlatego, że duża część mojego „światopoglądu” programistycznego wywodzi się od Wujka Boba.
    Dodałbym jeszcze, że warto posprzątać również przed nabałaganieniem.

    Polubienie

  2. Sprzątanie przed bałaganem jest formą przedwczesnej optymalizacji. Najpierw powinniśmy wykonać nasze zadanie, a dopiero potem sprzątać. Sprzątając przed, często okazuje się, że i tak musimy zmienić dopiero co uprzątnięty kod, bo czegoś nie przewidzieliśmy.
    Tak więc najpierw trzeba sprawić, aby kod działał, a dopiero potem sprzątać (optymalizować).

    Polubione przez 1 osoba

Dodaj komentarz