Złożoność cyklomatyczna: co budować i refaktorować
Summary
Złożoność cyklomatyczna to metryka z 1976 roku, która zlicza niezależne ścieżki wykonania w funkcji. Wyniki powyżej 10 wymagają testów, powyżej 20 refaktoryzacji przed nowymi featurami, powyżej 50 przepisania od zera. Używana jako mapa decyzyjna, a nie ocena jakości, daje kolejkę priorytetów: funkcje z wysokim CC, których dotknie twój następny feature, to twój prawdziwy plan sprintu.
Otwierasz projekt o 22:00. Wiesz, co dalej chcesz shipować. Po dwudziestu minutach debugujesz coś zupełnie innego. Funkcja, którą edytujesz, ma 300 linii, obsługuje sześć różnych przypadków i nikt, łącznie z tobą, nie pamięta, skąd się wzięła. Złożoność cyklomatyczna to metryka, która wykryłaby tę funkcję wiele miesięcy temu. Zlicza niezależne ścieżki wykonania w kodzie. Funkcje powyżej CC 10 wymagają testów, zanim je dotkniesz. Powyżej CC 20 refaktorujesz przed dodaniem czegokolwiek nowego. Powyżej CC 50 przepisujesz od zera. To drzewo decyzyjne. Reszta artykułu pokazuje, jak uruchomić scan na twoim projekcie i zamienić wynik w plan sprintu.
Co tak naprawdę mierzy złożoność cyklomatyczna
Thomas McCabe opublikował tę metrykę w 1976 roku. Wzór to M = E - N + 2P, gdzie E to krawędzie, N to węzły, a P to spójne składowe w grafie przepływu sterowania funkcji. W praktyce: zaczynasz od 1 i dodajesz 1 za każdy if, else if, for, while, case, catch, && lub ||. To jest wynik.
Funkcja bez rozgałęzień ma CC = 1. Funkcja sprawdzająca dziesięć warunków, iterująca po liście i obsługująca trzy typy błędów osiągnie CC 15-20. Funkcja budowana przez sześciu dewów, którzy przez dwa lata łatali sobie nawzajem logikę, może dobić do CC 60.
Metryka nie mierzy wszystkiego. Nie mierzy głębokości zagnieżdżenia, jakości nazewnictwa ani klarowności abstrakcji. Funkcja może mieć niski CC i nadal być trudna do czytania. Ale funkcja z CC powyżej 25 jest prawie zawsze trudna do czytania. Korelacja w tym kierunku jest na tyle spójna, żeby służyć jako filtr priorytetów, nie jako ostateczna ocena jakości kodu.

