Złożoność cyklomatyczna w rollout gating i canary strategy
Summary
Złożoność cyklomatyczna mierzy ścieżki w kodzie i decyzje branching. Większość zespołów ignoruje deltę złożoności przy deploymencie. Traktuj to jako input do canary pacing, cross-referenced z test delta. Complexity signal przewiduje risky rollout zanim error-rate SLO to zobacze i problem się pojawi.
Złożoność cyklomatyczna: od bramy CI do gating canary przy rollout
Złożoność cyklomatyczna liczy niezależne ścieżki w funkcji. NIST rekomenduje pułap na poziomie 10 na funkcję; większość zespołów egzekwuje coś zbliżonego w CI. Co ta liczba nie mówi, to które ze ścieżek faktycznie będą obsługiwane przez kanaryjkę zaraz po wysłaniu do 5% ruchu produkcyjnego (i ta luka to tam gdzie zaczyna się większość incydentów rollout). Potraktuj złożoność cyklomatyczną jako wejście do ryzyka rollout, nie jako regułę lint, a ta liczba zaczyna się opłacać.
Co mierzy złożoność cyklomatyczna
Metrika McCabe'a to graf: węzły, krawędzie, komponenty spójne. Każde if, for, while, case i operator boolowski dodaje ścieżkę. Funkcja ze złożonością 25 ma co najmniej 25 liniowo niezależnych ścieżek. To nie opinia: to matematyka testowania ścieżek bazowych (basis path testing), i wyznacza ona dolną granicę liczby przypadków testowych potrzebnych do pokrycia funkcji w całości. Rozbiórkę od Sourcegraph zawiera pełne progi ryzyka: 1-10 niskie, 11-20 umiarkowane, 21-50 wysokie, 50+ to wskaźnik wymagający rozmowy.
Większość zespołów tego progu nie osiąga. Narzędzia Coverage raportują pokrycie linii, nie pokrycie ścieżek, więc funkcja może wykazać 90% pokrycie podczas gdy połowa jej branchy nigdy się nie wykonają w CI. To ta część, którą nikt nie wpisuje w szablon PR, i to powód dla którego testy przeszły oraz to jest bezpieczne do wysłania to dwa różne twierdzenia, ale traktuje się je jak jedno.
Mamy ich wystarczająco dużo, aby dostrzegać wzór w post-mortemach: przegląd incydentu zawsze ma linię dla spalenia error budżetu i linię dla czasu detekcji. Prawie nigdy nie ma linii dla tego, jak wyglądała złożoność zmienionej funkcji zanim diff wylądował. To luka w dokumentacji, nie tylko w narzędziach. Ten brak podważa naszą zdolność do predykcji w rollout.

