Na co zwrócić uwagę sprawdzając kod przed uruchomieniem

Jakość kodu

Niezależnie od tego czy pracujesz w zespole czy poza, czy realizujesz duży projekt czy mały – zawsze powinieneś dbać o jakoś swojego kodu.

Oczywiście – nie będzie on zawsze idealny, ale trzeba do tego dążyć. Swoje umiejętności poszerzasz każdego dnia (a przynajmniej powinieneś), używany język programowania ulega zmianom, standardy programowania także się zmieniają (np. PSR-12) lub nowe techniki wytwarzania kodu zdobywają popularność – nie zależnie od tego wszystkiego, zawsze powinieneś napisać najlepszej możliwej jakości kod.

Testowanie

Zapomnij o tym co napisano powyżej 🙂 Nie musisz dbać o jakość kodu jeśli nie robi on tego co powinien. Dla twojego klienta nie ma znaczenia jak doskonały jest kod, jeśli nie realizuje on założonej funkcjonalności i zawiera mnóstwo błędów.

Nigdy nie powinieneś zbytnio ufać samemu sobie – bo czy jesteś pewien, że nigdy nie robisz pomyłek? Każdy programista popełnia jakieś błędy spowodowane różnymi aspektami – nie możesz oczekiwać, że ty jesteś wyjątkiem.

Dlatego też – najważniejszym elementem jest to, aby twój kod robił to co powinien. Aby mieć tego pewność – powinieneś go testować. Jeśli nie możesz napisać testów na całość kodu – pokryj testami choćby te najważniejsze czy też skomplikowane elementy, których funkcjonowanie ma kluczowe znaczenie dla działania aplikacji.

Najlepszym rozwiązaniem są oczywiście zautomatyzowane testy. W PHP można np. użyć PHPUnit lub Codeception. To potężne narzędzia, dające Ci możliwość napisania zautomatyzowanych testów, wykrywających zaistniałe błędy. Dobrze jest przyjąć praktykę, że jeśli piszesz jakiś nowy kawałek kodu – tworzysz do niego także testy, pokrywające jak największą ilość możliwych kluczowych kombinacji warunków brzegowych. Jest tu jednak pewien haczyk – testy będą tak dobre, jak dobrze je sam przygotujesz i napiszesz.

Niestety w dalszym ciągu zdarzają się projekty, z którymi musisz pracować a takowych testów nie posiadają. A nawet jeśli posiadają – dobrze jest je zweryfikować, bo czy masz pewność że w odziedziczonym kodzie są one poprawne? Reasumując – zawsze testuj kod!!

Poza testami automatycznymi, dobrymi narzędziami wspomagającymi testowanie jest np. Postman – który pomaga testować API.

Ważne jest też, żeby testować kod zarówno na lokalnym środowisku jak i na środowisku docelowym – czasem zdarzają się drobne różnice, które mogą mieć duży wpływ na działanie aplikacji.

Nawet jeśli posiadasz w zespole testera, nie oznacza to że jesteś zwolniony z testowania swojego kodu, zanim trafi on do tej osoby. Takie podejście generuje znaczący wzrost kosztów wytworzenia aplikacji.

Repozytorium kodu

Zakładam, że używasz jakiegoś repozytorium kodu np. GIT? Jeśli nie – to definitywnie powinieneś zacząć, nawet jeśli tworzysz kod tylko dla siebie.
W najprostszym podejściu, dobrą praktyka jest aby podzielić projekt na zadania i każde realizowane zadanie tworzyć na osobnej gałęzi, dedykowanej do tego zadania. Przykładowo – jeśli dodajesz nową metodę w API, utwórz nową gałęź, zatwierdzaj zmiany w kodzie na tej gałęzi a kiedy wszystko jest skończone – wyślij na repozytorium kodu i utwórz żądanie złączenia do głównej gałęzi. Dzięki temu oddzielisz kod związany z nową funkcjonalności i łatwiej będzie przeglądnąć wszystkie zmiany jej dotyczące.Także osoba prowadząca projekt będzie miała w ten sposób ułatwione zadanie jeśli będzie chciał zrobić przegląd twojego kodu (co przy okazji jest dobrą praktyką, aby programiści w zespole przeglądali wzajemnie swój kod).

Ogólna jakość kodu

A więc osiągnąłeś pułap, gdzie twój kod robi to co powinien. Działa poprawnie. Ale czy jest gotowy żeby być uruchomiony jako wersja produkcyjna? Niestety prawdopodobnie nie!!

Pierwsza sprawa to poprawa to poprawa jego jakości, czytelności – przeglądnij go, i pomyśl czy nie można niektórych elementów zrobić lepiej?

Krótszy znaczy lepszy?

Czasem patrząc na kod dostrzeżesz, że pewnie jego elementy można skrócić. Ale czy zawsze krótsze znaczy lepsze? W wielu przypadkach tak – ale nie zawsze.

