Czytelny kod

Pisanie czytelnego kodu sprawia wielu programistom ogromne kłopoty. Ta uwaga bynajmniej nie dotyczy wyłącznie osób początkujących, ale niestety także tych z latami doświadczenia. Z czego wynika problem? Jak sprawić, aby pisany kod był bardziej czytelny i przystępny?

Ostatnio mam okazję prowadzić webinary, warsztaty i szkolenia. Dodatkowo od dłuższego czasu staram się kontynuować inicjatywę Weekly JavaScript Challenge. Dzięki temu mam kontakt z wieloma programistami o różnym poziomie doświadczenia i z nieudawanym przerażeniem stwierdzam, że czytelność kodu nadal traktowana jest jako coś mało istotnego. Nic bardziej błędnego!

Programiści przez zdecydowaną większość czasu czytają, a nie piszą kod źródłowy. Dodanie krótkiego fragmentu do istniejącego projektu często wymaga przeczytania kilku lub kilkunastu linii kodu. To jeden z powodów, dla których czytelność powinna stać na pierwszym miejscu. Nieczytelny kod znacznie wydłuża czas potrzebny do napisania czegokolwiek nowego. Na domiar złego, niejasny i zagmatwany kod prowadzi do powstawania nieprzewidzianych błędów w aplikacjach, wynikających z niezrozumienia przepływu danych albo niespodziewanych efektów ubocznych.

Dlaczego więc czytelność traktowana jest drugorzędowo? Zapytałem kilku znajomych programistów1.

Wydaje mi się, że…

Wydaje mi się, że napisanie tego fragmentu w taki, a nie inny sposób, sprawia, że aplikacja działa znacznie szybciej.
– Małgorzata, Junior Software Developer w korporacji; 1 rok komercyjnego doświadczenia

Cieszę się, Gosiu, że poruszyłaś ten wątek. Mój dziadek często powtarzał, że „na oko to chłop w szpitalu umarł”. Niby głupie powiedzenie, ale jednak często okazuje się bardzo prawdziwe.

Gdy ktoś mówi o wydajności i używa zwrotu „wydaje mi się”, to każdemu powinna się zapalić ostrzegawcza lampka. Jakie jest przyspieszenie? Ile milisekund? Ile procent? Jaki to ma wpływ na postrzeganą prędkość aplikacji i na UX? Są to niezwykle ważne pytania, a bez odpowiedzi na nie cała „optymalizacja” nie ma żadnego sensu.

Co oznacza zwrot „wydaje mi się”? Oznacza on „jestem zbyt leniwy i arogancki, by sprawdzić”.

Brałem udział w dyskusji, gdzie jedna z osób użyła takiego argumentu:

Jestem pewien, że to działa szybciej. Operacje na gołym DOM muszą być szybsze niż framework, to chyba oczywiste?

To chyba najgorszy rodzaj arogancji: opinia przedstawiona jako oczywisty fakt. Jest to całkowicie puste stwierdzenie. Panie kolego, prosimy o rzeczowe argumenty, a nie krzykliwe hasła. Warto też zwrócić uwagę, że nawet jeśli ta wypowiedź wydaje się być sensowna, to jednak nie jest prawdziwa – istnieją biblioteki, które dzięki mądrym rozwiązaniom, potrafią renderować HTML szybciej niż gdybyśmy ręcznie operowali na gołym DOM.

Jeśli optymalizujesz kod i nie wykonujesz pomiarów by potwierdzić wzrost wydajności, jedyne czego możesz być pewien to fakt, iż sprawiłeś, że Twój kod stał się mniej czytelny.
– Martin Fowler, “Yet Another Optimization Article”

Gosi możemy wybaczyć z racji małego doświadczenia. Rada od bardziej doświadczonego kolegi: Od teraz, gdy ktoś Ci powie, że jedna wersja kodu jest szybsza od drugiej, zawsze zapytaj: „O ile?”. To zachowanie godne profesjonalisty.

Ale mnie się to jawi jako kolory…

Gdy piszę swój kod, to od razu dostrzegam miejsca, które w oczywisty sposób mogą zostać zoptymalizowane. Nauczyli mnie tego na studiach: gdy programowałem mikroprocesory z 8KB RAM w języku C to często robiłem wstawki asemblerowe, żeby było szybciej. Czytelność jest ważna, ale na pewno nie najważniejsza.
– Tomek, Full-Stack Ninja JavaScript Rockstar Crusader we własnej jednoosobowej firmie; 2 lata komercyjnego doświadczenia

Tomek ma rację, o ile cofniemy się 15 lat w czasie i będziemy programować Amigę. W innym wypadku – nie (chyba, że Tomasz ma magiczną kryształową kulę). Niestety takie podejście bardzo często pokutuje wśród osób przyzwyczajonych do programowania w językach niskopoziomowych. Najczęstsze grzechy to obawa przed tworzeniem wielu małych funkcji i ręczne cache’owanie zmiennych. Często też enigmatyczne nazwy – wszak w Fortranie obowiązywał limit liczby znaków na zmienną!

