Cyklomatisk komplexitet: bygg eller refaktorera näst?

Summary

Cyklomatisk komplexitet är ett mått från 1976 som räknar oberoende exekveringsvägar i en funktion. Poäng över 10 kräver tester; över 20 refaktorerar du innan du lägger till något nytt; över 50 skriver du om. Används som en beslutskarta ger det dig en prioriteringskö: de högt rankade funktionerna din nästa feature berör är din faktiska sprintplan. Artikeln visar hur du kör scanen, läser resultaten och omvandlar komplexitetsdata till ett shippingbeslut.

Cyklomatisk komplexitet visualiserad som ett kontrollflödesdiagram med färgkodade riskzoner

Cyklomatisk komplexitet: bygg eller refaktorera näst?

Du öppnar kodbasen klockan 22. Du vet vilken feature du vill shippa. Tjugo minuter senare debuggar du något helt orelaterat. Funktionen du redigerar är 300 rader lång, hanterar sex olika scenarion och ingen, inklusive du, minns varför den växte sig sådan.

Cyklomatisk komplexitet (CC) är det mätvärde som hade flaggat den här funktionen månader tidigare. Det räknar oberoende exekveringsvägar i din kod. Funktioner med CC över 10 behöver tester innan du rör dem. Över CC 20 refaktorerar du innan du lägger till något nytt. Över CC 50 skriver du om. Det är beslutsträdet. Resten av den här artikeln visar hur du kör en scan på ditt projekt idag och omvandlar resultaten till en konkret sprintplan.

Vad cyklomatisk komplexitet faktiskt mäter

Thomas McCabe publicerade måttet 1976. Formeln är M = E - N + 2P, där E är kanter, N är noder och P är sammankopplade komponenter i kontrollflödesgrafen för en funktion. I praktiken: börja på 1, lägg till 1 för varje if, else if, for, while, case, catch, && eller ||. Det är poängen.

En funktion utan grenar har CC = 1. En funktion som kontrollerar tio villkor, itererar över en lista och hanterar tre feltyper hamnar runt CC 15-20. En funktion som sex devs har lappat varandras logik i under två år kan nå CC 60.

Måttet fångar inte allt. Det mäter inte nästningsdjup, namnkvalitet eller abstraktionsklarhet. En funktion kan ha lågt CC och ändå vara svårläst. Men en funktion med CC över 25 är nästan alltid svårläst. Korrelationen i den riktningen är tillräckligt konsekvent för att fungera som ett prioriteringsfilter. Det är skillnaden mellan ett mätvärde som ger rätt alarm och ett som skapar brus.

Trösklarna som avgör ditt nästa drag

De flesta stilguider och statiska analysverktyg konvergerar mot liknande intervall:

NIST:s ursprungliga rekommendation var ett tak på 10 per funktion. I praktiken behandlar de flesta devs 15 som en realistisk mjuk gräns för produktionskod, och allt över 20 som en blockerare för nytt arbete som berör den funktionen.

Tabellen är ditt beslutsträd för vad du bygger härnäst. När din backlog är full och du inte kan bestämma dig för att lägga till en feature eller rensa upp i en befintlig modul, kör en komplexitetsscan först. Funktionerna som sitter över 20 är ditt obligatoriska stopp innan något nytt berör dem. Det är inte en känsla, det är ett filter.

Kör din första scan på 5 minuter

Varje stort språkekosystem har ett CLI-verktyg redo. Du behöver ingen SaaS-dashboard för det här första steget.

Python (Radon):

pip install radon
radon cc -s -a ./src

JavaScript / TypeScript (ESLint):

// .eslintrc
{ "rules": { "complexity": ["error", 10] } }

Kör sedan eslint ./src --ext .ts,.js i din pipeline.

Go (gocyclo):

go install github.com/fzipp/gocyclo/cmd/gocyclo@latest
gocyclo -over 10 ./...

Java / Kotlin (PMD):

pmd check -d ./src -R rulesets/java/quickstart.xml | grep CyclomaticComplexity

Utmatningen listar varje funktion sorterad efter CC-poäng. Det du letar efter är de 10 funktionerna med högst poäng i ditt projekt. Spara den listan. Det är din prioriteringskö för nästa sprint. Listan tar tre minuter att generera och kan spara dagar av felsökning nedströms.

Abstrakt visualisering av kodgrenar med komplexitetsheatmap

Läs rapporten som en beslutskarta

Funktioner med hög CC klustrar på förutsägbara ställen. I ett rekommendations-API med ungefär 8 000 rader körde radon cc -s upp tre funktioner med CC över 30. Alla tre låg i datanormaliseringslagret. Det lagret hade lappats inkrementellt under sex månader, varje gång för ett lite annorlunda kantfall i en datakälla. Ingen hade någonsin tittat på den ackumulerade skadan.