Przykładowo skrócenie 3 linijek do 1 nie będzie dobrym rozwiązaniem, jeśli w ten sposób kod stanie się mniej czytelny dla innych programistów.
Z drugiej strony będzie dobrym rozwiązaniem, jeśli jest to jakaś podstawowa sprawa i możesz do tego wykorzystać wbudowane narzędzia dostarczane przez twój framework lub składnię języka.

Dla przykładu w Laravel można napisać kod tak:

$user = User::find($id); 

if($user) {
    abort(404); 
}

Nie ma nic złego w tym kodzie, ale można go usprawnić i zapisać tak:

$user = User::findOrFail($id);

Zamiast 4 linijek kodu, użyta została wbudowana metoda z frameworka, zmieniając to na jedna czytelną linię kodu.

Duplikacja kodu

Podczas tworzenia nowych fragmentów kodu, w wielu przypadkach nie zrobisz tego odrazu w najlepszej możliwej formie. Bawisz się z kodem, dodajesz nowe funkcje, przenosisz coś, zmieniasz, kopiujesz etc. Nie ma w tym nic złego – ale w końcowym etapie, dobrze było by posprzątać bałagan jaki może się zrobić.

Jednym z elementów powstałych w rezultacie pracy będzie duplikacja kodu, której powinno się unikać jak to tylko możliwe. Posiadanie 3 takich samych lini kodu w 10 różnych miejscach aplikacji nie jest najlepszym rozwiązaniem. Co jeśli będziesz musiał zmienić jakąś funkcjonalność, gdzie te fragmenty są wykorzystywane? Będziesz zawsze pamiętaj o tych wszystkich 10 miejscach, żeby nanieść na nie poprawki?

Zduplikowany kod jest bardzo ciężki w utrzymaniu.  Duplikacja kodu nie oznacza jedynie miejsc z dokładnie identycznym kodem ale także takich, gdzie masz możliwość wydzielenia kodu do osobnej funkcji z parametrami, ale tego nie robisz – a zamiast tego kopiujesz ten kawałek kodu z różnymi wartościami.Dla przykładu:

protected function applyUserId($value)
{
    // no value, nothing to do
    if (! $value) {
        return;
    }
    ($value === static::EMPTY) ? $this->query->whereNull('user_id') : $this->query->where('user_id', (int) $value);
}

protected function applyProjectId($value)
{
    // no value, nothing to do
    if (! $value) {
        return;
    }
    ($value === static::EMPTY) ? $this->query->whereNull('project_id') : $this->query->where('project_id', (int) $value);
}

Jeśli spojrzysz uważniej na kod powyżej, zauważysz że jest to powtarzający się dokładnie taki sam. W jednej metodzie używany jest user_id w dwóch miejscach, w drugiej project_id. Ale czy reszta kodu nie jest identyczna? Można więc to zrobić lepiej, przykładowo:

protected function addEmptyFieldQueries($field, $value)
{
   // no value, nothing to do
   if (! $value) {
       return;
   }
   ($value === static::EMPTY) ? $this->query->whereNull($field) : $this->query->where($field, (int) $value);
}

i teraz możesz tego kodu używać wielokrotnei np. tak:

protected function applyUserId($value)
{
    $this->addEmptyFieldQueries('user_id', $value);
}
protected function applyProjectId($value)
{
    $this->addEmptyFieldQueries('project_id', $value);
}

Widać różnicę? Zduplikowana część kodu została usunięta.

W prawdziwych projektach często widzę nawet 20 linijkowe elementy kodu powtarzające się w 10 razy, gdzie jedyną różnicą jest numer kolumny w zapytaniu SQL. Analiza takiego kodu, kiedy przyjdzie Ci z nim pracować to koszmar. Lepiej odrazu porównać czy faktycznie te zapytania nie robią tego samego i go zreafaktoryzować. Pozornie dodatkowy czas na refaktoring, zwróci się później – ponieważ nie będzie potrzeby wielokrotnej analizy tych 10 miejsc aplikacji.

Organizacja kodu

Następny element to organizacja kodu. Oczywiście – możesz wsadzić cały kod do jednego pliku i pewnie da się to zrobić aby on działał. Ale czy to najlepsze rozwiązanie i możliwa potem będzie wydajna praca z takim kodem? Twój kod powinien być podzielony na odpowiednie bloki logiczne, używać możliwie krótkich funkcji i klas. Nie jestem fanem tworzenia klas z jedną metodą, ale czasem jest to właściwe rozwiązanie,  jeśli dana klasa zajmuje się czymś specyficznym i możliwe, że będzie to rozbudowane w przyszłości.

