W procesach QA najwięcej kosztują nie te fragmenty kodu, które są długie, ale te, które mają zbyt wiele dróg wykonania. Złożoność cyklomatyczna pomaga ocenić, ile niezależnych ścieżek ma funkcja i jak bardzo utrudnia projektowanie testów, utrzymanie regresji oraz refaktoryzację. W tym artykule pokazuję, jak czytać wynik, kiedy uznać go za sygnał ostrzegawczy i jak używać tej metryki bez ślepego zaufania do liczb.
Co ta metryka mówi o kodzie i testach
- Mierzy liczbę niezależnych ścieżek wykonania w funkcji, a nie ogólną „ładność” kodu.
- Im wyższy wynik, tym trudniej zaplanować testy i bezpiecznie wprowadzać zmiany.
- W QA używam jej do priorytetyzacji testów, refaktoryzacji i quality gate w CI.
- Nie zastępuje pokrycia testami ani oceny logiki biznesowej.
- W praktyce próg około 10 bywa dobrym punktem startowym, ale zawsze trzeba brać pod uwagę kontekst kodu.
Jak ta metryka opisuje ścieżki wykonania kodu
W wersji grafowej liczy się ją na podstawie grafu przepływu sterowania: v(G) = E - N + 2P, gdzie E to liczba krawędzi, N liczba węzłów, a P liczba spójnych składowych. W praktyce większość narzędzi upraszcza to do reguły „1 plus liczba rozgałęzień”, więc każda instrukcja warunkowa, pętla czy dodatkowa gałąź logiczna podnosi wynik.
Ja traktuję tę metrykę przede wszystkim jako miarę testowalności, nie „ładności” kodu. W testach białoskrzynkowych mówi ona, ile niezależnych ścieżek warto przemyśleć, żeby nie wpaść w pułapkę jednego happy patha, który wygląda dobrze tylko na papierze. To właśnie dlatego dobrze sprawdza się w QA: szybko pokazuje, gdzie logika zaczyna rozgałęziać się bardziej, niż zespół widzi to na pierwszy rzut oka.
Ważny detal: przy pętlach i bardziej złożonych strukturach liczba teoretycznych przebiegów może być bardzo duża, więc nie chodzi o mechaniczne „odfajkowanie wszystkiego”, tylko o rozsądne pokrycie głównych dróg działania. Gdy to rozumiemy, łatwiej przejść do pytania, jak interpretować sam wynik bez nadmiernej wiary w jedną liczbę.
Jak złożoność cyklomatyczna wpływa na plan testów
W praktyce używam wyniku jako sygnału do planowania testów białoskrzynkowych i regresji. Nie chodzi o to, żeby każdą funkcję mierzyć tym samym kijem, tylko o to, by od razu wiedzieć, gdzie jeden brakujący scenariusz może zostawić dziurę w pokryciu logiki.
| Wynik | Jak to czytam | Co robię w QA |
|---|---|---|
| 1-4 | Funkcja zwykle jest prosta, a liczba ścieżek niewielka. | Sprawdzam podstawowe przypadki i granice wejścia. |
| 5-10 | Logika zaczyna wymagać większej dyscypliny w testach. | Rozpisuję ścieżki, wyjątkowe wejścia i przypadki brzegowe. |
| 11-20 | Pojawia się wysoki sygnał ryzyka dla utrzymania i regresji. | Dokładam refaktoryzację do planu i zwiększam nacisk na review. |
| Powyżej 20 | To zwykle już bardzo złożona logika, chyba że kod z natury jest wielogałęziowy. | Traktuję funkcję jako kandydata do przebudowy albo mocniejszego podziału. |
Tu nie ma jednego prawa dla wszystkich zespołów. NIST wskazuje 10 jako rozsądny punkt startowy, a wyższe limity mają sens tylko tam, gdzie organizacja naprawdę kontroluje proces, ma dojrzałe przeglądy i solidny plan testów. Z kolei w jednym z narzędzi Microsoftu ostrzeżenie potrafi pojawić się dopiero przy 25, ale ja nie czekałbym z reakcją aż tak długo, jeśli patrzę na pojedynczą funkcję w aktywnym projekcie.
Najważniejszy wniosek jest prosty: liczba sama w sobie niczego nie naprawia. Dopiero w połączeniu z kontekstem kodu i praktyką QA pokazuje, gdzie warto wejść głębiej.

