Kodowanie pod nadzorem

Narzędzia do automatycznego badania jakości kodu mogą podnieść niezawodność oprogramowania, ale nie zastąpią pracowników działów jakości.

Narzędzia do automatycznego badania jakości kodu mogą podnieść niezawodność oprogramowania, ale nie zastąpią pracowników działów jakości.

O jakości oprogramowania decydują dwie rzeczy - projekt i kod. Projekt to fundament jakości - gdy jest wadliwy, jakość kodu na niewiele się zda - rozwiązanie nie będzie działać tak, jak życzyliby sobie jego twórcy, a już na pewno nie tak, jak wyobraża to sobie zamawiający. Jednak nawet najlepiej zaprojektowany system, jeżeli zostanie źle zakodowany, będzie działać nieprawidłowo, a koszty jego utrzymania będą relatywnie wysokie.

Ostrzeżenia na temat poprawności projektowania aplikacji, choć w istocie słuszne, mają dziś nieco mniejsze znaczenie niż kiedyś. Średni poziom wiedzy i oprzyrządowania analityków oraz projektantów systemów podniósł się bardzo znacząco. Patrząc na oferowane obecnie narzędzia i dostępność literatury i materiałów pomocniczych (wzorce, przykłady, gotowe biblioteki itd.) można nawet zaryzykować twierdzenie, że źle zaprojektować aplikację jest naprawdę trudno.

O ile lata innowacji w metodach i narzędziach projektowania zrobiły swoje, o tyle postęp w dziedzinie jakości kodowania nie jest imponujący. Wygenerować szkielet aplikacji może dziś każdy, zaś napisać dobry, czytelny, wolny od błędów kod - to nadal jest sztuka. Im większy projekt, tym bardziej jakość kodowania zyskuje na znaczeniu, tymczasem o powszechności wykorzystania rozwiązań do analizy jakości kodu mówić raczej nie można.

Istnieje jednak spore prawdopodobieństwo, że pod wpływem klientów wkrótce się to zmieni. Nie można zakładać, że użytkownicy oprogramowania będą bez końca tolerować przeciętną jakość - informatyka bierze na siebie coraz więcej, a klienci są coraz mniej skorzy, by wybaczać jej błędy. Wielu z nich można by uniknąć, stosując narzędzia do analizy kodu. Poniżej przedstawiamy ich krótką typologię wraz z opisem i przykładami konkretnych pakietów.

Kod z metryczką

Każdy tekst - również kod źródłowy, może być analizowany w sposób automatyczny. Koncepcja metryk zakłada, że w kodzie pojawiają się pewne charakterystyczne fragmenty oraz że pomiędzy różnymi blokami istnieją pewne związki. Większość opracowanych metryk pasuje niemal do "każdego" języka obiektowego czy strukturalnego. Język narzuca tylko sposób "wyliczania" indeksu. Najprostszym przykładem metryki jest po prostu średnia liczba linii kodu w pliku czy klasie. Jeżeli jest za duża, być może warto wydzielić pewien fragment(y) w oddzielnym module - choćby w celu zwiększenia czytelności. Inną zdroworozsądkową metryką może być stosunek liczby linii komentarzy do liczby linii kodu.

Warto także wiedzieć, w jakim stopniu określony moduł zależy od innych. Dwa wskaźniki - CA (Afferen Coupling) i CE (Efferent Coupling) określają, czy typy z danego modułu (w .NET - np. z danego pakietu) są często wykorzystywane w innych fragmentach kodu i odwrotnie - ile typów zewnętrznych używa dany moduł. Podobnie analizowane są związki pomiędzy klasami, np. jak często dana klasa jest "parametrem" czy wartością zwracaną przez funkcję w innej klasie itp.

