Spaghetti na ostro czyli jak uniknąć kodu spaghetti


- Nie możesz nazwać funkcji udpate_counters a w ciele funkcji robić coś innego. Nazwa funkcji powinna opisywać jej funkcjonalność. A tutaj podbijasz licznik, super, ale oprócz tego robisz jakieś DELETE. Co to za DELETE? On nie ma nic wspólnego z tymi licznikami. Wyjmijmy go funkcji i przełóżmy gdzieś indziej, do odrębnej procedury.
- Jasne, jasne.. Nazwa ma odzwierciedlać funkcjonalność… -chwila głębokiego namysłu i nagle – EUREKA - To ja zmienię nazwę z na update_counters_and_delete_tmp_tbl!

Kod Spaghetti

Co to jest kod spaghetti? To chyba oczywiste. Każdy programista go widział. Oczywiście autorem tego kodu zawsze jest ktoś inny, nie ja.

Według definicji na wikipedii  to:

„ termin określający skomplikowany, trudny do zrozumienia kod źródłowy programu. Z takiego kodu skompilowano wiele programów w starszych językach proceduralnych, typu Fortran czy BASIC. Było to jeszcze przed powstaniem metody programowania strukturalnego.”

Kod spaghetti kojarzy się głównie z długimi, poplątanymi procedurami napisanymi bardzo dawno temu. Bo w dzisiejszych czasach nikt nie pisze kodu spaghetti, prawda?


Jak zatem sprawdzić, czy mamy do czynienia z kodem spaghetti?


FAKT: Kod spaghetti jest długi jak spaghetti

Oczywiście pierwszym symptomem kodu spaghetti jest sama długość kodu. Długie programy są trudne do zrozumienia i analizy. Ekstremalnie długie programy potrafią mieć kilkaset a nawet kilka tysięcy linii kodu. Ilość rozgałęzień czy ścieżek logicznych w takich programach jest praktycznie nieskończona. Poruszanie się po takim kodzie jest bardzo trudne a każda zmiana może powodować lawinowe błędy w innych miejscach programu. Znalezienie zaś miejsca wystąpienia błędu – bardzo czasochłonne.

Dużo łatwiejsze do obsługi, analizy, rozwoju są krótkie programy. Standardowo długość jednej jednostki programowej nie powinna wykraczać poza ekran monitora. Dość istotne jest by daną procedurę można było objąć wzrokiem bez przewijania ekranu. Łatwiej jest wtedy zrozumieć całość działania procedury. Z reguły w Dobrych Praktykach przyjmuje się około 50-80 linii kodu (samego body, bez sekcji deklaracji i sekcji obsługi wyjątków). W krótkim programie łatwo zrozumieć jego funkcjonalność, przetestować, wykryć naprawić błąd. Ponieważ funkcjonalności są od siebie odizolowane w mniejszym stopniu zmiany w jednej jednostce programowej wpływają na działanie innych procedur.


MIT: Tylko długi kod to kod spaghetti

To niestety nieprawda. Kod może być kodem spaghetti nawet jak jest bardzo krótki! Nawet jak się mieści w zalecanych 50-80 liniach. Czasem wystarczy tylko 50 linii kodu by móc na tyle skomplikować proces, wsadzić tam tyle różnych funkcjonalności by móc pretendować do miana kodu spaghetti. Na przykład funkcja z początkowej anegdotki. Funkcja ta nie miała więcej niż 10 linijek. W jednej linijce podbijany był licznik. Następnie były usuwane dane z tabeli – nie związanej z licznikami. Niby krótka funkcja ale z uwagi na pomieszanie różnych funkcjonalności jak najbardziej zasługuje na miano kodu spaghetti. W tej sytuacji należałoby przejrzeć kod i wydzielić obie funkcjonalności do osobnych jednostek programowych.

FAKT: Skomplikowany proces

Drugim symptomem jest duża ilość różnych klauzuli warunkowych i pętli. Jeśli w procedurze mamy dużo IF’ów, pętli, if’ów w pętlach w pętlach, przerwań wykonywania procesu – GOTO, return, exit – od razu możemy stwierdzić, że mamy do czynienia z kodem spaghetti. Procedury powinny mieć jasny i przejrzysty przebieg.

