Cyclomatische complexiteit: wat het echt meet en waarom
Samenvatting
Cyclomatische complexiteit telt onafhankelijke paden door een functie: beslispunten tellen en 1 optellen. Het getal zegt je hoeveel testgevallen je nodig hebt, maar tools en teams zijn het oneens over waar je de limiet stelt. Goed meten voordat je enforceert.
Cyclomatische complexiteit telt het aantal onafhankelijke paden door een functie. Een functie zonder vertakkingen scoort 1. Voeg een if toe en het wordt 2. Voeg een switch met vier cases toe, en het springt naar 6. Het getal zegt je hoeveel testgevallen je nodig hebt om elk pad te controleren, en het correleert sterk met hoeveel tijd een ander teamlid nodig heeft om de functie te begrijpen.
Dat is de hele metriek. Wat telt is wat teams ervan maken, en waar ze het fout doen.
Wat cyclomatische complexiteit echt meet
McCabe definieerde het in 1976 in grafiektheorie. In de praktijk heb je dat niet nodig. Tel de beslispunten: if, else if, case, while, for, catch, &&, ||, ternaires. Tel ze bij elkaar op en voeg 1 toe. Dat is de score.
def prijs(bestelling):
if bestelling.is_vip: # +1
if bestelling.totaal > 100: # +1
return bestelling.totaal * 0.8
return bestelling.totaal * 0.9
elif bestelling.heeft_coupon: # +1
return bestelling.totaal * 0.95
return bestelling.totaalDeze functie scoort 4. Niet hoog. Maar het is al drie niveaus diep voor een functie die alleen korting toepassen hoort te doen. Dit is het patroon dat opvalt: complexiteit sluipt er stukje bij stukje in, en niemand is de schuldige.
Waar die beruchte '10' vandaan komt (en waarom tools het anders zien)
Iedereen citeert het getal 10. Het komt van NIST Special Publication 500-235, waar McCabe en Watson schreven dat limieten boven de 10 gereserveerd zouden moeten zijn voor teams met ervaren staf, formeel ontwerp en een uitgebreid testplan. Met andere woorden: 10 is een standaard voorstel, geen wet van nature.
Gereedschappen zijn het onderling niet eens waar je de lijn moet trekken, en dit is nuttig om te weten voordat je een CI-gate instelt:
NIST SP 500-235: 10
ESLint
complexityregel: 20Microsoft CA1502: 25
Steve McConnell, Code Complete: 0-5 prima, 6-10 let op, 10+ refactor
Carnegie Mellon referentiebereiken: 1-10 eenvoudig, 11-20 moeilijker te testen, 20+ lastig te begrijpen, 50+ onhoudbaar
Vier bronnen, vier getallen. Als jouw CI-gate faalt bij 10 en je teamgenoot stelt 25 in voor zijn kantoorproject, heeft geen van jullie het mis. Jullie meten tegen verschillende risicotoleranties. Microsoft's eigen documentatie loopt door de NIST-redenering heen als je de bron wil in plaats van de samenvatting.
Wat we zouden stellen: 10-15 als waarschuwingslijn voor nieuwe code, 20 als het punt waar een PR een tweede blik krijgt, 50 als hard blokkade. Onder de 10, besteed geen reviewtijd aan discussie.
De val: een schone score betekent niet een leesbare functie
Hier raken teams in de problemen. Een functie kan 6 scoren en toch lastig zijn om te lezen, en een functie kan 14 scoren en triviaal zijn.
Neem een vlakke switch met tien cases, elk een constant retourneren. Dat is complexiteit 10, en de meeste ingenieurs lezen het in vijftien seconden omdat het patroon duidelijk is: één ding gaat in, één ding komt eruit, geen status tussen de branches. Nu neem je een functie met drie geneste if blokken en een loop die een gedeelde variabele muteert. Die kan 6 scoren, en het zal een senior engineer vijf minuten kosten om erdoor heen te gaan, omdat je de hele call stack in je hoofd moet houden om te weten in welke branch je werkelijk bent.
Cyclomatische complexiteit meet paden. Het meet geen nestingdiepte, geen variabele scope, en geen hoe ver een voorwaarde en zijn gevolg uit elkaar liggen in het bestand. Twee functies met dezelfde score kunnen een volledig ander leesgevoel zijn.
We hebben teams gezien die "onder de 10" gebruikten als proxy voor "beoordeel baar", een PR doorstuurden omdat de linter groen was, en daarna een junior engineer een hele middag in diezelfde functie zagen verliezen drie weken later. De score was goedgekeurd. Het lezen werd niet makkelijker.
Cyclomatische complexiteit tegenover cognitieve complexiteit: twee verschillende vragen
SonarSource introduceerde cognitieve complexiteit in 2017 precies om dit gat te dichten. Cyclomatische complexiteit beantwoordt "hoeveel paden heeft deze functie." Cognitieve complexiteit beantwoordt "hoe lastig is deze functie om vast te houden in je hoofd," en doet dat door nestingdiepte sterker te bestraffen dan vlakke structuur.
Een switch statement beweegt de cognitieve score nauwelijks. Een drie niveaus diep if binnen een loop beweegt het snel, omdat elk extra nestingniveau de mentale kost van het voorgaande verveelvoudigt. SonarSource's eigen schrijven stelt de scoringsregels uit als je het zelf wil implementeren, en de meeste linters die cyclomatische complexiteit ondersteunen ondersteunen nu ook cognitieve complexiteit als een tweede, aparte regel.
Sla cognitieve complexiteit over als je team klein genoeg is dat iedereen al weet waar de rommelige functies zitten. Zet het aan op het moment je meer dan twee mensen hebt die code beoordelen die ze niet schreven, want dat is precies het gat dat het gebouwd is om op te vangen.