Dlaczego płaska liczba złożoności ukrywa faktyczny blast radius
O tym każdy dashboard złożoności się myli: dwie funkcje mogą mieć identyczny wynik złożoności cyklomatycznej i niesamowicie różne ryzyko. Instrukcja switch z 20 gałęziami kierującymi do dobrze przetestowanych handlerów to nie ten sam zwierzak co funkcja z czterema oddzielonymi kieszeniami zagnieżdżonych warunków rozrzuconych przez 80 linii, każda trzy poziomy głęboko. CodeScene nazywa drugi kształt bumpy road, co oznacza wyboista droga, i nazwa jest dokładna. To głębokość zagnieżdżenia, nie surowa liczba gałęzi, która obciąża pamięć pracującą i ukrywa edge case'a, o którym nikt nie pomyślał, aby przetestować. Artykuł od CodeScene przechodzi przez porównanie w szczegółach i mapuje się czysto na to, co widzimy w danych rollout.
To ważne dla nas konkretnie, bo kanaryjka nie pada na średnią złożoność. Pada na jedną wyboistą funkcję, którą dotknął ten diff, o 2 nad ranem, pod obciążeniem które nikt nie load-testował. W post-mortemach które przejrzeliśmy z zespołami platform, wzór się powtarza: funkcja pierwotnej przyczyny prawie nigdy nie ma najwyższego wyniku złożoności w repo. Ma najwyższą deltę złożoności w diffie który został wysłany. To inny sygnał, i prawie nikt go nie instrumentuje.
Dashboard, który mówi ci że złożoność codebasy ma trend spadkowy, to nie ten sam dashboard, który mówi ci że ten rollout, teraz, dotknął funkcję która właśnie podniosła o trzy nowe gałęzie zagnieżdżone. Jeden to raport zdrowia który czytasz raz na kwartał. Drugi to brama którą faktycznie chciałbyś w pipelineie deploymentu, siedząc obok SLO check, nie piętrzone w oddzielnym narzędziu do analizy statycznej które nikt nie otwiera podczas incydentu.
Rollout oparte na ringach i procentach już zakładają że niektóre dify są bardziej ryzykowne niż inne, to cały premise kanaryjki. Co większość pipelineów nie robi, to pozwolenie aby kształt złożoności diffie sam informował jak szybko ta kanaryjka powinna się poszerzać. Traktuje się to jako problem code review, a potem zapomina się o tym w momencie gdy PR się merge'a.
Brama CI na poziomie 10, którą ustawia każdy, i dlaczego nie rusza igłę
Standardowa rada, uciskaj złożoność cyklomatyczną poniżej 10 i zawalaj build powyżej tego, to step skip który prawie każdy tech blog rekomenduje i prawie nikt nie waliduje przeciw rzeczywistym danym incydentów. To nie jest źle, dokładnie. To jest niekompletne w taki sposób który pozwala zespołom wierzyć że obsłużyli ryzyko, gdy faktycznie je tylko przesunęli.
Dwa tryby awarii, oba częste w zespołach z którymi rozmawialiśmy. Po pierwsze: granie na liczbach. Extract Method to podręcznikowy fix, i działa, złożoność cyklomatyczna na funkcję spada. Ale jeśli ekstrakcja nie zmniejsza faktycznej liczby decyzji, tylko ją relokuje przez trzy funkcje zamiast jednej, złożoność systemu się nie zmienia. Brama robi green. Blast radius się nie zmniejsza, robi się po prostu trudniejszy do zobaczenia w single diff view. Po drugie: ignorowanie kształtu. Płaska próg traktuje dispatcher z 12 gałęziami i 12-ścieżkową wyboistą drogę jako równie ryzykowne. Nie są. Dispatcher to prawdopodobnie ok, to mechaniczne routing. Wyboista droga to tam gdzie żyje twój następny rollback, bo reviewer przestał śledzić state trzy poziomy głębi w.
AI coding agents zmieniają to na gorzej zanim będzie lepiej. Devin i podobne autonomous agenty wysyłają PR szybko, a szybko często oznacza dodanie gałęzi zamiast refaktoryzacji tego które już są. To ścieżka najmniejszego oporu dla modelu optymalizującego dla passing test suite, nie dla cognitive load reviewera. Jeśli twój zespół merge'a AI-authored dify w volume, delta złożoności na PR to metrika którą chciałbyś na dashboardzie zanim się stanie finding z post-mortemów, nie po.

