Cyklomatisk komplexitet: Vad metriken faktiskt säger
Summary
Cyklomatisk komplexitet mäter antal beslutsvägar i en funktion, inte läsbarhet. Många lag gör misstaget att behandla ett lågt värde som bevis på god kod. I verkligheten kan en flat switch med 10 cases vara trivial att läsa, medan en kluven if-struktur med 6 paths ta fem minuter att spåra. Vi tittar på vad metriken faktiskt säger, varför verktyg använder olika trösklar, och hur du implementerar den utan att bli blockerad av legacy-kod.
Cyklomatisk komplexitet räknar antalet oberoende vägar genom en funktion. En funktion utan någon förgrening får poängen 1. Lägg till en if, det blir 2. Lägg till en switch med fyra cases, det hoppar till 6. Siffran säger hur många testfall du behöver för att täcka alla vägar, och den korrelerar med hur lång tid det tar en främling att hålla funktionen i sitt huvud.
Det är allt metriken är. Det som spelar roll är vad team gör med den, och vad de förstår fel.
Vad cyklomatisk komplexitet faktiskt räknar
McCabe definierade det 1976 som kanter minus noder plus två, i grafteoretiska termer. I praktiken behöver du inte grafteori. Räkna bara beslutspoängen: if, else if, case, while, for, catch, &&, ||, ternära operatorer. Lägg till 1. Det är poängen.
def price(order):
if order.is_vip: # +1
if order.total > 100: # +1
return order.total * 0.8
return order.total * 0.9
elif order.has_coupon: # +1
return order.total * 0.95
return order.totalDetta får poängen 4. Inte hög. Men det är redan tre nivåer av förgreningar för en funktion som började som "använd en rabatt." Det är mönstret som är värt att lägga märke till: komplexitet ökar en elif i taget, och ingen är skurken.
Varifrån "10"-tröskeln kommer (och varför verktyg är oeniga)
Siffran alla citerar är 10. Den kommer från NIST Special Publication 500-235, där McCabe och Watson skrev att gränser över 10 bör reserveras för team med erfaren personal, formell design och en omfattande testplan. Med andra ord: 10 är standardvärdet, inte en naturlag.
Verktyg är inte överens om var gränsen ska dras, och det är värt att veta innan du konfigurerar en port:
NIST SP 500-235: 10
ESLint
complexityrule: 20Microsoft CA1502: 25
Steve McConnell, Code Complete: 0-5 okej, 6-10 titta noga, 10+ refaktorera
Carnegie Mellon referensintervall: 1-10 enkel, 11-20 svårare att testa, 20+ svår att förstå, 50+ omöjlig att underhålla
Fyra källor, fyra siffror. Om din CI-port misslyckas vid 10 och en kollegas sidoprojekt använder 25, har ingen av er fel. Ni mäter bara mot olika risktoleranser. Microsofts egen dokumentation går igenom NISTresonemanget i mer detalj om du vill ha källan istället för sammanfattningen.
Vad vi faktiskt skulle sätta: 10-15 som varningsgräns för ny kod, 20 som punkten där en PR får en andra titt, 50 som en hård stopp. Under 10, slösa inte granskingstid på att tvista om det.
Fällan: En ren poäng betyder inte läsbar funktion
Här är där team drabbas. En funktion kan få poängen 6 och vara genuint svår att läsa, och en funktion kan få 14 och vara trivial.
Ta en flat switch med tio cases, var och en returnerar en konstant. Det är en komplexitet på 10, och de flesta ingenjörer läser det på femton sekunder eftersom mönstret är uppenbart: en sak går in, en sak kommer ut, ingen tillstånd mellan grenarna. Ta nu en funktion med tre kapslade if-block och en loop som muterar en delad variabel. Det kan få poängen 6, och det tar en erfaren ingenjör fem minuter att spåra genom, eftersom du måste hålla hela anropsstacken i ditt huvud för att veta vilken gren du faktiskt är i.
Cyklomatisk komplexitet mäter vägar. Det mäter inte kapslingsdjup, variabelomfattning eller hur långt från varandra ett villkor och dess effekt sitter i filen. Två funktioner med samma poäng kan vara en helt olika läsning.
Vi har sett team behandla "under 10" som en proxy för "gransksbar", pusha en PR genom för att linten var grön, och sedan titta på en juniöringenjör förlora en eftermiddag inuti samma funktion tre veckor senare. Poängen passade. Läsningen blev inte lättare.
Cyklomatisk komplexitet jämfört med kognitiv komplexitet: Två olika frågor
SonarSource introducerade kognitiv komplexitet 2017 specifikt för att åtgärda denna lucka. Cyklomatisk komplexitet svarar på "hur många vägar har denna funktion." Kognitiv komplexitet svarar på "hur svårt är denna funktion att hålla i ditt huvud", och det gör det genom att straffar kapsling mer än platt struktur.
En switch-sats ökar knappt det kognitiva värdet. En tre-nivå-djup if inuti en loop ökar det snabbt, eftersom varje tillagd kapslingsnivå förstärker den mentala kostnaden för den före den. SonarSources egen artikel bryter ner poängreglerna om du vill implementera det själv, och de flesta linters som stöder cyklomatisk komplexitet stöder nu kognitiv komplexitet som en andra, separat regel.
Skip kognitiv komplexitet om ditt team är litet nog att alla redan vet var de röriga funktionerna ligger. Aktivera den i det ögonblick du har mer än två personer som inte skrev koden de granskar, för det är exakt gapet det är byggt för att fånga.

