Zyklomatische Komplexität: Refaktorieren oder zuerst bauen?

Zusammenfassung

Zyklomatische Komplexität zählt unabhängige Ausführungspfade durch eine Funktion. Scores über CC 10 erfordern Tests vor jeder Änderung, über CC 20 wird zuerst refaktoriert, über CC 50 neu geschrieben. Als Entscheidungsfilter eingesetzt, liefert die Metrik eine klare Prioritätswarteschlange: Welche Funktionen berührt dein nächstes Feature? Das sind deine Pflichtaufgaben vor dem Shippen. Dieser Artikel zeigt, wie du den Scan heute ausführst und die Ausgabe in einen Sprint-Plan umwandelst.

Visualisierung der zyklomatischen Komplexität in einer Entwickler-Codebase mit farbigen Scores

Du öffnest die Codebase um 22 Uhr. Du weißt, welches Feature du als nächstes shippen willst. Zwanzig Minuten später debuggst du etwas Unerwartetes. Die Funktion, die du bearbeitest, ist 300 Zeilen lang, behandelt sechs verschiedene Fälle, und niemand -- auch du nicht -- erinnert sich, warum sie so gewachsen ist. Zyklomatische Komplexität ist die Metrik, die diese Funktion vor Monaten hätte markieren sollen. Sie zählt unabhängige Ausführungspfade durch deinen Code. Dieser Artikel zeigt, wie du den Scan heute auf deinem Projekt ausführst und die Ausgabe in einen Sprint-Plan umwandelst.

Was zyklomatische Komplexität wirklich misst

Thomas McCabe veröffentlichte die Metrik 1976. Die Formel ist M = E - N + 2P, wobei E Kanten, N Knoten und P verbundene Komponenten im Kontrollfluss-Graphen einer Funktion sind. In der Praxis: Start bei 1, addiere 1 für jedes if, else if, for, while, case, catch, && oder ||. Das ist der Score.

Eine Funktion ohne Verzweigungen hat CC = 1. Eine Funktion, die zehn Bedingungen prüft, über eine Liste iteriert und drei Fehlertypen behandelt, landet bei CC 15-20. Eine Funktion, die sechs Entwickler über zwei Jahre in Patches geflickt haben, kann CC 60 erreichen.

Die Metrik erfasst nicht alles. Sie misst keine Verschachtelungstiefe, keine Namensqualität, keine Abstraktionsklarheit. Eine Funktion kann niedrige CC haben und trotzdem schwer lesbar sein. Aber eine Funktion mit CC über 25 ist fast immer schwer lesbar. Die Korrelation in eine Richtung ist konsistent genug, um als Prioritätsfilter nützlich zu sein.

Wichtig: Zyklomatische Komplexität und kognitive Komplexität sind nicht dasselbe. Die kognitive Komplexität (entwickelt von SonarSource) bestraft tiefe Verschachtelung stärker und misst, wie schwer Code für einen Menschen zu lesen ist. Für CI-Checks ist die zyklomatische Variante besser geeignet -- sie ist mathematisch klar definiert und konsistent über Sprachgrenzen hinweg.

Welche Schwellenwerte über dein nächstes Vorgehen entscheiden

Die meisten Style-Guides und Static-Analysis-Tools konvergieren auf ähnliche Bereiche:

Die ursprüngliche NIST-Empfehlung lag bei maximal 10 pro Funktion. In der Praxis behandeln die meisten Devs 15 als realistisches Soft-Limit für Produktionscode, und alles über 20 als Blocker für neue Arbeit, die diese Funktion berührt.

Die Tabelle ist dein Entscheidungsbaum für das, was du als nächstes bauen sollst. Wenn dein Backlog voll ist und du nicht zwischen Feature-Ergänzung und Aufräumen entscheiden kannst, führe zuerst einen Komplexitäts-Scan durch. Funktionen über CC 20 sind deine Pflicht-Stopps, bevor irgendetwas Neues sie berührt.

Übersicht der zyklomatischen Komplexitäts-Schwellenwerte und Entscheidungspfade für Entwickler

Den ersten Scan in 5 Minuten durchführen

Jedes große Sprach-Ökosystem hat ein CLI-Tool bereit. Du brauchst für diesen ersten Schritt kein SaaS-Dashboard:

Python (Radon):

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

JavaScript / TypeScript (ESLint):

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

