We wpisie na temat czystego kodu jako jeden z jego atrybutów wymieniłem nadawanie odpowiednich nazw.

 

Napisany kod jest czytany jest przez kompilator. Dla niego nie ma znaczenia to jak obiekty, zmienne czy inne rzeczy będą nazwane. Ostatecznie będzie kod maszynowy, który się wykona albo nie. Jednakże najczęściej to kod ten czytany jest albo przez autora albo też innych programistów.

 

Z uwagi na to, że będzie to czytała istota ludzka to warto by było aby czytający mógł się skupić na tym co ten kod robi a nie jak wygląda. Jak piszesz kod dla kolegi, to pisz go tak jak ty byś chciał aby on pisał dla Ciebie.

 

Konwencja nazewnicza

Programujesz od dłuższego czasu? Zmieniasz projekt? Siadasz do środowiska, w którym programujesz. I co? Piszesz jak zawsze czy tak jak inni w projekcie? Konwencja nazewnicza używana dotychczas może nie być taka sama jak w nowym projekcie. Jeżeli uważasz, że to w jaki sposób zespół nazywa elementy w swoim kodzie jest daleki od idealnego, to zmieńcie tą praktykę wspólnie. Jednakże, niech będzie spójność.

 

Inspekcja kodu

Jeżeli w projekcie nad którym pracujesz przeprowadzasz regularnie inspekcję kodu, to konwencja nazewnicza ma ogromne znaczenie. Sprawdzając kod innej osoby zamiast skupiać się na kłujących w oczy nazwach lepiej poświęcić uwagę na to co ten kod rzeczywiście robi. Albo powinien robić.

 

Sposób komunikacji

Pisanie kodu to nie tylko komunikacja z kompilatorem. Pisząc kod komunikujesz się z innymi osobami, które będą go w przyszłości czytały. Po pierwsze niech napisany kod jasno wyraża cel. Po drugie niech łatwo się go czyta. Jak już te warunki masz spełnione to pomyśl o tym jak będą Ciebie oceniać inni. Napisany przez Ciebie kod jest Twoją wizytówką. To jak Twoje CV. Tak więc dbaj o to by wyglądał on jak najlepiej.

 

Powszechne nazwy to część języka programowania

Już wcześniej cytowałem Phila Karltona.

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

 

Problem nie musi być jednakże tak duży jak by się mogło wydawać. Zacznij od tego by nazwy określające powszechne zagadnienia stały się częścią języka programowania. Nie wymyślaj nowych nazw na istniejące już terminy. Na to nie sensu tracić czasu. Odstaw swoje ego a kreatywność wykorzystaj na lepsze rozwiązanie problemów z jakimi się mierzysz. Na pewno nimi nie są nazwy jakich powinieneś używać.

 

Bądź też konsekwentny w tym co robisz. Jak spełnisz ten warunek to nazywanie elementów kodu przestanie być problemem. Stanie się to tak naturalne jak pisanie nowej klasy czy deklarowanie zmiennej.

 

Nazwy łatwe do wymówienia

Jeżeli musisz prezentować co robi Twój kod innym osobom z zespołu, to niech on będzie napisany tak by łatwo było Ci go wymówić. Nie komplikuj w tym miejscu. Nie skracaj przesadnie nazw. Nie ma sensu łamać sobie języka omawiając kod tylko po to by zaoszczędzić kilka znaków napisanych na klawiaturze.

 

Nazwa wskazuje cel

Nazwa metody, argumentów, zmiennych itp. Powinny jasno wskazywać na cel w jakim będą użyte. Jeżeli warunek ten zostanie spełniony to kod staje się samo komentujący. Analizując tak napisany kod nie trzeba poświęcać zbyt wiele czasu na rozszyfrowanie działania algorytmu. Zagadnienie to nawiązuje na używania powszechnych nazw. W przypadku zmiennych może być to bardzo proste, jednakże większy poziom skomplikowania jest w przypadku metod. Metody jest trudniej nazywać szczególnie w przypadku gdy robią zbyt dużo rzeczy. Na ratunek przychodzi pojedyncza odpowiedzialność. Temu zagadnieniu poświęcę osobny wpis. Pisanie kodu SOLID-nego to bardzo ciekawy, choć trudny aspekt programowania. Pozornie trudny, bo gdy nawyki się już wyrobi to trudne stają się inne rzeczy.

 

Komentarze w kodzie

Jest to temat bardzo często kontrowersyjny. Ja jednakże wychodzę z założenia, że kod sam się powinien komentować. Jak wszystko zostanie dobrze nazwane to nie ma potrzeby dodawania komentarzy. Po co spędzać czas na pisanie komentarzy jak można go wykorzystać na wymyślenie dobrej nazwy. Komentarze w kodzie to bardziej złożony temat. Czasem straszny a czasem zabawny. Poświęcę na niego osobny wpis w przyszłości.

 

Długość nazw