Wat AI-codebeoordeling met dit getal doet (en wat het mist)
GitHub Copilot's codebeoordelingen, CodeRabbit, Qodo, en Greptile geven allemaal complexiteitssignalen op een PR tot op zekere hoote weer. Sommigen markeren een functie die een drempel overschreed. Sommigen vatten "deze PR verhoogt complexiteit in drie bestanden" samen. Bijna geen van hen vertelt je waarom dat van belang is voor de persoon die het bestand zes maanden later zonder context opent.
Dat is de werkelijke kloof. Een complexiteitswaarschuwing op een PR is een getal in een opmerking. Het vertelt een beoordelaar niet of de toegevoegde branch daar thuishoort, of het logica drie bestanden over dupiceert, of het juiste antwoord is een guard-clausule versus een volledige extract-method operatie. De AI-tools zijn goed in tellen. Ze zijn nog niet goed in uitleggen waar de puinhoop zit.
Wat die kloof in de praktijk echt dicht maakt is het getal combineren met een vraag die nog steeds een mens moet beantwoorden: doet deze functie één ding, of doet het drie dingen gewikkeld in if statements? Geen linter beantwoordt dat voor je. Het vertelt je alleen waar je moet kijken.
Waarom dit meer betekent op een 100K-regels repo dan een hobbyproject
Op een codebase die je alleen schreef is complexiteit een geheugenwaarschuwing die je al hebt opgelost. Je kent de tien lastige functies bij naam, je weet waarom ze lastig zijn, en je navigeert er voorbij zonder na te denken. Dit is niet de situatie waarin de meeste van jullie zitten.
Op een gedeelde repo met vijf tot vijftig ingenieurs houdt niemand de hele kaart vast. Een functie die 22 scoort en die de originele schrijver volkomen begreep, wordt zes maanden later een functie die een ander team lid helemaal opnieuw moet reconstrueren, meestal onder druk. Jullie hebben dit al gedaan: grep naar de functienaam, Ctrl+F door het bestand, git blame de verdachte regels, dan iemand bellen die acht maanden geleden het team verliet.
Dit is ook waar het complexiteitsgetal gaat interageren met zoeken. Een hoog-complexiteits functie is moeilijker om correct samen te vatten, wat betekent dat het voor een teamgenoot of een code-zoektool moeilijker is om nauwkeurig in één zin te beschrijven. Vraag "wat doet deze functie" over een complexiteit-4 functie en je krijgt een schoon antwoord. Vraag hetzelfde over een complexiteit-22 functie met vier geneste branches en het eerlijke antwoord is "het hangt ervan af welk pad je vraagt." Die dubbelzinnigheid is precies wat een nieuwe medewerker op dag één vertraagt, en daarom is complexiteit de moeite waard om op het repo-niveau bij te houden, niet alleen als een per-PR linter waarschuwing.
De echte kost die niemand in de metriek zet: onboardingtijd
Hier is het stukje dat niet op een dashboard verschijnt. Een junior engineer die zich bij een team aansluit ervaart niet "cyclomatische complexiteit van 23." Ze ervaren: ik heb dit bestand geopend, ik weet niet welke branch wanneer loopt, en ik ben al veertig minuten aan het lezen.
We hebben dit losjes gemeten over een paar onboarding cycli: functies met een complexiteit score boven de 15 kostte nieuwe medewerkers ruwweg drie tot vier keer langer om correct uit te leggen in een rondleiding dan functies die onder de 8 scoren. Niet omdat de hoog-complexiteits functies meer deden, maar omdat traceren welke branch onder welke voorwaarde brandt echte, opeenvolgende leestijd kost, en niemand leest een geneste voorwaarde correct bij de eerste keer over.
Hier verdient de metriek haar plaats voor teams die veel inwerken. Het is eigenlijk geen code-kwaliteitsgetal. Het is een proxy voor "hoeveel minuten gaat dit de volgende persoon die het niet schreef kosten." Volg het op die manier en de drempelgesprek wordt veel minder abstract.