Jak przekładam wynik na decyzje QA
Buduję przypadki wokół niezależnych ścieżek
Jeżeli funkcja ma kilka rozgałęzień, nie piszę testów „na czuja”, tylko rozbijam logikę na drogi wykonania. Dla każdej ścieżki sprawdzam nie tylko happy path, ale też dane puste, wartości graniczne, błędy walidacji i sytuacje, w których warunek powinien przejść w przeciwną stronę. Przy pętlach dokładam przypadki typu: zero iteracji, jedna iteracja i kilka iteracji, bo właśnie tam często siedzą regresje, których nie widać w podstawowym scenariuszu.
Priorytetyzuję testy tam, gdzie zmiana jest najbardziej ryzykowna
Wysoki wynik sam w sobie nie musi oznaczać katastrofy, ale jeśli taka funkcja jest też często zmieniana, dotyka krytycznej ścieżki biznesowej albo ma słabe pokrycie testami, staje się dla mnie pierwszym kandydatem do dokładniejszej kontroli. W praktyce najbardziej lubię połączenie: wysoka złożoność, niedawne zmiany i obszar, w którym błąd kosztuje użytkownika albo zespół najwięcej. Taki miks zwykle mówi więcej niż sam numer z narzędzia.
Przeczytaj również: FMEA w QA - Jak zamienić analizę ryzyka w realne działania?
Wpinam metrykę w CI, ale nie robię z niej religii
Najzdrowsze podejście, jakie widzę, to używanie progu na nowym kodzie, a nie karanie całego legacy jednym statycznym limitem. Jeśli nowa funkcja podbija wynik o kilka punktów, traktuję to jako sygnał do review, a nie automatyczny powód do blokady wszystkiego. W dobrze ustawionym pipeline metryka ma pomagać szybciej łapać ryzyko, a nie produkować szum i fałszywe alarmy.
Kiedy już wiem, gdzie szukać, najwięcej problemów znajduję nie w samym wyniku, tylko w błędnej interpretacji. I właśnie tam wchodzą najczęstsze pułapki.
Najczęstsze pułapki, które psują interpretację wyniku
Największy błąd, jaki widzę w zespołach, to utożsamianie tej liczby z jakością całego modułu. Funkcja może mieć niski wynik i nadal być zła biznesowo, a może mieć wyższy wynik i być całkiem w porządku, jeśli rozgałęzienia są naturalne i wynikają z domeny.
| Cel metryki | Co mierzy | Czego nie mierzy | Kiedy jej używam |
|---|---|---|---|
| Złożoność cyklomatyczna | Liczbę ścieżek i rozgałęzień w kodzie | Łatwości czytania i rozumienia | Gdy chcę ocenić testowalność i ryzyko regresji |
| Złożoność poznawcza | To, jak trudno śledzić logikę w głowie | Minimalnej liczby testów potrzebnych do pokrycia ścieżek | Gdy oceniam czytelność i utrzymanie kodu |
Dwie funkcje z tym samym wynikiem mogą być zupełnie różne. Prosty switch i głęboko zagnieżdżone if-y czasem dostają podobny rezultat, ale dla testera i utrzymania kodu nie oznacza to tego samego kosztu. Dlatego nie oceniam tylko numeru, ale też kształt logiki, liczbę poziomów zagnieżdżenia i to, czy rozgałęzienia są naprawdę potrzebne.
- Nie mylę tej metryki z pokryciem testami. 100% coverage nie gwarantuje, że testy dobrze sprawdzają logikę albo błędy na granicach.
- Nie patrzę wyłącznie na cały plik lub klasę. Najcenniejsze są hotspoty na poziomie funkcji, bo tam zwykle siedzi prawdziwe ryzyko.
- Nie demonizuję naturalnych rozgałęzień. Parser, router, reguły cenowe czy maszyna stanów mogą mieć wyższy wynik z uzasadnionych powodów.
- Nie ignoruję wysokiego wyniku tylko dlatego, że kod działa. Działa dziś, ale może być drogi w zmianie jutro.
Jeśli wynik faktycznie przeszkadza, następny krok to redukcja złożoności bez niszczenia logiki biznesowej.
Jak obniżać wynik bez niszczenia logiki biznesowej
Najlepsza refaktoryzacja to taka, która poprawia czytelność i testowalność jednocześnie. Ja zaczynam od prostych ruchów: wydzielenia warunków do nazwanych metod, skrócenia zagnieżdżeń przez guard clauses i oddzielenia walidacji od samej logiki biznesowej. To często daje szybki efekt bez przebudowy całej architektury.
- Wyciągam warunki do nazwanych funkcji. Dzięki temu samo wywołanie mówi, co sprawdzam, zamiast zmuszać do czytania kilku poziomów ifów.
- Rozbijam głębokie zagnieżdżenia. Guard clauses skracają ścieżki i zmniejszają liczbę miejsc, w których trzeba pamiętać o stanie pośrednim.
- Zamieniam rozgałęzienia oparte na danych na mapy lub tabele reguł. To działa szczególnie dobrze tam, gdzie logika przypomina wybór wariantu, a nie prawdziwe przetwarzanie decyzji.
- Rozważam strategię albo polimorfizm. Gdy kod decyduje głównie na podstawie typu obiektu, stanu albo roli, to zwykle lepsze niż długi łańcuch warunków.
- Nie rozbijam kodu mechanicznie. Jeśli jedna złożona struktura jest bardziej czytelna niż pięć sztucznych helperów, zostawiam ją i wzmacniam testy.
Są też sytuacje, w których wysoki wynik nie jest sam w sobie problemem. W kodzie generatorowym, parserach, silnikach reguł czy modułach routingowych rozgałęzienia bywają naturalne, więc wtedy szukam sensownego podziału warstw i lepszych testów, a nie kosmetycznego obniżania licznika. Właśnie dlatego ta metryka ma sens tylko wtedy, gdy czytam ją razem z kontekstem domenowym.
W efekcie ta metryka nie kończy rozmowy o jakości, tylko ją porządkuje.
Co daje zespołowi QA spojrzenie na ścieżki, a nie na sam rozmiar modułu
W codziennym QA ta metryka pomaga mi oddzielić kod, który tylko wygląda na prosty, od kodu, który naprawdę jest prosty do zmiany. Najwięcej zyskują zespoły, które używają jej jako wczesnego sygnału: do selekcji testów, przeglądów kodu i decyzji, czy funkcja powinna zostać rozbita przed kolejnym releasem.
Jeżeli miałbym zostawić jedną praktyczną zasadę, brzmiałaby tak: patrz na wynik funkcji, nie na statystykę całego projektu. Jedna zbyt złożona metoda potrafi generować więcej ryzyka niż dziesiątki prostych klas, a właśnie takie miejsca najczęściej psują regresję, rozciągają review i utrudniają dopinanie sensownych testów.
W praktyce najbardziej opłaca się patrzeć na nią razem z pokryciem testami, częstością zmian i krytycznością biznesową. Dopiero taki zestaw pokazuje, czy kod wymaga zwykłej uwagi, czy już konkretnej przebudowy.