Co delta złożoności faktycznie mówi ci o burden testowania
Zapomnij na chwilę o absolute score. Liczba która przewiduje incident risk to zmiana złożoności wprowadzona przez single diff, cross-referenced przeciw czy testy które dotykają ten diff faktycznie rosły żeby to dopasować. Funkcja która idzie ze złożoności 8 do 19 w jednym PR ma, przez basis path testing math wyżej, mniej więcej podwojoną minimalną wymaganą liczbę testów. Jeśli PR dodał dwa testy, wysłałeś lukę w pokryciu przebraną za passing CI run. Ta luka się nie pojawia aż kanaryjka nie trafi traffic slice, którą 5% która executes untested branch, i wtedy to jest incident, nie code review komentarz siedzący unresolved w PR thread.
To instrumentation luka którą AI Pilot upstreamapi zbudowany żeby zamknąć na stronie rollout: gate procent kanaryjki i hold time na delcie złożoności wysłanego diffie, cross-checked przeciwko jego test delta, nie tylko na downstream error-rate SLO. Error-rate SLO mówi ci że coś już się złamało. Complexity-delta gate mówi ci że diff był bardziej skłonny aby coś złamać zanim serve live traffic, co to jedynego punktu gdzie ta informacja jest wciąż actionable. Oto kod który brama by funkcjonował.
function canaryStep(diff: DiffMetrics): CanaryDecision {
const complexityRisk = diff.complexityDelta / Math.max(diff.testDelta, 1);
if (complexityRisk > 3 && diff.slo.errorBudgetBurn > 0.1) {
return { action: "hold", trafficPct: diff.currentTrafficPct };
}
if (complexityRisk > 3) {
return { action: "extend_bake_time", bakeMinutes: 45 };
}
return { action: "advance", trafficPct: diff.currentTrafficPct + 10 };
}Czy badania w ogóle się na tym zgadzają
Nie, i to warto jasno powiedzieć. Część practitioner research stawiać się mocno przeciwko złożoności cyklomatycznej jako defect predictor w ogóle, argumentując że zespoły które optymalizują dla niższego score, często relokują złożoność gdzieś mniej widocznym. Krytyka od GetDX robi ten case i wskazuje zespoły w kierunku developer-experience metrics zamiast tego. Nie myślimy że ten argument kills metrika, myślimy że kills metrika używana sama, jako static repo-wide score, disconnected od diffie i rollout do którego jest attached.
Jak faktycznie byśmy gated canary na złożoności, nie tylko na error rate
Trzy rzeczy, w kolejności jak wiele friction dodają do PR. Po pierwsze, compute complexity delta per diff, nie per repo. Repo-wide averages ukrywają tę jedną funkcję która matters ten tydzień. Delta per PR jest cheap aby compute w większości static analysis tools, i to liczba która correlates z tym co faktycznie się łamie w następne 48 godzin.
Po drugie, cross-reference przeciwko test delta, nie test count. Funkcja z 40 testami i complexity jump ze 8 do 19 z zero nowymi testami to bigger risk niż fresh function ze złożonością 15 i matching coverage od day one.
Po trzecie, feed ratio do rollout pacing, nie tylko do merge approval. High complexity-delta diff nie musi być blocked przy review, plenty of legitnie complex domain logic takie jak state machines czy protocol parsers zawsze będzie score wysoko. Potrzebuje wolniejszą kanayjkę i krótszą error-budget leash, co jest rollout decision, nie code review decision.
Napisy runbook entry zanim rollout, nie po incydencie. Jeśli on-call engineer otwierający incident channel o 3 nad ranem musi reverse-engineer dlaczego clean PR właśnie spalił error budget, dokumentacja zawiodła zanim kod. Jedna-liniowy note w deploy log (complexity delta 11, test delta 1, held at 20 procent) costs nic do write i saves pierwsze piętnaście minut każdego incydent review które następuje.

Czy to warte śledzenia, czy tylko kolejny number na dashboardzie
Zależy całkowicie od tego gdzie go przypiąć. Złożoność cyklomatyczna jako repo-health trend line to głównie decoration, ładnie na quarterly slide, bezużyteczne o 2 nad ranem. Złożoność cyklomatyczna jako per-diff delta, podana do canary pacing i cross-checked przeciwko test growth, jest jednym z tańszych leading indicators które znaleźliśmy dla sygnału że ten rollout będzie page somebody.
Post-mortem będzie pytać co SLO budget wyglądał zanim rollback. Coraz częściej, nasz też pyta co complexity delta wyglądał na diffie która wysłana. Worth dodania tego pytania do twojego własnego runbook zanim incident narzuci konwersację, nie podczas retro gdy odpowiedź to wzruszenie i obietnica dodamy lepsze testy następnym razem.