Hoe je het instelt zonder je team te blokkeren
Meet voordat je enforceert. Kies één:
Python:
radon cc -a -s .toont elke functie met een lettercijfer;radon cc -n c .filtert op C-cijfer en erger.JavaScript / TypeScript: ESLint's
complexityregel, ingesteld met['warn', { max: 15 }]om te beginnen.Go:
gocyclo, wijs het naar een package en het print elke functie boven een drempel.Java / C#: SonarQube of PMD, meestal al verbonden aan CI als je team dat gebruikt.
Voer het eenmaal uit over de hele repo voordat je enforcement aanzet. Je krijgt een baseline, en waarschijnlijk een paar functies in de 40-plus range die voor iedereen die nu in het team zit dateert. Blokkeer die niet achteraf; dat leert mensen alleen om rond de linter heen te routeren.
Gate nieuwe code op 10-15. Markeer alles wat 20 overschrijdt voor een tweede beoordelaar, niet automatisch afwijzen; sommige van die functies, zoals de vlakke switch, zijn prima. Behandel alles voorbij de 50 als een technical debt ticket, niet een opmerking op iemands PR.

De limiet houdt alleen stand als de toolchain het elke keer op dezelfde manier forceert. Een regel die eenmaal voor een deadline wordt omzeild wordt voor altijd omzeild.
Achtervolgje het getal of de functie?
Een team dat hard gatet op 10 en vlakke, saaie, makkelijk te lezen functies verzendt is in goede vorm. Een team dat hard gatet op 10 en start met het splitsen van functies in vier kleinere functies die elkaar aanroepen in een keten die niemand zonder drie tabs open kan traceren heeft het getal beter gemaakt en de codebase erger.
Cyclomatische complexiteit is een rookmelder, niet een brandblusser. Het vertelt je waar je moet kijken. Het vertelt je niet wat je moet doen als je eenmaal daar bent, en het getal als het doel behandelen in plaats van de leesbaarheid die het moet proxy-en is hoe teams eindigen met een groen dashboard en een repo die nog steeds een nieuwe medewerker drie weken kost om zich nuttig in te voelen.