Progi, które decydują o twoim następnym kroku
Większość wytycznych i narzędzi do analizy statycznej zbiega się w podobnych przedziałach:
CC 1-10 (akceptowalne): shipujesz
CC 11-20 (wymaga uwagi): dodajesz testy przed modyfikacją
CC 21-50 (wysokie ryzyko): refaktorujesz przed dodaniem nowych funkcji
CC >50 (niemożliwe do przetestowania): przepisujesz, nie łatasz
Pierwotna rekomendacja NIST to sufit 10 dla funkcji. W praktyce większość inżynierów traktuje 15 jako realistyczny miękki limit dla kodu produkcyjnego, a wszystko powyżej 20 jako blokadę dla nowych prac dotykających tej funkcji.
Tabela to twoje drzewo decyzyjne dla tego, co budować dalej. Kiedy backlog jest pełny i nie możesz zdecydować, czy dodać feature, czy naprawić istniejący moduł, najpierw uruchom scan złożoności. Funkcje siedzące powyżej 20 to twój obowiązkowy stop przed wszystkim, co nowe ich dotknie. To nie jest sugestia, to jest dane wejściowe do planowania.
Jak uruchomić pierwszą analizę w 5 minut
W każdym ekosystemie językowym jest gotowe narzędzie CLI. Nie potrzebujesz dashboardu SaaS do tego pierwszego kroku.
Python (Radon):
pip install radon
radon cc -s -a ./srcJavaScript i TypeScript (ESLint):
{ "rules": { "complexity": ["error", 10] } }Następnie uruchamiasz eslint ./src --ext .ts,.js w pipeline.
Go (gocyclo):
go install github.com/fzipp/gocyclo/cmd/gocyclo@latest
gocyclo -over 10 ./...Java i Kotlin (PMD):
pmd check -d ./src -R rulesets/java/quickstart.xml | grep CyclomaticComplexityWynik to lista każdej funkcji posortowana według wyniku CC. Szukasz dziesięciu najwyżej ocenionych funkcji w projekcie. Zapisz tę listę. To twoja kolejka priorytetów na następny sprint. Nie potrzeba nic więcej do podjęcia pierwszej decyzji.
Jeśli twój projekt ma mniej niż 2000 linii, radon albo gocyclo skończy w kilka sekund. Jeśli przekracza 50 000 linii, rozważ uruchomienie scanu tylko na katalogu, w którym pracujesz aktualnie, żeby wynik był natychmiastowy i działał jako nawyk roboczy, a nie jednorazowy audyt.
Pierwszy scan ma jeden cel: znaleźć top 10 funkcji. Nie analizuj całego raportu przy pierwszym uruchomieniu. Zapisz listę, posortuj malejąco, zaznacz te, których dotknie twój następny feature. Wszystko inne możesz odłożyć na później.
Jak czytać raport złożoności jako mapę decyzyjną
Funkcje z wysokim CC skupiają się w przewidywalnych miejscach. W API rekomendacyjnym, liczącym około 8000 linii, uruchomienie radon cc -s wykazało trzy funkcje z CC powyżej 30. Wszystkie trzy znajdowały się w warstwie normalizacji danych. Ta warstwa była łatana przyrostowo przez sześć miesięcy, za każdym razem dla nieco innego edge case ze źródła danych. Nikt nigdy nie spojrzał na skumulowane szkody.
Każdy nowy feature dotykający warstwy normalizacji zajmował 40% dłużej do shipowania. Dwa dni przed kolejnym sprintem poświęciliśmy na refaktoryzację tych trzech funkcji, obniżając wyniki CC z 32, 28 i 31 do 7, 5 i 8. Następny sprint był najszybszy od czterech miesięcy.
Trzy funkcje. Dwa dni. Cztery miesiące opóźnień, wyjaśnione.
Wzorzec jest spójny w różnych bazach kodu: około 10-15% funkcji przechowuje 70-80% złożoności w każdym organicznie rozwijającym się projekcie. Ta koncentracja to twoja prawdziwa mapa. Przed zacommitowaniem się do nowego feature'a uruchom scan. Jeśli twój feature dotyka funkcji powyżej CC 15, masz konkretny wybór: shipować go teraz wolno w ryzykowny kod, albo poświęcić dwa dni na refaktoryzację i shipować go porządnie w następnym tygodniu. Matematyka zwykle faworyzuje refaktoryzację.
W side projektach ma to większe znaczenie niż w ustawieniach zespołowych. Nie możesz przekazać pogmatwanej funkcji współpracownikowi. Pełny koszt kognitywny ponownego zrozumienia kodu CC 30 spada na ciebie, zazwyczaj o 22:00, sześć miesięcy po tym, jak go napisałeś. Mając to w głowie, wybór między "dodaję feature" a "najpierw czyszczę" przestaje być subiektywny.