Nazwy powinny być na tyle długie by było wiadomo jaki jest cel tego na co wskazują. Jednakże powinny być też na tyle krótkie, by nie trzeba było używać suwaka poziomego lub zmniejszania wielkości czcionki aby wszystko objąć wzrokiem. PO prostu ich długość powinna być w sam raz. Jeżeli nazwa jest zbyt długa to może to oznaczać na złamanie zasady pojedynczej odpowiedzialności. Znowu kłania nam się jeden z filarów SOLID-nego kodu. Reasumując nazwa powinna być zwięzła ale nie taka która ma w sobie ukrytych wiele skrótów. Najlepiej jak skrótów w nazwach nie ma w ogóle.

 

Jeżeli akcja to czasownik

Metody odpowiedzialne są za jakieś akcje. Dlatego też w ich nazwie powinien znajdować się czasownik, lub też powinny być czasownikiem. Warto spojrzeć na ten problem z perspektywy kontekstu w jakim jest kod wykonywany. W przypadku metod oczekujemy, że coś zrobią. Jeżeli funkcja ma pełni rolę gettera, np. zwracającego wartość CSV obiektu, to może być nazwana GetCSV. Obiekt, mógłby mieć także właściwość CSV, której getter by zwracał tą samą wartość. Jednakże metoda sugeruje, że coś się będzie wykonywać, natomiast właściwość, że wartość jest już zapisana w obiekcie. Może mieć to wpływ na wydajność programu.

 

Ciekawym praktycznym przykładem, z którym większość programistów .NET ma styczność na co dzień są metoda i właściwość Count w LINQ. W tym przypadku nazwa może być zarówno rzeczownikiem jak i czasownikiem. Jednakże wykorzystanie właściwości i metody może mieć określony wpływ na wydajność. W przypadku gdy zwracany obiekt jest typu ICollection, wtedy metoda odwołuje się bezpośrednio do właściwości Count, która została już obliczona podczas dodawania i usuwania elementów. Natomiast w przypadku gdy obiekt nie jest typu ICollection, to wtedy cała kolekcja musi być enumerowana aby obliczyć ilość elementów.

 

W przypadku oporu przed czasownikami dobrze jest chociaż zastosować przyimek lub zaimek, który będzie wskazywał na relację pomiędzy funkcją i jej wynikiem. Przykładem takiego podejścia jest rzutowania typów. Np.. ToString lub IndexOf.

 

Brak akcji jest rzeczownikiem

W przeciwieństwie do metod, od zmiennych i właściwości oczekujemy, że będą one statyczne. Tutaj nie ma z reguły żadnej akcji. Pewnym wyjątkiem są właściwości, które jako minimum wykonują akcję ustawienia wartości ukrywającej się pod nim zmiennej. Z racji tego, że nazywają one coś statycznego powinny być rzeczownikami.

 

Jedna czynność = jedna nazwa

Unikaj używania wielu nazw do określenia tej samej czynności. Jeżeli jest kilka klas, które mają operować na źródle danych to niech metody odpowiedzialne za poszczególne akcje nazywają się tak samo. Czyli jak coś ma być zapisane to metoda w każdej klasie niech nazywa się Save, a nie w jednej Save, w innej Persist a jeszcze w innej Record. Tutaj powrócę do powszechności nazw. Jeżeli jedna nazwa jest używana do opisania danej akcji i jest to traktowane jako część języka to odpada konieczność silenia się nad tworzeniem nazw. Po prostu ta konkretna akcja staję się częścią języka.

 

Tak samo, nie powinno się używać jednej nazwy do nazwania wielu różnych akcji. Załóżmy, że klasa może pobierać wartości z różnych źródeł danych. Np. z bazy danych i pliku. Użycie w tym przypadku nazwy Get dla obu operacji wyciągania danych utrudniłoby czytanie kodu. O wiele czytelniejszy byłby kod gdyby nazwy metod były GetFromDatabase  oraz GetFromFile. Dalej metody, mogłyby przyjmować różną ilość, bądź tym parametrów wejściowych, jednakże byłoby jednoznaczne z jakiego źródła pobierane są dane. To takie uproszczenie tylko, gdyż w przypadku tego typu problemu, lepiej było by zastosować wzorzec Repository. Wtedy rzeczywiście nazwa mogłaby być identyczna, ale to już nazwa klasy wskazywałaby na typ źródła danych.

 

Nieczytelny kod pokusą do jego przepisania

Kod, który jest nieczytelny i trudny do odszyfrowania staje się pokusą do jego przepisania. Przecież jak się przepisze kod na taki, który będzie bardziej czytelny to łatwej będzie go utrzymywać. Tak może i często jest. Jednakże, każda próba przepisania, wymyślenia na nowo, programu zwiększa ryzyko wprowadzenia błędów lub co więcej niepożądanej zmiany logiki istniejącego programu.

 

Najlepiej aby pisać kod, by był on zrozumiały dla innych osób i nie rodził pokus przepisywania. Prosta zmiana nazwy także może rodzić problemy w przypadku, gdy ktoś używa dynamicznych typów danych. Niestety ale na etapie kompilacji nie będzie wiadomo czy stare nazwy są dalej w użyciu.

 

To kilka zasad, których ja staram się przestrzegać pisząc kod z definicji czytelny. Jednakże to jest tylko mój styl i każdy może kodować w inny sposób. Masz inne spostrzeżenia, proszę podziel się opinią.

 

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.