Skomplikowane klauzule warunkowe, wyliczenia czy pętle powinny być pochowane w wyspecjalizowanych funkcjach.


Każda procedura powinna mieć tylko jedno wyjście. Funkcja też.


W funkcjach nagminne jest nadużywanie klauzuli RETURN. Ile razy powinno występować? Policzcie w swojej bazie ile razy w pojedynczej funkcji występuje u was słówko RETURN? Powinno być jedno. Dokładnie jedno. Zamiast wyjścia z programu lepiej jest ustawić zmienną wyjściową a przerwanie programu i zwrócenie wyliczonej wartości wykonać raz, na końcu kodu.

W pętlach często niepotrzebnie się stosuje przerwania EXIT. Mówicie, że czasem potrzeba wyjść z pętli przed przejrzeniem wszystkich rekordów? To może zastosować inny rodzaj pętli: zamiast for z in użyć while?

Innym nadużywanym wyjściem z programu jest wywołanie wyjątku RAISE. Zamiast wywoływać RAISE w niespodziewanym miejscu w kodzie lepiej jest zdefiniować konkretny wyjątek, wywołać go odpowiednim miejscu RAISE <nazwa wyjątku> a dopiero sekcji EXCEPTIONS wyjść z programu.


MIT: proces podzielony na mniejsze jednostki programowe nie jest kodem spaghetti

Czy procedura podzielona na mniejsze jednostki programowe nigdy nie będzie kodem spaghetti? Niekoniecznie. Proces powinien być podzielony według zasady „od ogółu do szczegółu” a poszczególne jednostki programowe powinny realizować jedno konkretne zadanie. Niestety często programiści próbując zastosować się do tych zasad robią to na chybił trafił i dzielą długą procedurę na kawałki w całkowicie losowych miejscach. Wygląda to trochę jak rąbanka. W ten sposób dalej pozostajemy przy kodzie spaghetti. Tyle że podzielonym na mniejsze kawałki. Mamy jeden duży kocioł z małymi kodzikami spaghetti. Podział taki tak naprawdę niewiele wnosi.

Podział procesu na mniejsze jednostki programowe ma sens wtedy, gdy ten podział umożliwia lepsze zrozumienie przebiegu procesu, łatwiejszą analizę i lepszą czytelność kodu. Przy podziale losowym czy nieprawidłowym – żadna z tych zalet nie jest uzyskana. Wiele kodów spaghetti nie jest łatwiejsza do zrozumienia czy analizy niż jeden duży kod spaghetti. A do tego można się zirytować musząc skakać od funkcji do funkcji w zupełnie często niespodziewanych miejscach.


Przykład:

Pakiet: generate_file
Procedury: Main_file, Start_file, Run_file, Gen_file

Ile wiemy po przejrzeniu specygikacji pakietu? Szczerze mówiąc niewiele. Jedne co wiemy, to że procedury generują jakiś plik. Ale która procedura odpowiada za jaką funckjonalność w procesie?


Przykład 2.

Pakiet: generate_file
Procedury: Main, Get_filename, Get_data, Open_file, Create_line, Save_line, Close_file

W powyższym pakiecie będzie dużo łatwiej się poruszać. Już na pierwszy rzut oka mamy rozeznanie, jakie zadania wykonuje proces i która procedura odpowiada za jaką czynność podczas generowania pliku.


Soczewica koło miele młyn czyli ostateczny test kodu spaghetti

Za czasów Władysława Łokietka przeprowadzano test polskości każąc powtarzać słowa „soczewica koło miele młyn”. Dzięki temu oddzielano obcokrajowców od rdzennych mieszkańców Polski.
I my też możemy zweryfikować prostym testem czy mamy ładny kod czy niestety wyszedł nam kod spaghetti.