Jeszcze inne wskaźniki wyliczają średnią liczbę odwołań do typu, "stopień abstrakcyjności" klasy (a więc jej wpływu na inne klasy), spójność (współczynnik LCOM - Lack of Cohesion Of Methods) itp. Najpopularniejszym wskaźnikiem jest CC (Cyclomatic Complexity). Wskazuje on na złożoność bloku kodu, która jest wyznaczana na podstawie liczby instrukcji warunkowych i pętli w danym fragmencie kodu.

Jakość według wzorca

Przykładowy raport wygenerowany przez CCCC.

Przykładowy raport wygenerowany przez CCCC.

Przykładów narzędzi wyliczających metryki jest bardzo dużo - od rozwiązań open source (NDepend, CCCC), po rozbudowane pakiety komercyjne. Semantic Design jest firmą wyspecjalizowaną w tworzeniu narzędzi do automatycznej analizy kodu (m.in. Ada, C++, C#, Java). Firma rozbudowała koncepcję podstawowych metryk, wprowadzając kilkanaście różnych wskaźników złożoności kodu. Warto dodać, że oprócz aplikacji firma oferuje także wyczerpującą dokumentację, która pozwala zorientować się, jak należy analizować wyliczone wskaźniki. Analizując kod rozwiązania Semantic Design generują także graf wywołań - jednak w przypadku większych systemów jest on po prostu nieczytelny (brakuje wygodnej "przeglądarki").

Drugim typem aplikacji związanych z automatyczną analizą kodu są systemy, które bazują na tzw. dobrych praktykach. Mogą one np. sprawdzać zgodność kodu z przyjętym standardem nazewnictwa, czy też wykrywać w kodzie niebezpieczne i/lub nieoptymalne konstrukcje.

BoundsChecker firmy Compuware pozwala automatycznie odnaleźć w kodzie C++ konstrukcje, które mogą stać się przyczyną przepełnienia bufora, np. na skutek nieprawidłowej obsługi stosu lub sterty, albo braku instrukcji zwalniającej zasoby. Pakiet wyposażono w olbrzymią bazę "często popełnianych błędów" związanych z wywoływaniem API Windows. Podobne rozwiązanie oferuje CodeWizard firmy ParaSoft.

W ramach nowszych wersji pakietu Borland Together, oprócz modułu do modelowania, można także znaleźć mechanizmy pozwalające badać jakość kodu. Automatyczny moduł QA wskazuje te fragmenty kodu, które nie odpowiadają zaprogramowanym w nim przez administratora "najlepszym praktykom". Firma Instantions oferuje z kolei CodePro Advisor, będący bazą "dobrych praktyk" kodowania w Javie. Ten sam pakiet może być równie dobrze wykorzystany jako baza wzorców do wstawiania do kodu pisanego ręcznie.

Z pokryciem lub bez

CCCC wykonuje analizę wszystkich plików podanych jako parametry wywołania, a wyniki zapisuje w domyślnym pliku HTML.

CCCC wykonuje analizę wszystkich plików podanych jako parametry wywołania, a wyniki zapisuje w domyślnym pliku HTML.

Oprócz zgodności z najlepszymi praktykami, istotnym problemem związanym z jakością kodu jest tzw. pokrycie testami. Przetestowanie w stu procentach kodu aplikacji, zwłaszcza większej, to przy obecnym tempie pisania nowych funkcji raczej marzenie niż rzeczywistość. Podstawowym ograniczeniem jest czas, ale w dużej mierze także i koszty. Z konieczności więc testuje się przede wszystkim funkcje istotne, a także te, które są najczęściej wykorzystywane.

Wiedza o tym, jaki odsetek linii kodu czy też funkcji został poddany testom określonego rodzaju daje wprawdzie zgrubny, ale zgodny ze zdrowym rozsądkiem, obraz jakości oprogramowania. W szczególności warto wiedzieć, jaki procent wybranych modułów jest naprawdę testowany w sposób automatyczny. Może warto dodać kilka procedur testowych?

Należy pamiętać, że narzędzia, które wskazują pokrycie testami, analizują, jaka część instrukcji jest wykonywana w ramach kolejnych testów. Nie analizują (zwykle) instrukcji warunkowych czy różnych parametrów wejściowych wpływających na kolejność wykonania kodu. Współczynnik pokrycia nie jest więc absolutną miarą jakości, a jego wysokość należy traktować raczej orientacyjnie. Dobrym przykładem narzędzia analizującego pokrycie testami jUnit/nUnit jest Clover (firmy Cenqua) - dostępny w wersji Javy i .NET. Potrafi też wskazać ścieżkę przebiegu programu podczas wykonywania testu.

Doradca, nie prorok

Wszystkie wskaźniki liczbowe oraz "rady" systemów do automatycznej weryfikacji kodu powinno się z zasady traktować z dużą ostrożnością. To, że według np. wskaźnika CC pewna funkcja jest "zbyt złożona" oznacza zwykle, że występuje w niej dużo instrukcji warunkowych. Nie musi to wcale oznaczać, że kod jest źle napisany - być może np. fragment ten odzwierciedla skomplikowany algorytm i z natury rzeczy musi być rozbudowany. Co więcej, rozbicie takiego fragmentu kodu na kilka osobnych pakietów wcale nie musi poprawiać czytelności kodu. To programista - kierując się zdrowym rozsądkiem - powinien ostatecznie weryfikować kod. Automat powinien jedynie pełnić rolę doradcy.

Aplikacji wspomagających utrzymanie kodu w należytym stanie jakościowym nie należy również traktować jako panaceum na problemy z jakością. To tylko narzędzia. Gdyby były one w stanie samodzielnie rozwiązać problem jakości kodu, większość komercyjnych aplikacji byłaby wolna od błędów, a utrzymanie systemów byłoby dziecinną igraszką. Wszyscy wiemy, że jest inaczej. Narzędzia są więc jednym ze składników sukcesu, i to być może nie najważniejszym. Jakość, choć nieraz trudno się z tym pogodzić, zależy w praktyce od czasu przeznaczonego na testy, od kwalifikacji i postawy programistów, a także umiejętności organizacyjnych i zaangażowania kierownictwa projektu.

Potęga refleksji

Technologia .NET Microsoftu ma bardzo rozbudowane mechanizmy refleksji i metadanych, dzięki którym pakiet .NET jest obiektem "samoopisujcym się". Metadane zawierają typy, sygnatury metod, dodatkowe związki opisane atrybutami itp., w wyniku czego stosunkowo łatwo można analizować samą strukturę aplikacji, nie zagłębiając się w sam kod.

Na tej zasadzie działa narzędzie FxCop, które bada zgodność konkretnego pakietu .NET z założeniami określającymi "dobre zwyczaje". Jego możliwości obejmują sprawdzanie m.in. tego, czy wyjątki są prawidłowo obsługiwane, czy nazewnictwo jest zgodne z przyjętymi konwencjami, czy istnieją furtki pozwalające zmienić sposób przekazywania parametrów itp. Za jego pomocą można także sprawdzić zgodność pakietu z .NET Common Language Specification.

Podobne mechanizmy mają być także dostępne w Javie. W JDK 1.5 będą dostępne rozbudowane mechanizmy opisu kodu w postaci metadanych, atrybuty itp. Jest więc tylko kwestią czasu, by również Java doczekała się większej popularyzacji narzędzi do analizy struktury na podstawie tylko kodu skompilowanej aplikacji.

Elastyczne reguły

Ciekawym rozwiązaniem z dziedziny badania jakości kodu jest czeski program Code Analyzer (obecnie w wersji beta), przeznaczony do analizy kodu w C#. Autor programu bardzo dobrze opisał mechanizm "dodawania" do aplikacji własnych zestawów reguł. Code Analyzer pozwala m.in. na to, by oddzielny zestaw "najlepszych praktyk" przypisać każdemu projektowi. Oczywiście wraz z pakietem dostępne są także gotowe wzorce.


Zobacz również