Hello World. Napisałeś dzisiaj jakiś kod? Jesteś z niego zadowolony? Jak na te dwa pytania odpowiedziałeś Tak, to czas aby zapytać się kogoś innego aby przeczytał Twoją pracę. Najlepiej dać go do poczytania z zaznaczeniem, że ma być to w godzinach wieczornych. Poproś o informację zwrotną od osoby czytającej kod czy był on nużący i ciężki do przebrnięcia. A może wręcz przeciwnie czytało się go przyjemnie i łatwo było zrozumieć co dokładnie ma robić.

 

Co zrobić aby Twój recenzent nie musiał wkładać zapałek do oczu aby zakończyć inspekcję kodu? Jeżeli kiedykolwiek było to Twoim zmartwieniem i nie znalazłeś jeszcze odpowiedzi to Ci podpowiem.

 

Czysty kod

Dwa słowa. Czysty kod. Co definiuje czysty kod? Kod czysty, z którym się przyjemnie pracuje to kod prosty. Proste rzeczy są łatwiej przyswajalne.

 

Wyobraź sobie, że pracujesz z audytorem. Jego rolą jest to aby znaleźć jak najwięcej słabych punktów w Twojej pracy. Musi zidentyfikować wszystkie ryzyka. Ty wiesz, że masz trochę za uszami, ale nie chcesz aby prawda wyszła na jaw. Osoba, z którą rozmawiasz uważa się za eksperta. Jednakże Ty wyczuwasz rysę na szkle. Wyczuwasz, że to jest tylko maska i wiedza Twojego oponenta nie jest tak obszerna jak mu się wydaje. On także to wie, ale nie może zrzucić maski. Podejmujesz grę. Mówisz rzeczowo, ale używasz bardzo dużo żargonu. Nie kłamiesz, ale mówisz tak by nie wiele zrozumiał. Im mniej będzie rozumiał tym mniej będzie zadawał pytań. On wie, że musi uchodzić za eksperta, a nie rozumiejąc o czym Ty mówisz nie może zadawać głupich pytań, które mogłyby go ośmieszyć.

 

Czysty kod jest dokładnym przeciwieństwem. Jest prosty w zrozumieniu. Jest taki, o którym osoba robiąca inspekcje kodu może z łatwością opowiadać. Jest to kod, który bez większego wysiłku umożliwi osobie go czytającej opowiedzieć całą logikę biznesową jaki on opisuje.

 

Reguła pierwsza. Keep It Simple, Stupid. Im mniej złożoności tym lepiej

Niech kod będzie prosty.

 

Załóżmy, że dostajemy następujący ciąg znaków: "10 NAN 15". Każdy element w ciągu jest oddzielony spacją. Celem zadania jest wyciągnięcie liczb całkowitych i zsumowanie ich.

 

Proste zadanie, jednakże można do niego użyć wielu sposobów.

 

Metoda 1. Wyrażenia regularne

private static int RegEx(string text)

        {

            return Regex.Matches(text, @"([1-9]\d*)").Cast<Match>().Sum(i => {

                int.TryParse(i.Value, out int numericValue);

                return numericValue;

            });

        }

 

Metoda 2. String split + LINQ

private static int SplitLinq(string text)

        {

            return text.Split(' ').Sum(i=> {

                int.TryParse(i, out int numericItem);

                return numericItem;

            });

        }

 

Metoda 3. String split + pętla for

private static int SplitFor(string text)

        {

            int result = 0;

            foreach(var item in text.Split(' '))

            {

                int.TryParse(item, out int numericValue);

                result += numericValue;

            }

 

            return result;

        }

 

Wynik każdej z metod będzie taki sam. Liczba 25. Jeżeli chodzi o długość kodu to metoda 3 ma najwięcej linii. Ale to nie ilość kodu świadczy o jego prostocie. Na szczęście minęły czasy, kiedy ktoś oceniał programistów po tym ile linii kodu zostało stworzonych.

 

Metody 1 i 2, taka sama ilość linii kodu. Takie samo podejście jeżeli chodzi o sumowania. Wyrażenie lambda niczym się nie różni. Jednakże metoda 1 ma dodatkowy element skomplikowania. Jest nim wyrażenie regularne. Myślę, że większość programistów sięga po wyrażenia regularne tylko w ostateczności. Jednakże spotkałem się także z ich gorącymi zwolennikami, którzy by do każdego problemu ich użyli. Dodatkowo metoda 1 zawiera jeszcze rzutowanie na Match.

 