Varje ny feature som rörde normaliseringslagret tog 40% längre tid att shippa. Vi spenderade två dagar före nästa sprint på att refaktorera de tre funktionerna och sänkte CC-poängen från 32, 28 och 31 till 7, 5 och 8. Nästa sprint var den snabbaste på fyra månader.

Tre funktioner. Två dagar. Fyra månader av motstånd, förklarat.

Mönstret är konsekvent i kodbaserna: ungefär 10-15% av funktionerna håller 70-80% av komplexiteten i ett organiskt växande projekt. Den koncentrationen är din verkliga roadmap. Innan du sätter dig ner med en ny feature, kör scanen. Om din feature berör en funktion med CC över 15 har du ett konkret val: shippa den nu in i riskabel kod, eller spendera två dagar på att refaktorera och shippa den ordentligt veckan därpå. Matematiken gynnar oftast refaktorering.

För sidoprojekt är det här viktigare än i teammiljöer. Du kan inte lämna en snårig funktion till en kollega. Den fulla kognitiva kostnaden för att förstå CC 30-kod faller på dig, oftast klockan 22, sex månader efter att du skrev den.

Det andra som komplexitetsrapporter gör bra: de ger ett konkret svar när du är paralyserad mellan att lägga till en feature och städa upp först. Scanen tar bort tvetydigheten. Om featuren berör kod med högt CC, städar du först. Om den inte gör det, shippar du. Det är ett beslut, inte en debatt.

När hög komplexitet är rätt beslut

Alla funktioner med högt CC är inte ett problem.

En betalningsstate machine som hanterar 12 transaktionsstatus behöver genuint hantera 12 status. En parser som bearbetar 15 grammatikregler har 15 verkliga fall. Att platta ut dessa i 15 hjälpfunktioner med CC 1 var minskar inte komplexiteten, det distribuerar den över filer som nu är svårare att navigera tillsammans.

Den användbara frågan är inte "är det här CC för högt?" utan "är det här CC högre än domänen kräver?" En routingfunktion som hanterar 15 URL-mönster med CC = 18 är troligtvis lämplig. En profiluppdateringsfunktion med CC = 18 som växte sig stor av ackumulerade lappar för kantfall som ingen planerade för är ett annat problem.

Håll CC under 15 på dina kritiska vägar. Låt det gå högre i genuint komplexa domäner och dokumentera anledningen inline. Funktionerna som förvånar dig senare är alltid de som inte har någon motivering för sin komplexitet utöver ackumulerade fixar.

Dev analyserar kodkvalitetsmätvärden på dubbla skärmar i en mörk arbetsmiljö

Koppla in komplexitetskontroller i CI och sluta debugga i blindo

Att köra scanen manuellt en gång är användbart. Att koppla den till CI är vad som faktiskt förändrar beteendet över tid. En ny dev som öppnar en PR och ser bygget flagga för CC förstår regeln direkt, utan att behöva fråga teamet om osynliga normer.

Den praktiska konfigurationen: sätt ett tröskelvärde i din linterkonfiguration, misslyckas bygget om någon ny funktion överstiger det, och spara komplexitetsrapporten som en CI-artefakt vid varje körning. Listan finns vid varje bygge, utan att någon behöver komma ihåg att generera den.

För GitHub Actions med ett Python-projekt:

- name: Komplexitetskontroll
  run: |
    pip install radon
    radon cc -n C -s ./src
    if [ $? -ne 0 ]; then exit 1; fi

För JavaScript med ESLint redan i pipelinen, lägg till "complexity": ["error", 12] i dina regler. Varje PR som pushar en funktion över 12 misslyckas automatiskt.

En praktisk teamstandard: CC <= 15 som ett hårt CI-block, CC 11-15 som en varning som kräver en inline-kommentar som förklarar affärsanledningen. Det andra lagret tvingar fram konversationen utan att göra varje kodgranskning till en förhandling om tröskelundantag.

Tre verktyg för löpande komplexitetsuppföljning

CLI-verktygen ovan ger dig en engångsbild. För löpande synlighet på projektnivå sticker tre verktyg ut:

SonarQube är det mest kompletta alternativet för komplex uppföljning på projektnivå. Community edition täcker cyklomatisk komplexitet, kognitiv komplexitet och testtäckning i ett dashboard. Kvalitetsgrindar kan blockera en merge om ny kod pressar komplexiteten över ett tröskelvärde. Det är verktyget att nå för när ett projekt har fler än två bidragsgivare och en riktig granskningsprocess.