Dann führe eslint ./src --ext .ts,.js in deiner Pipeline aus.

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

Die Ausgabe listet jede Funktion nach CC-Score sortiert. Was du suchst: die 10 Funktionen mit den höchsten Scores in deinem Projekt. Speicher diese Liste. Das ist deine Prioritätswarteschlange für den nächsten Sprint.

Ein häufiger Fehler bei diesem Schritt: Den gesamten Report als Aufgabe behandeln. Das ist falsch. Du schaust dir nur die Funktionen an, die dein nächstes Feature berühren wird. Alles andere bleibt in der Liste für später -- oder gar nicht, wenn es nie wieder angefasst wird.

Ein reales Radon-Ergebnis sieht so aus: F 142:4 handle_checkout - C (34). Das bedeutet: Funktion handle_checkout, Zeile 142, CC-Score 34, Bewertung C (für "complex"). Alles ab B (CC > 10) ist orange, C (CC > 15) ist rot. Du willst, dass dein Scan ausschließlich A-Bewertungen zeigt, bevor du neue Logik in eine Funktion gibst.

Den Report als Entscheidungsgrundlage lesen

Funktionen mit hohem CC clustern sich an vorhersehbaren Stellen. In einer Empfehlungs-API mit ca. 8.000 Zeilen lieferte radon cc -s drei Funktionen mit CC über 30. Alle drei lagen in der Datennormalisierungsschicht. Diese Schicht war über sechs Monate inkrementell gepatcht worden, jedes Mal für einen anderen Edge-Case einer Datenquelle. Niemand hatte den kumulativen Schaden jemals angeschaut.

Jedes neue Feature, das die Normalisierungsschicht berührte, dauerte 40% länger zum Shippen. Zwei Tage vor dem nächsten Sprint refaktorisierten wir diese drei Funktionen und brachten die CC-Scores von 32, 28 und 31 auf 7, 5 und 8. Der folgende Sprint war der schnellste seit vier Monaten.

Drei Funktionen. Zwei Tage. Vier Monate Rückstand, erklärt.

Das Muster ist konsistent: Etwa 10-15% der Funktionen halten 70-80% der Komplexität in jedem organisch gewachsenen Projekt. Diese Konzentration ist deine echte Prioritätsliste. Bevor du ein neues Feature committest, führe den Scan durch. Wenn dein Feature eine Funktion über CC 15 berührt, hast du eine konkrete Wahl: jetzt langsam in riskanten Code shippen, oder zwei Tage refaktorieren und in der darauffolgenden Woche ordentlich shippen. Die Rechnung spricht meist für das Refaktorieren.

Für Side-Projects gilt das noch mehr als in Team-Settings. Du kannst eine schwierige Funktion nicht an einen Kollegen weitergeben. Die vollen kognitiven Kosten, CC-30-Code neu zu verstehen, fallen auf dich -- meist um 22 Uhr, sechs Monate nachdem du ihn geschrieben hast.

Das zweite, was Komplexitätsberichte gut können: Sie geben dir eine konkrete Antwort, wenn du zwischen "Feature hinzufügen" und "zuerst aufräumen" gelähmt bist. Der Scan beseitigt die Ambiguität. Berührt das Feature hochgradigen CC-Code? Dann erst aufräumen. Falls nicht, shippen. Das ist eine Entscheidung, keine Debatte.

Wann hohe Komplexität berechtigt ist

Nicht jede Funktion mit hohem CC ist ein Problem.

Eine Payment-State-Machine, die 12 Transaktionszustände behandelt, muss diese 12 Zustände wirklich behandeln. Ein Parser, der 15 Grammatikregeln verarbeitet, hat 15 echte Fälle. Diese in 15 Hilfsfunktionen mit jeweils CC 1 aufzuteilen, reduziert die Komplexität nicht -- es verteilt sie auf Dateien, die jetzt schwerer gemeinsam zu navigieren sind.

Die nützliche Frage ist nicht "ist dieser CC zu hoch?", sondern "ist dieser CC höher als die Domäne erfordert?" Eine Routing-Funktion, die 15 URL-Muster mit CC = 18 behandelt, ist wahrscheinlich angemessen. Eine User-Profile-Update-Funktion mit CC = 18, die durch angesammelte Patches für ungeplante Edge-Cases gewachsen ist, ist ein anderes Problem.