Tak naprawdę metoda 1 zawiera błąd w wyrażeniu regularnym. Wystarczy, że parametr wejściowy będzie wyglądał następująco "10 NAN 15 1.5" i wynik zwrócony przez pierwszą metoda nie będzie już 25 a 31. Po prostu wzorzec wyrażenia regularnego jest niekompletny.

 

Tak więc po pierwsze rozwiązania najprostsze są najlepsze. Jak ktoś nie programował w języku, w którym są wykorzystywane lambdy, to metoda 3 będzie najbardziej czytelna i zrozumiała na pierwszy rzut oka.

 

Rozmawiałem kiedyś z kolegą. Mówił, że pracował z bardzo mądrym programistą. Napisał on bardzo ciekawy i zwięzły algorytm. Ów kolega dodał jednak, że innym programistom zrozumienie tego kodu zajęło pół dnia. Są okoliczności, w których warto wysilić się i napisać bardzo optymalny kod. Jednakże w większości aplikacji jest to zwyczajnie niepotrzebne.

 

Spójność

Jakkolwiek piszesz kod. Jakichkolwiek używasz standardów, trzymaj się ich w każdym fragmencie kodu jaki piszesz. Tak naprawdę odnosi się to do całego zespołu. Niech kod wygląda tak jak by pił pisany jednego dnia przez tą samą osobę. Nawet jak produkt rozwijasz od 10 lat.

 

Poniżej krótki przykład braku spójności.

public void DoSomething(string strParam1, int intParam2, string param3)

{…}

 

Uwierz mi taki kod istnieje. Ktoś kiedy zaczął pisać używając notacji węgierskiej. Ktoś inny musiał zmodyfikować metodę, ale już tej notacji węgierskiej nie używał. Ot taki klasyczny potworek. Może zmieniły się standardy kodowania w danej firmie. Nie ma to aż tak wielkiego znaczenia. Jednakże jak już się zmienia konwencje, to albo należy zmienić też istniejący kod, albo też dostosować się do tego co już zostało napisane. Mieszanie stylów sprawia, że po prostu odechciewa się czytać taki kod.

 

Nazwy wszystkiego

Programujesz obiektowo? Twoje klasy i ich atrybuty są reprezentacją rzeczywistości. Powinny przyjmować też rzeczywiste nazwy.

 

Jak piszesz program rysujący kwadraty, to potrzebujesz odpowiedni obiekt.

public class Shape

    {

        public int x { get; set; }

 

    }

 

Powyższy obiekt opisuje tą prostą rzeczywistość. Jest to kształt i ma jeden wymiar. Jednakże będzie to czytelne tylko dla osoby piszącej kod. Bo Shape to obiekt? Może być to równie dobrze koło. A włąściwość x nie będzie już długością boku tylko średnicą. A może ten x to w ogóle nie jest długość boku, tylko ilość jaka ma być narysowana. Tych informacji nie da się niestety wywnioskować czytając ten kod w oderwaniu od całej reszty programu w jakim on jest osadzony.

 

O wiele łatwiej zrozumieć intencje mając następującą klasę

public class Square

    {

        public int SideLengthMilimiters { get; set; }

 

    }

 

Nazwy jakich użyjesz mają bardzo duże znaczenie. Muszą one mieć jednoznacznie definiujące cel. Nie ma znaczenia czy to będzie zmienna, pole klasy, właściwość, metoda czy sama klasa. Cokolwiek to jest niech nazwa nie wprowadza cienia wątpliwości z czym mamy do czynienia.

 

Klasa i właściwość reprezentują rzeczy, tak więc ich nazwy powinny być rzeczownikami. Metody z kolei wykonują jakieś czynności, tak więc naturalnie powinny być czasownikami.

 

Nazywanie rzeczy nie jest łatwe. W końcu nie bez powodu Phil Karlton powiedział kiedyś

 

There are only two hard things in Computer Science: cache invalidation and naming things.

 

Więcej o nadawaniu nazw możesz poczytać w tym wpisie.

 