Kiedy wysoka złożoność jest uzasadniona
Nie każda funkcja z wysokim CC jest problemem.
Maszyna stanów płatności obsługująca 12 stanów transakcji naprawdę musi obsługiwać 12 stanów. Parser przetwarzający 15 reguł gramatycznych ma 15 prawdziwych przypadków. Spłaszczenie ich do 15 helperów z CC 1 każdy nie redukuje złożoności, tylko ją rozprasza po plikach, które są teraz trudniejsze w nawigacji razem.
Użyteczne pytanie to nie "czy CC jest zbyt wysokie?", ale "czy CC jest wyższe, niż wymaga domena?" Funkcja routingu obsługująca 15 wzorców URL z CC = 18 jest prawdopodobnie właściwa. Funkcja aktualizacji profilu użytkownika z CC = 18, która urosła przez akumulowane łatki dla edge case'ów, których nikt nie planował, to inny problem.
Trzymaj CC poniżej 15 na krytycznych ścieżkach. Pozwól mu rosnąć wyżej w naprawdę złożonych domenach i dokumentuj powód inline. Funkcje, które później cię zaskakują, to zawsze te bez uzasadnienia dla swojej złożoności poza akumulowanymi poprawkami. To co kosztuje, to nie wysokie CC samo w sobie, lecz wysokie CC bez wyjaśnienia dlaczego musi być wysokie.
Dodaj kontrolę złożoności do CI i przestań debugować na ślepo
Uruchomienie scanu raz jest przydatne. Podłączenie go do CI to to, co naprawdę zmienia zachowanie w czasie.
Praktyczna konfiguracja: ustaw próg w konfiguracji lintera, niech build nie przejdzie, jeśli jakakolwiek nowa funkcja go przekroczy, i zapisuj raport złożoności jako artefakt CI przy każdym uruchomieniu. Lista istnieje przy każdym buildzie, bez żeby ktokolwiek pamiętał o jej generowaniu.
Dla GitHub Actions z projektem Python:
- name: Sprawdź złożoność
run: |
pip install radon
radon cc -n C -s ./src
if [ $? -ne 0 ]; then exit 1; fiDla JavaScript z ESLint już w pipeline, dodaj "complexity": ["error", 12] do reguł. Każdy PR wypychający funkcję powyżej 12 automatycznie nie przechodzi.
Praktyczny standard zespołowy: CC <= 15 jako twarda blokada CI, CC 11-15 jako ostrzeżenie wymagające komentarza inline wyjaśniającego powód biznesowy. Ten drugi poziom wymusza rozmowę bez zamieniania każdego code review w negocjację wyjątków od progu. W projekcie solo to samo ostrzeżenie działa jako punkt refleksji przed mergem.
Trzy narzędzia do bieżącego śledzenia złożoności
Narzędzia CLI powyżej dają jednorazową migawkę. Do bieżącej widoczności na poziomie projektu wyróżniają się trzy opcje:
SonarQube to najbardziej kompletna opcja do śledzenia złożoności na poziomie projektu. Edycja Community obejmuje złożoność cyklomatyczną, złożoność kognitywną i pokrycie testami w jednym dashboardzie. Quality gates mogą blokować merge, jeśli nowy kod przekracza próg złożoności. To narzędzie, po które sięgasz najpierw, gdy projekt ma więcej niż dwóch współtwórców i prawdziwy proces review.
CodeScene koreluje wyniki złożoności z historią commitów, wskazując funkcje zarówno złożone, jak i często zmieniane. Te przecięcia to twój prawdziwy dług techniczny, nie tylko trudne do zrozumienia, ale aktywnie kosztujące czas każdego sprintu. Jeśli musisz przedstawić argument za sprintem refaktoryzacji w roadmapie zespołu, wynik CodeScene jest tym argumentem.
Code Climate Quality to lżejsza opcja, odpowiednia dla projektów solo i repozytoriów open-source. Integracja z GitHub działa od razu po zainstalowaniu, ustawienia domyślne są sensowne, a bezpłatny tier dla publicznych repozytoriów jest użyteczny. Jeśli zarządzasz side projektem i chcesz śledzić dryfowanie złożoności bez stawiania własnej infrastruktury, zacznij tutaj.
Jaka decyzja naprawdę wymusza złożoność cyklomatyczna
Złożoność cyklomatyczna nie mówi ci, czy twój kod jest dobry. Mówi ci, gdzie ryzyko jest skoncentrowane. To jest bardziej użyteczna informacja.
Uruchom scan na bieżącym projekcie przed następnym sprintem. Spójrz na dziesięć najwyżej ocenionych funkcji. Zapytaj, które z nich dotknie twój następny feature. Jeśli odpowiedź brzmi "trzy z nich, wszystkie powyżej CC 20", twój plan sprintu jest jasny: sprowadź te trzy funkcje poniżej CC 10, potem shipuj feature. Zajmie to połowę czasu, który by zajął w przeciwnym razie.
Wniosek dla builderów: jeśli szukasz następnego side projektu i pracujesz w prawdziwych bazach kodu, twój raport złożoności jest specyfikacją. Najbardziej złożony moduł blokujący velocity, ten złożony i dotykany ciągle, to narzędzie warte zbudowania. Lekki dashboard złożoności korelujący wyniki CC z danymi git blame i częstotliwością zmienianych plików, wyceniony dla dwuosobowego zespołu, a nie dla kontrastu enterprise, to projekt, który jeszcze w pełni nie istnieje. Luka w narzędziach jest realna.
To nie pitch. To wzorzec: narzędzia, których chcesz, a nie możesz znaleźć, to często side projekty najbardziej warte shipowania.