Halte CC unter 15 auf deinen kritischen Pfaden. Lass es in echten Komplexdomänen höher werden und dokumentiere den Grund inline. Die Funktionen, die dich später überraschen, sind immer die, für die es keine Rechtfertigung gibt ausser angesammelten Fixes.

Ein gutes Signal für "berechtigt hohe Komplexität": Die Funktion hat seit sechs Monaten keinen Bug gehabt und wird selten geändert. Wenn die Commit-Historie zeigt, dass eine hochgradige Funktion nie angefasst wird, ist das Risiko abstrakt. Wenn sie drei Mal pro Woche geändert wird, ist das Risiko real und sofort adressierbar.

Komplexitätsscan-Ausgabe in einem modernen Entwickler-Terminal mit farbiger Score-Übersicht

Komplexitätsprüfungen in CI integrieren und blind debuggen vermeiden

Den Scan einmal manuell durchzuführen ist nützlich. Ihn in CI zu integrieren ist das, was das Verhalten über Zeit wirklich verändert.

Die praktische Einrichtung: Setze einen Schwellenwert in deiner Linter-Config, lass den Build fehlschlagen, wenn eine neue Funktion ihn überschreitet, und speichere den Komplexitätsbericht als CI-Artefakt bei jedem Lauf. Die Liste existiert bei jedem Build, ohne dass jemand daran denken muss, sie zu generieren.

Für GitHub Actions mit einem Python-Projekt:

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

Für JavaScript mit ESLint bereits in der Pipeline: füge "complexity": ["error", 12] zu deinen Rules hinzu. Jeder PR, der eine Funktion über 12 schiebt, schlägt automatisch fehl.

Ein praktischer Team-Standard: CC <= 15 als harter CI-Block, CC 11-15 als Warnung mit einem Inline-Kommentar, der den fachlichen Grund erklärt. Diese zweite Stufe erzwingt das Gespräch, ohne dass jede Code-Review eine Verhandlung über Schwellenwert-Ausnahmen wird.

Der unterschätzte Vorteil eines CI-Checks: Er macht die Konversation datenbasiert. Statt "ich finde diese Funktion zu komplex" heißt es "diese Funktion hat CC 23, unser Team-Standard ist 15, hier ist die Business-Begründung für die Ausnahme". Das ist ein anderes Gespräch.

Drei Tools für dauerhaftes Komplexitäts-Tracking

Die CLI-Tools oben liefern dir einen einmaligen Snapshot. Für projektweite Sichtbarkeit über Zeit stechen drei Tools heraus.

SonarQube ist die vollständigste Option für projektweites Komplexitäts-Tracking. Die Community-Edition deckt zyklomatische Komplexität, kognitive Komplexität und Testabdeckung in einem Dashboard ab. Quality-Gates können einen Merge blockieren, wenn neuer Code die Komplexität über einen Schwellenwert schiebt. Es ist das Tool, nach dem du zuerst greifst, wenn ein Projekt mehr als zwei Mitwirkende und einen echten Review-Prozess hat.

CodeScene korreliert Komplexitäts-Scores mit der Commit-Historie und zeigt Funktionen, die komplex und häufig geändert sind. Diese Schnittmengen sind deine echte technische Schuld -- nicht nur schwer zu verstehen, sondern aktiv teuer bei jedem Sprint. Wenn du ein Argument für einen Refactoring-Sprint auf der Team-Roadmap brauchst, ist CodeScenes Ausgabe das Argument.

Code Climate Quality ist die leichtgewichtigere Option, geeignet für Solo-Projekte und Open-Source-Repos. Die GitHub-Integration funktioniert out-of-the-box, die Defaults sind vernünftig, und der Free-Tier für öffentliche Repos ist nutzbar. Wenn du ein Side-Project managst und Komplexitätsdrift tracken willst, ohne Infrastruktur aufzubauen, fang hier an.

Was der nächste konkrete Schritt ist

Zyklomatische Komplexität sagt dir nicht, ob dein Code gut ist. Sie sagt dir, wo das Risiko konzentriert ist. Das ist eine nützlichere Information.