CodeScene korrelerar komplexitetspoäng med commit-historik och lyfter fram funktioner som är både komplexa och ofta förändrade. De skärningspunkterna är din verkliga tekniska skuld, inte bara svåra att förstå utan aktivt kostsamma i varje sprint. Om du behöver argumentera för en refaktoreringssprint på ett teams roadmap är CodeScenes utdata argumentet.

Code Climate Quality är det lättare alternativet, lämpat för soloprojekt och open source-repon. GitHub-integration fungerar direkt, standardinställningarna är förnuftiga och gratistjänsten för publika repon är användbar. Om du hanterar ett sidoprojekt och vill att komplexitetsdriften spåras utan att sätta upp infrastruktur, börja här.

Det beslut som cyklomatisk komplexitet faktiskt tvingar fram

Cyklomatisk komplexitet berättar inte om din kod är bra. Det berättar var risken är koncentrerad. Det är en mer användbar information.

Kör scanen på ditt nuvarande projekt innan nästa sprint. Titta på de 10 funktionerna med högst poäng. Fråga vilka av dem din nästa feature berör. Om svaret är "tre av dem, alla över CC 20" är din sprintplan tydlig: ta de tre funktionerna under CC 10 och shippa sedan featuren. Det tar hälften av den tid det annars hade tagit.

Korollariet för builders: om du letar efter nästa sidoprojekt och arbetar i riktiga kodbaserna är din komplexitetsrapport en spec. Den mest komplexa modulen som blockerar hastigheten, en som är komplex och ständigt berörs, är ett verktyg värt att bygga runt. Ett lättviktigt komplexitetsdashboard som korrelerar CC-poäng med git blame-data och frekvens för ändrade filer, prissatt för ett team på två istället för ett enterprise-kontrakt, är ett projekt som inte riktigt finns ännu. Verktygsgapet är verkligt.

Det är inte ett pitch. Det är ett mönster: de verktyg du vill ha men inte kan hitta är ofta de sidoprojekt som är mest värda att shippa.

Frequently asked questions

Vad är ett bra värde för cyklomatisk komplexitet?
CC mellan 1 och 10 är generellt acceptabelt. NIST:s ursprungliga rekommendation var ett tak på 10 per funktion. Poäng mellan 11 och 20 kräver granskning och testtäckning innan modifiering. Allt över 20 är en stark kandidat för refaktorering innan nytt featurearbete berör samma kod.
Hur beräknar man cyklomatisk komplexitet?
Formeln är M = E - N + 2P, där E är kanter, N är noder och P är sammankopplade komponenter i kontrollflödesgrafen. I praktiken: börja på 1 och lägg till 1 för varje if, else if, for, while, case, catch samt logiska operatorer && och ||. Alla stora statiska analysverktyg gör det här automatiskt.
Hur påverkar cyklomatisk komplexitet sidoprojekt?
I solobuilds ackumuleras hög CC snabbt. Du har inget team att dela kontext med. En funktion skriven på CC 30 för fyra månader sedan kostar en dag av omförståelse innan du säkert kan ändra den. Att hålla kritiska funktioner under CC 15 är en av de mest effektiva investeringarna i framtida shippinghastighet.
Är cyklomatisk komplexitet samma sak som kognitiv komplexitet?
Nej. Cyklomatisk komplexitet räknar oberoende vägar i kontrollflödet, vilket direkt mappar till det minsta antalet testfall som behövs. Kognitiv komplexitet, utvecklad av SonarSource, viktar nästningsdjup och kontrollflödesbrytningar och återspeglar bättre den mentala ansträngning som krävs för att läsa kod. Bägge är användbara; CC stöds mer universellt i verktyg.
När ska man skriva om istället för att refaktorera?
När en funktion har CC över 50 och ingen meningsfull testtäckning kräver säker refaktorering tester som du inte kan skriva eftersom koden är för sammanflätad för att testa isolerat. En omskrivning med tester från grunden är ofta snabbare och säkrare. De flesta devs sätter den praktiska inflektionspunkten mellan CC 40 och 60.
Vilka verktyg mäter cyklomatisk komplexitet?
Radon för Python, ESLint complexity rule för JavaScript och TypeScript, gocyclo för Go, PMD för Java, SonarQube för flerspråkiga enterprise-projekt, Code Climate Quality för ett lättare flerspråkigt alternativ samt CodeScene för komplexitet korrelerad med git-historik. De flesta CI-plattformar integrerar med minst ett av dessa.
Kan cyklomatisk komplexitet vara artificiellt lågt?
Ja. Att extrahera varje gren till en enrads-helper sänker CC för originalfunktionen utan att minska verklig komplexitet, det sprider den över filer som nu är svårare att navigera tillsammans. En funktion med CC 1 som delegerar all grenar till ogenomskinliga helpers är vilseledande. Använd CC som en signal tillsammans med läsbarhetsgranskningar, inte som ett tal att minimera till varje pris.