Nie zostawiaj po sobie śladu obecności

Czytałem niedawno książkę na temat ćwiczenia uważności (Mindfulness: Jak wytrenować dzikiego słonia i inne przygody w praktyce uważności). Jedna z metod ćwiczeń polega na tym by nie zostawiać po sobie śladu obecności. Jak przygotowujesz sobie posiłek w kuchni, to doprowadź ją do takiego stanu w jakim była przed Twoim pojawieniem się tam. A najlepiej dodaj coś jeszcze od siebie i uprzątnij coś jeszcze.

 

W programowaniu ciężko nie pozostawić po sobie śladu. No chyba, że się tylko kod ogląda, a nie pisze go. Metoda ta ma jednakże swoje zastosowanie. Z tym, że jest opisana w inny sposób.

 

Boy scout rule. Leave the campground cleaner than you found it.

 

W praktyce sprowadza się to do tego by kod, który jest modyfikowany wyglądał lepiej niż został zastany. Nie ma sensu rzucać się na refaktoryzację całego systemu. Jednakże poprawienie kodu na poziomie metody lub klasy będzie na pewno wartością dodaną. A wysiłku dużo nie kosztuje.

 

Nie duplikuj kodu. Don't Repeat Yourself (DRY)

Do rzadkości nie należy kopiowanie już istniejącego kodu do swojego programu. Jednym z przejawów tego jest podejście zwane "Copy and Paste driven development".

 

Programista 1, napisał metodę w swojej klasie. Zobaczył ją Programista 2 i pomyślał, że potrzebuje dokładnie takiego samego kodu u siebie. Podobnie postąpił Programista 3, kopiując kod od Programisty 2. No i wyszło, że ten sam kawałek kodu jest w 3 miejscach. A mógł być tylko w jednym aby łatwiej było nim zarządzać.

 

Oprogramowania jednakże lubi żyć swoim życiem. Czasami rodzą się w nim inne życia. Czyli błędy. No i taki znalazł Programista 1 w swoim kodzie. Poprawił błąd i jest szczęśliwy. Nie powiedział jednakże tego Programiście 2, no bo skąd miał wiedzieć, że ten skorzystał z jego kodu. Tym bardziej skąd miał wiedzieć, że Programista 3 także miał ten sam kod u siebie. Po pierwsze kod stał się nie spójny, a po drugi są w nim błędy, które trzeba niezależnie utrzymywać. Same problemy.

 

Jak by ich było mało, Programista 3 jest bardzo kreatywny i dużo kodu dodaje do repozytorium (nie jest to równoznaczne z pisaniem). Stwierdził, że ta metoda, którą skopiował nie działa tak jak powinna. Zmienił ją tak by działała dobrze.

 

Jako, że Programista 3 jest trzecią iteracją programisty, jest więc bardziej obeznany z dobrymi praktykami. Postanowił, że poprawi też metodę w klasie Programisty 2. Tylko, że nic mu nie powiedział, bo ten był prawie na urlopie.

 

Pech chciał, że ten kod nie działał już tak jak on tego oczekiwał.  Przy okazji wyszło, że użytkownicy programu, przyzwyczaili się już do błędu, który został naprawiony przez Programistę 1 a występował on w kodzie Programisty 2. Zrobili sobie nawet obejście problemu. No i kolejny problem. Okazało się, że ten błąd w kodzie Programisty 2, przekształcił się w inny klasyk.

 

It'a not a bug, it's a feature

 

Chodzenie na skróty jest dobre na krótką metę. Długo terminowo może być bardzo kosztowne. Jak masz zamiar kopiować kod to zastanów się nie dwa a pięć razy czy nie lepiej byłoby go wydzielić do osobnego miejsca i dzielenie pomiędzy modułami.

 

Czy nie tak postępujesz z frameworkiem, którego używasz? A może dekompilujesz kod i wklejasz do swojej klasy?

 

To tylko kilka zasad czystego kodu. Temat będę w różnych wątkach kontynuował w przyszłości. Złożoność i mnogość aspektów jest bardzo duża i każde z zagadnień mogłoby być poparte długim wywodem.

Komentarze

Bądź pierwszą osobą, która skomentuje ten post.

Napisz komentarz