Testem tym jest „próba nazwania procedury”. Ale nie tak jak programista z historyjki z użyciem łącznika AND. Ale określenie jednym krótkim wyrażeniem sedno procesu. Jeśli mamy z tym problem - prawdopodobnie albo nie wiemy dokładnie, co robi nasza funkcja albo robi tak dużo rzeczy, że nie jesteśmy w stanie tego konkretnie określić. W obu przypadkach mamy prawdopodobnie kod spaghetti. I problem. W takiej sytuacji należy przejrzeć kod, wydzielić poszczególne funkcjonalności, sprawdzić pętle i przerwania procesów, pochować skomplikowane algorytmy za wyspecjalizowane funkcje. Powtarzać – aż uda nam się dla każdej jednostki programowej wymyślić nazwę, która będzie krótka, zwięzła ale idealnie odzwierciedlała odpowiedzialność danej jednostki programowej.


Unikajmy kodu spaghetti nawet w małych porcjach

Wszyscy wiedzą, że kod spaghetti jest zły. I nikt nie chce pisać kodu spaghetti. Jednak niewiele osób zdaje sobie sprawę, że być może z powodu presji czasu czy błędnego myślenia, że kod, póki jest krótki, nie jest kodem spaghetti, sami wpadają w pułapke. Mam nadzieję, że ten post skłoni Was do spojrzenia krytycznie nawet na małe procedurki i zachęci do unikania kodu spaghetti nawet w małych porcjach.

Dobre Praktyki
1.  Nazwy jednostek programowych powinny być precyzyjne, odzwierciedlające funkcjonalność procedury
2.  Kod powinien mieścić się na ekranie
3. Podział programu na mniejsze jednostki powinien odbywać się według zasady "od ogółu do szczegółu" a nie na chybił trafił
4.  Ilość wyjść z programu powinna wynosić 1.
5. Skomplikowane algorytmy, warunki if należy schować za wyspecjalizowanymi funkcjami
6. Należy unikać przerwań pętli - lepiej dostosować typ pętli do typu problemu



Komentarze

  1. Bardzo fajny artykuł!
    Byle tak dalej!

    OdpowiedzUsuń
  2. Właśnie patrzę na procedurkę, która ma około 800 linii kodu i 14 wyjść z programu rozrzuconych po calej procedurze i pochowanych w ifach czy pętlach.
    Tak z ciekawości - jak u Ciebie, u Was to wygląda?

    OdpowiedzUsuń
  3. Dobry artykuł. Osobiście uważam że są 2 strony medalu - wiele funkcji w 1 pliku źle, zbyt wiele plików również źle. Przesadnie rozbudowana struktura również źle, prawdopodobnie zamuli program przesadną formą. Jedno jest pewne, wygoda programisty - sztuka dla sztuki jest ważna ale nie jest tu najważniejsza, najważniejsze są wrażenia użytkownika - szybkość i bezawaryjność programu.

    OdpowiedzUsuń
  4. Zgadza się. Najważniejszy złoty środek!A sensowna dekompozycja to nie takie proste zadanie.
    Przebijanie się przez gąszcz kodu spaghetti jest tragiczne. Jednak "skakanie" z jednej atomowej funkcyjki do drugiej, która nas przekierowuje do trzeciej i tak jeszcze ze 20 razy też może doprowadzić do skrajnych emocji...

    Do cech które słusznie wskazujesz : szybkość i bezawarynjność można by dodać jeszcze dwie ważne cechy-
    - łatwość modyfikacji (naprawa błędów, rozwój) oraz łatwość analizy kodu/debuggowania.
    Przy kodzie spaghetti - dwie cechy: szybkość i bezawaryjność - jestem w stanie sobie jakoś wyobrazić, choć z trudem, że da się osiagnąć.
    Jednak przy długich jednostkach programowych praktycznie nie ma szans, by kod był łatwy w modyfikacji czy łatwy do analizy :D
    A jeśli kod jest trudny do rozwoju i trudny do debugowania -nie będzie na pewno tani :D

    OdpowiedzUsuń
  5. Ten komentarz został usunięty przez administratora bloga.

    OdpowiedzUsuń

Prześlij komentarz