Sama wiedza o tym, że każda operacja JMP w asmie to dużo zachodu dla procesora nie szkodzi. Cóż, wiedza chyba nigdy nie przeszkadza! Ale próba stosowania zwyczajów i porad wyniesionych z dawnych lat, albo z języków niskopoziomowych w językach nowoczesnych nie tylko nikomu nie pomaga, ale wręcz szkodzi.

Dzisiejsze kompilatory to bardzo zaawansowane narzędzia. Niemal dzieła sztuki, wynik lat prac i badań matematyków i informatyków. Czy naprawdę wydaje Ci się, że jesteś w stanie zoptymalizować kod bardziej niż kompilator? Nie jesteś2. Możesz być o tym przekonany. Jest to w szczególności istotne w językach kompilowanych Just In Time (JIT), gdzie kompilator czyni cuda (dosłownie!) i przykładowo inteligentnie bardziej optymalizuje fragmenty kodu, które są częściej uruchamiane.

Jednym z takich języków jest JavaScript. Nie sposób jest dzisiaj przewidzieć, czy pierwsza wersja kodu będzie bardziej optymalna od drugiej. Nie dość, że mały fragment może zostać skompilowany zupełnie inaczej niż ten sam kod w kontekście całej aplikacji, to jeszcze dochodzi do tego fakt, że sposób pracy kompilatorów może zmieniać się z każdą nową wersją przeglądarek. Z tego samego powodu wyniki typu jsperf rzadko są merytorycznym argumentem w dyskusji.

Optymalizację kodu w większości przypadków lepiej zostawić maszynom.

Optymalizacja to nie proces

Gosi wybaczyliśmy optymalizowanie kodu „na oko”. Ale czy możemy wybaczyć Tomkowi, który twierdzi, że jest gwiazdą rocka programowanaia?

Tomku. Optymalizacja to nie proces. Niemożliwe jest napisanie całkowicie optymalnego kodu od zera z przynajmniej dwóch powodów. Po pierwsze, jak już wspomniałem, nie wiesz jak dokładnie zadziała kompilator w kontekście dużej aplikacji, więc optymalizowanie małych fragmentów nie ma sensu. Po drugie, nie wiesz jeszcze, w jaki sposób dokładnie Twoja aplikacja będzie wykorzystywana przez użytkowników.

Jeśli tego nie wiesz, to skąd wiesz co tak właściwie optymalizujesz? A może właśnie pogarszasz czas działania aplikacji? Prosty przykład. Dla wielu osób oczywisty. Który z poniższych kodów zadziała szybciej?

// 1
for (let i = 0; i < arr.length; ++i) {  
    …
}
// 2
const l = arr.length;  
for (let i = 0; i < l; ++i) {  
    …
}

Jeśli powiedziałeś, że wersja 1, to odpowiedziałeś błędnie.
Jeśli powiedziałeś, że wersja 2, to również odpowiedziałeś błędnie.
Nie ma jednoznacznej prawidłowej odpowiedzi. Jedyne co powinieneś powiedzieć to: „Zależy.”

Nawet tak prosty, teoretycznie oczywisty przypadek, pozostawia pole do interpretacji dla kompilatora. Zazwyczaj oba te fragmenty kodu zostaną skompilowane dokładnie do tego samego kodu wykonywalnego. Zazwyczaj. Jednak znany jest przypadek, gdy wersja 2 działała wolniej niż wersja 1.

Tomku, nie dość, że pogorszyłeś czytelność kodu, to jeszcze zmarnowałeś przy tym sporo czasu na myślenie o różnych mikrooptymalizacjach. Ani współpracownicy, ani przełożeni nie byliby zadowoleni, gdybyś oczywiście ich miał. Shame on you.

Jak żyć?

Stwórz aplikację. Pisz czytelny kod. Poszukaj problemów z wydajnością. Zmierz i zidentyfikuj co dokładnie jest przyczyną. Będziesz zaskoczony rezultatami. Wtedy optymalizuj to, co ma sens.

Jakiś czas temu miałem zoptymalizować pewną aplikację internetową. Jej zadaniem było rysowanie różnych wykresów w przeglądarce, a całość była oparta o framework AngularJS. W pierwszej chwili pomyślałem: „No oczywiście, wydajność rysowania na pewno jest do poprawki, a w drugiej kolejności to pewnie framework sprawia problemy”. Nie zabrałem się jednak do optymalizowania na oślep, lecz odpaliłem narzędzia developerskie. Rezultat pomiarów? Ani framework, ani nawet rysowanie SVG nie były żadnym problemem. Problemem była zła architektura aplikacji i przepływ danych oraz zdarzeń, przez które wykres często był przerysowywany kilkukrotnie bez żadnych zmian. Kto by pomyślał! Poprawki zajęły sporo czasu, ale efekty były bardzo dobre: poprawa czasów renderowania o 75%.