Mówiąc o organizacji kodu nie mam na myśli wyłącznie refaktoryzacji dużych metod do kilku mniejszych.Czasem jest to reorganizacja kodu w skali mikro, której celem jest poprawa czytelności kodu i ułatwienie jego utrzymania.

Dla przykładu, załóżmy że masz model Article zawierający pole z datą publikacji (published_at) , zawierające wartość null  jeśli artykuł nie był jeszcze publikowany. Aby zdecydować, czy poinformować kogoś o tym, że artykuł został opublikowany możesz napisać przykładowy kod tak:

if ($article->published_at !== null) {
    $this->notifyAdministrator($article);
}

Nie ma w nim nic złego – ale myślę, że można zrobić to lepiej. Ten warunek jest bardzo prosty, ale co jeśli używasz go w kilku innych miejscach aplikacji?Co jeśli w przyszłości będziesz chciał zmienić ten warunek na powiedzmy coś takiego:

if ($article->published_at !== null && $article->verified) {
    $this->notifyAdministrator($article);
}

Musiałbyś przeszukać całą aplikację i zmienić wszystkie wystąpienia tego kodu (trochę powiązane z tym co pisałem powyżej o duplikacji kodu). Lepiej odrazu wyodrębnić taki kod i napisać przykładowo taką metodę w modelu artykułu:

function isPublished()
{
   return $this->published_at !== null;
} a wystąpinie w kodzie zamienić na jej wywołanie: if ($article->isPublished()) {
    $this->notifyAdministrator($article);
}

Jest to o wiele bardziej czytelne, mniej skomplikowane i łatwiejsze w utrzymaniu.

Właściwy dobór nazw

Dla utrzymania przejrzystości kodu istotnym elementem jest właściwy dobór nazw dla zmiennych, metod, klas, interfejsów czy traitsów. Źle dobrane nazwy, mogą czynić kod nie tylko mało czytelnym, ale nawet wprowadzać w błąd potencjalnego programistę który będzie pracować z kodem aplikacji. Spójrzmy na przykład:

$users = $this->findMatchingUser();
protected function findMatchingUser()
{
    // very complicated code here
    return new User();
}

W powyższym przykładnie nazwa metody jest poprawna, ale już nazwa zmiennej  $users jest myląca. Osoba pracująca z tym kodem będzie myślała, że zmienna $users zawiera kolekcję/tablicę użytkowników a nie pojedynczego użytkownika.

Kolejny przykład:

$array = [1, 2, 3];
$sum = $this->calculateSum($array);
protected function calculateSum(array &$array)
{
    array_walk($array, function(&$item) {
        ++$item;
    });
    return array_sum($array);
}

Można zauważyć, że nazwa funkcji calculateSum jest całkowicie niedopasowana. Funkcja ta nie tylko oblicza sumę, ale także zmienia elementy podanej tablicy zmiennych,

W przypadku, kiedy jakiś programista chciałby użyć tej metody bez zaglądania co ona rzeczywiście robi w środku, mogło by to doprowadzić do nieoczekiwanych rezultatów – w końcu ta metoda powinna tylko obliczać sumę.

Komentarze

Kolejną istotną sprawą są komentarze. Nie chodzi tutaj o docblocs, ale ogólne umieszczanie komentarzy wewnątrz własnego kodu. Nie są one zawsze potrzebne – zwłaszcza jeśli nazwy zmiennych i funkcji są dobrze dobrane, jednakże czasem zdarza się potrzeba wyjaśnienia

Dla przykładu:

public function store(IntegrationCreate $request)
{
    $provider = IntegrationProvider::findOrFail($request->input('provider_id'));
    $integration = Factory::make($provider);

    // run specific integration validation
    $request = app()->make($integration::getValidationClass());
    // ...
}

Widać powyżej, że zamieszczony został komentarz. Dla mnie w tym przypadku było oczywiste, że walidacja musi się odbywać w tym konkretnym miejscu – ale dla innego programisty nie musi to już być takie oczywiste. Tak więc myśląc o komentarzach, stosuj je tam, gdzie mogą być elementy niezbyt oczywiste dla innego programisty lub nawet dla Ciebie, jeśli będziesz musiał wrócić do kodu po paru latach.

Refaktoryzacja

Potrzeba refaktoryzacji kodu przed opublikowaniem na serwerze produkcyjnym powinna być czymś oczywistym. Część elementów refaktoryzacji jak usuwanie duplikacji kodu czy jego poprawna organizacja, już opisałem powyżej. Ale jest coś więcej.

Za każdym razem, kiedy tworzysz nową funkcjonalność, może się zdarzyć, że skopiujesz kawałek innego kodu i wprowadzisz na nim drobne zmiany. Za każdym razem gdy takie coś robisz, warto zastanowić się czy ten kod nie będzie ponownie gdzieś użyty. Może warto stworzyć funkcję z tego kawałka kodu, i użyć ją zarówno w nowym miejscu jak i w starym z któego pochodziła?
Najważniejsze o czym trzeba pamiętać podczas refaktoryzacji, to aby nie zepsuć czegoś co już działa. Jeśli jest ot jakaś bardziej skomplikowana część – upewnij się, że masz do niej napisane testy i uruchamiaj je po każdej nawet małej zmianie (a nie dopiero na sam koniec). Refaktoryzacja powinna być etapowa.