Führe den Scan auf deinem aktuellen Projekt durch, bevor dein nächster Sprint beginnt. Schau dir die 10 Funktionen mit den höchsten Scores an. Frag, welche davon dein nächstes Feature berührt. Wenn die Antwort "drei davon, alle über CC 20" lautet, ist dein Sprint-Plan klar: diese drei Funktionen unter CC 10 bringen, dann das Feature shippen. Es wird halb so lange dauern wie ohne das Refaktorieren.

Das Pendant für Builder: Wenn du nach deinem nächsten Side-Project suchst und in echten Codebases arbeitest, ist dein Komplexitätsbericht eine Spec. Das komplexeste Modul, das Velocity blockiert -- eines, das komplex und ständig geändert ist -- ist ein Tool wert, das man darum baut. Ein schlankes Komplexitäts-Dashboard, das CC-Scores mit Git-Blame-Daten und Dateiänderungshäufigkeit korreliert, für ein Team von zwei statt für einen Enterprise-Vertrag, ist ein Projekt, das noch nicht vollständig existiert. Die Lücke im Tooling ist real.

Das ist kein Pitch. Es ist ein Muster: Die Tools, die du willst, aber nicht findest, sind oft die Side-Projects, die es am meisten wert sind, geshipped zu werden.

Fang heute Abend an: radon cc -s -a ./src oder gocyclo -over 10 ./.... Zehn Minuten. Die Liste, die du bekommst, ist ehrlicher als jede Retrospektive. Sie sagt dir nicht, was du hättest tun sollen -- sie sagt dir konkret, was du als nächstes tust. Kein Raten, kein Bauchgefühl, nur Daten.

Häufig gestellte Fragen

Was ist zyklomatische Komplexität?
Zyklomatische Komplexität ist eine 1976 von Thomas McCabe entwickelte Code-Metrik, die die Anzahl unabhängiger Ausführungspfade durch eine Funktion zählt. Der Score beginnt bei 1 und erhöht sich für jedes `if`, `for`, `while`, `case`, `catch`, `&&` oder `||`. Je höher der Wert, desto schwerer ist die Funktion zu testen und zu warten.
Ab welchem CC-Wert sollte ich refaktorieren?
Funktionen mit CC über 20 sollten vor dem Hinzufügen neuer Features refaktoriert werden. CC 11-20 erfordert zumindest Tests, bevor du Änderungen vornimmst. Bei CC über 50 ist ein komplettes Neuschreiben meist effizienter als weiteres Patchen.
Welches Tool eignet sich für den ersten Komplexitätsscan in Python?
Radon ist die einfachste Lösung für Python: `pip install radon` gefolgt von `radon cc -s -a ./src` gibt dir eine nach CC sortierte Liste aller Funktionen. Für JavaScript nutze die ESLint-Regel `"complexity": ["error", 10]`.
Muss ich immer refaktorieren, wenn CC über 20 liegt?
Nicht automatisch. Eine Payment-State-Machine, die 12 echte Transaktionszustände abbildet, hat CC = 12 aus fachlichen Gründen. Die Frage ist: Ist der CC höher als die Domäne es erfordert? Wenn ja, refaktorieren. Wenn nein, den Grund inline dokumentieren und weitermachen.
Wie integriere ich Komplexitätsprüfungen in meine CI-Pipeline?
Für Python: Radon in einem GitHub-Actions-Schritt mit `radon cc -n C -s ./src` und einem Exit-Code-Check. Für JavaScript: die ESLint-Regel `"complexity": ["error", 12]` im bestehenden Lint-Schritt. Bei jedem PR schlägt der Build fehl, wenn eine neue Funktion den Schwellenwert überschreitet.
Was unterscheidet zyklomatische von kognitiver Komplexität?
Zyklomatische Komplexität zählt Pfade im Kontrollfluss-Graphen einer Funktion -- sie ist mathematisch definiert und gut für automatisierte CI-Checks geeignet. Kognitive Komplexität (von SonarSource) misst, wie schwer Code für einen Menschen zu lesen ist, und bestraft tiefe Verschachtelung stärker. Beide Metriken ergänzen sich.
Welche Tools eignen sich für dauerhaftes Komplexitäts-Tracking im Team?
SonarQube Community Edition für projektweites Tracking mit Quality-Gates, CodeScene für die Korrelation von Komplexität und Commit-Häufigkeit (ideal für Refactoring-Argumente), und Code Climate Quality für Solo-Projekte und Open-Source-Repos mit out-of-the-box GitHub-Integration.