Nieczytelnie to znaczy szybciej przecież…

Tworzę swój startup i tutaj wszystko zmienia się ciągle jak w kalejdoskopie. Nie mamy czasu na przejmowanie się czytelnością kodu, bo za dwa miesiące połowa aplikacji i tak zostanie zaorana.
– Maciej, CEO, CTO, CFO i COO jednego z polskich startupów; 5 lat komercyjnego doświadczenia

Macieju, życzę powodzenia w budowaniu biznesu. Jednak założenia, na których oparłeś swoją wypowiedź są niestety błędne i nie wróżę sukcesów w prędkim rozwijaniu aplikacji.

Stwierdzenie, że pisanie nieczytelnego kodu jest szybsze to absurd. Całkowita sprzeczność. Wszak jedną z głównych zalet pisania dobrego kodu jest szybkość rozwoju aplikacji!

Zastanówmy się czy w ogóle pisanie kodu byle jak jest szybsze. Jest? Nazywając zmienną x zamiast playerPosition zyskujemy ok. 1 sekundy. Jeśli pomnożymy to razy 10000 linii kodu to otrzymujemy prawie 3 godziny. Co za wynik, będziemy pierwsi na rynku, hurra!

Co na tym tracimy? Przede wszystkim godziny, a może nawet dni na wdrażaniu nowych programistów do zespołu. Dni, tygodnie na implementowaniu nowych funkcji w gąszczu niezrozumiałego kodu. Setki błędów na produkcji, które nie powinny się wydarzyć. Czy to ważne aspekty w szybko rozwijającym się startupie, który prędko zatrudnia nowe osoby i intensywnie pracuje nad nowymi funkcjami? Pozostawiam to do oceny Maćka.

Nawet jeśli za dwa miesiące połowa aplikacji ma pójść do kosza, to zdecydowanie łatwiej będzie to zrobić, jeśli kod będzie zrozumiały, granice pomiędzy komponentami dobrze ustalone, a architektura jasna i klarowna. Nie chcielibyśmy przecież przypadkiem usunąć modułu C, od którego zależy moduł B, od którego zależy moduł A, który jednak miał zostać… Może i nie znam się na uprawie rolnej, ale jestem pewien, że orka jest łatwiejsza, jeśli na polu nie leżą miny. Nieczytelny kod to właśnie taka mina. Taka tam alegoria.

Dla równowagi…

Nie panikujmy jeszcze, nie jest aż tak źle! To nie jest przecież tak, że wszyscy programiści w Polsce mają w poważaniu jakość i czytelność pisanego kodu. Dla równowagi nieco inna wypowiedź:

Czytelność kodu i dobra architektura zawsze idą w parze. Ja i cały mój zespół stawiamy je na pierwszym miejscu. Czytelny kod nie jest ani mniej wydajny, ani nie zabiera więcej czasu, gdy się go tworzy – wystarczy tylko nieco zmienić nastawienie i nabrać kilku dobrych zwyczajów. Często optymalizuję tworzone aplikacje, szczególnie na urządzenia mobilne, jednak rzadko kiedy pogarszam przez to czytelność kodu. Najczęstsze problemy związane są z renderowaniem albo komunikacją z API, a na to żadne mikrooptymalizacje i tak nie pomogą.
– Łukasz, Software Developer; 5 lat komercyjnego doświadczenia

Wnioski

Z tegu długiego wpisu zapamiętajmy dwa wnioski:

  1. Optymalizacje to nie proces.
  2. Czytelność kodu stawiaj na pierwszy miejscu.

Zrozumienie i stosowanie tych dwóch zasad ma ogromną wartość dodaną dla każdego profesjonalisty. Dzięki nim masz szansę pisać kod lepszy i bardziej zrozumiały, a do tego rozszerzalny. Łatwiej też będzie Ci wdrożyć nowe osoby do zespołu, a gdy już nabierzesz pewnych nawyków, zaoszczędzisz masę czasu na czytaniu kodu własnego i innych. Czy są pytania?

W kolejnych wpisach z tej serii przedstawię konkretne techniki pisania czytelnego kodu i refaktoryzacji.

  1. Imiona zostały zmienione.

  2. W 99,9% przypadków. Optymalizować warto, gdy udało się zlokalizować przyczynę, ale nigdy na oślep.

Michał Miszczyszyn

Programista z doświadczeniem w JavaScripcie po stronie klienta i serwera. Wielki fan TypeScripta.

Subscribe to Type of Web

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!