Vad AI-kodgranskningsverktyg gör med denna siffra (och vad de missar)
GitHub Copilots kodgranskning, CodeRabbit, Qodo och Greptile surfar alla på komplexitetssignaler på någon nivå på en PR. Några flaggar en funktion som korsade en tröskel. Några sammanfattar "denna PR ökar komplexiteten i tre filer." Nästan ingen av dem säger varför det spelar någon roll för personen som öppnar filen sex månader senare utan någon kontext.
Det är det riktiga gapet. En komplexitetsvarning på en PR är en siffra i en kommentar. Det säger inte en granskare om den tillagda grenen hör hemma där, om den duplicerar logik tre filer över, eller om rätt fix är en vakt-sats kontra ett helt extract-method pass. AI-verktygen är bra på att räkna. De är inte än bra på att förklara formen på rotan.
Vad faktiskt stänger det gapet i praktiken är att para ihop siffran med en fråga en människa fortfarande måste svara: gör denna funktion en sak, eller gör den tre saker lindade i if-satser? Ingen linter svarar det för dig. Det säger bara åt dig var du ska titta.
Varför detta spelar större roll i en 100K-LOC-repo än i ett sidoprojekt
I en kodkod du skrev ensam är komplexitet ett minnesproblem du redan löste. Du känner de tio snirkliga funktionerna vid namn, du vet varför de är snirkliga, och du navigerar runt dem utan att tänka. Det är inte situationen de flesta i denna publik är i.
I en delad repo med fem till femtio ingenjörer håller ingen hela kartan. En funktion som får poängen 22 som originalförfattaren förstod perfekt blir, sex månader senare, en funktion som en annan ingenjör måste rekonstruera från början, vanligtvis under ett deadline. Du har redan gjort detta: grep för funktionsnamnet, Ctrl+F genom filen, git blame på de misstänkta raderna, sedan meddelande till någon som lämnade teamet för åtta månader sedan.
Detta är också där komplexitetsnumret börjar interagera med sökning. En högkomplexitetsfunktion är svårare att sammanfatta korrekt, vilket betyder att den är svårare för en kollega eller ett kodssökningsverktyg att beskriva korrekt i en mening. Fråga "vad gör denna funktion" om en komplexitet-4-funktion och du får ett rent svar. Fråga samma fråga om en komplexitet-22-funktion med fyra kapslade grenar och det ärliga svaret är "det beror på vilken väg du frågar om." Det är denna tvetydighet som bromsar en nyoperatör på dag ett, och det är varför komplexitet är värt att spåra på reponivanivå, inte bara som en per-PR-lintvarning.
Den verkliga kostnaden som ingen lägger i metriken: Onboarding-tid
Här är delen som inte visas på någon instrumentpanel. En juniöringenjör som ansluter till ett team upplever inte "cyklomatisk komplexitet på 23." De upplever: jag öppnade denna fil, jag vet inte vilken gren som körs när, och jag har läst i fyrtio minuter.
Vi mätte detta löst över några onboarding-cykler: funktioner med ett komplexitetspoäng över 15 tog nya anställda ungefär tre till fyra gånger längre att förklara korrekt i en genomgång än funktioner som får poäng under 8. Inte för att de högkomplexitetsfunktionerna gjorde mer, utan för att spåra vilken gren som brinner under vilka villkor tar verklig, sekventiell lästid, och ingen skimmar en kapslad villkorslogik korrekt vid första försöket.
Det är här metriken tjänar sitt värde för team som ofta introducerar. Det är inte riktigt ett kodkvalitetsnummer. Det är en proxy för "hur många minuter kostar detta den nästa person som inte skrev det." Spåra det på det sättet och tröskeltdiskussionen blir mycket mindre abstrakt.

Hur du gater den utan att blockera ditt team
Mät innan du gater. Välj en:
Python:
radon cc -a -s .visar varje funktion med ett bokstavsbetyg;radon cc -n c .filtrar till C-betyg och värre.JavaScript / TypeScript: ESLints
complexityregel, ställ in med['warn', { max: 15 }]för att börja.Go:
gocyclo, peka det på ett paket och det skriver ut varje funktion över en tröskel.Java / C#: SonarQube eller PMD, vanligtvis redan kopplad till CI om ditt team använder någon av dem.
Kör den en gång över hela repot innan du aktiverar tillämpning. Du får en baslinje, och troligt några funktioner i 40-plus-intervallet som föregår någon för närvarande i teamet. Blockera inte bygget på dessa retroaktivt; det lär bara folk att dirigera runt linten.
Gate ny kod vid 10-15. Flagga något som korsar 20 för en andra granskare, inte ett automatiskt avslag; några av dessa funktioner, som den flata switch, är okej. Behandla allt förbi 50 som en teknisk skuldticket, inte en kommentar på någons PR.

Gränsen håller bara om verktygskretsen tillämpar den på samma sätt varje gång. En regel som stroppas för en deadline en gång stroppas för alltid.
Jagar du poängen eller funktionen?
Ett team som gater hårt vid 10 och levererar platt, tråkig, lätt läsbar funktioner är i god form. Ett team som gater hårt vid 10 och börjar dela upp funktioner i fyra mindre som anropar varandra i en kedja som ingen kan spåra utan tre flikar öppna har gjort numret bättre och kodkoden värre.
Cyklomatisk komplexitet är en rökdetektor, inte en brandsläckare. Det säger åt dig var du ska titta. Det säger inte vad du ska göra när du är där, och att behandla poängen som målet istället för läsbarheten den är tänkt att ersätta är hur team slutar upp med en grön instrumentpanel och en repo som fortfarande tar en nya anställda tre veckor att känna sig användbar i.