Format kodu

Ostatnim elementem, który może podnieść jakoś twojego kodu jest poprawne sformatowanie. Dobrze jest wdrożyć i utrzymywać jakiś wybrany styl formatowania kodu. Czy wolisz używać camelCase czy może snake_case i dlaczego? Napewno jako programista PHP powinieneś znać i stosować przynajmniej standardy PSR-2. Pomocnym narzędziem, które wspomaga naprawę źle sformatowanych elementów kodu jest np. PHP CS fixed lub PHP CodeSniffer.

Łączenie kodu w repozytorium

W momencie kiedy zakończyłeś pracę z kodem i chcesz go wysłać na repozytorium kodu, nie powinieneś  łączyć go bezpośrednio do gałęzi docelowej. Oczywiście jest kilka różnych metodyk – i w niektórych z nich jest to akurat właściwe podejście. Jeśli jednak pracujesz z zespołem, a poziom programistów jest zróżnicowany – powinieneś wykorzystywać mechanizm żądania złączeń (pull request). W momencie utworzenia żądania, dajesz znać, że twój kod jest gotowy do złączenia i zakończyłeś pracę nad nim. Osoba odpowiedzialna za projekt, wie w tym momencie, że ten kawałek kodu można sprawdzić i ewentualnie zatwierdzić. Nawet samemu dla siebie warto korzystać z tego, aby przyglądnąć się wprowadzonym zmianom i upewnić się, że napewno czegoś nie pominęliśmy – ponieważ w przeglądzie żądania widać dokładnie wszystkie zmiany kodu.

Techniki wspomagające pracę z własnym kodem

TODO twoim przyjacielem jest…

Podczas pracy nad nową funkcjonalnością, pewnie część elementów celowo opuszczasz – zostawiając je do zrobienia „na potem”. Jestem pewien, że nikt z nas nie pisze kodu od razu w wersji „finalnej”. więc jeśli coś pomijamy, dobrze jest dodać blok TODO, w którym zapiszemy co jeszcze trzeba dorobić, dla przykładu:

// @todo add validation

lub

// @todo add transaction

lub

// @todo what if there is no user set?

Pisząc w ten sposób, bardzo pomagasz sam sobie. Po pierwsze – jestem pewien, że przy konieczności pamiętania o wielu elementach takich jak te nie będziesz w stanie tego zrobić i coś pominiesz. A może jednak ufasz swojej pamięci że jest tak doskonała?
Inny przypadek – może zdążyć się coś nieoczekiwane – przykładowo, twój szef powie Ci, że masz przerwać cokolwiek teraz robisz i zająć się czymś innym. Jaka masz pewność, że po pewnym czasie kiedy powrócisz do pracy nad tym kodem, dalej będziesz o wszystkich szczegółach pamiętać, które miałeś dorobić? Dlatego bloki TODO należy dodawać na bierząco – a nie dopiero jak „coś skończysz”.

Małe zatwierdzenia kodu

Podczas pracy nad nową funkcjonalnością nie powinieneś czekać, aż kod będzie skończony, żeby zrobić commit (mowa np. o GIT). Dobrą praktyką jest bardzo częste robienie commitów przed wysłaniem finalnej wersji na serwer repozytorium kodu. Dzięki temu, łatwo potem sprawdzisz jaki kawałek kodu był zmieniany pod konkretny element funkcjonalności.
Oczywiście, bardzo istotne w tej kwestii jest nadawanie jasnych i klarownych opisów danego commita. Dodanie opisu typu “Bug fix” nie pomorze Ci zbytnio w późniejszej analizie.. Dobrą praktyką jest wpisywanie numeru zadania do którego dany commit się tyczy, oraz krótkiego opisu co jest robione np. “TICKET-123 – fix bug with invalid query string”.

Korzyści

Jest wiele korzyści płynących z stosowania powyższych zasad. Po pierwsze – przeglądając swój własny kod, sam zaczniesz dostrzegać co jest w nim nie tak i w ten sposób podnosić swoje umiejętności jako programista. Także przeglądanie kodu innych programistów podnosi twoje kwalifikacje. Przeglądając kod, wyłapujesz sporo błedów już na etapie przygotowania – w ten sposób koszty wytworzenia projektu maleją – bo mniej osób jest zaangażowanych w wykrycie błędu.

author: Marcin Nabiałek, programista zespołu Laravel w Devpark

Dołącz do nas, zostaw swoje CV na larajobs.pl