Grundlagen der zyklomatischen Komplexität

Die Grundlagen der zyklomatischen Komplexität und warum jeder Programmierer davon wissen sollte

Jede Funktion, die Sie schreiben, hat einen Komplexitätswert. Wenn die Funktion keine Entscheidungspunkte enthält, hat sie keinen Komplexitätswert. if, Kein elsekeine Schleife, keine switchSeine zyklomatische Komplexität beträgt 1: genau ein Pfad durch den Code. Füge ein if Fügt man eine weitere Anweisung hinzu, wird es 2. Fügt man eine weitere hinzu, wird es 3. Wenn eine Funktion ein Dutzend bedingter Verzweigungen über mehrere verschachtelte Ebenen angesammelt hat, kann ihre zyklomatische Komplexität 15 oder 20 betragen, und das vollständige Testen erfordert eine entsprechende Anzahl von Testfällen, von denen jeder einen anderen Ausführungspfad abdeckt.

Die zyklomatische Komplexität (CC) wurde 1976 von Thomas J. McCabe als quantitatives Maß für die logische Komplexität des Kontrollflusses eines Programms eingeführt. Sie zählt nach wie vor zu den praktisch nützlichsten Metriken für Codequalität, da ihre Auswirkungen konkret und umsetzbar sind: Ein Komplexitätswert gibt die minimale Anzahl an Testfällen an, die für eine vollständige Pfadabdeckung erforderlich sind, prognostiziert die Verständlichkeit und Modifizierbarkeit des Codes und identifiziert die Funktionen, die am ehesten unentdeckte Fehler enthalten. Dieser Leitfaden behandelt die Formel, die Schwellenwerte, sprachspezifische Beispiele, Refactoring-Techniken zur tatsächlichen Reduzierung der Komplexität sowie die automatische Messung und Überwachung der zyklomatischen Komplexität.

SMART TS XL

Hilft Ihnen, die zyklomatische Komplexität zu meistern, die Leistung zu optimieren und versteckte Fehler zu vermeiden

MEHR ERFAHREN…

Was ist zyklomatische Komplexität?

Die zyklomatische Komplexität misst die Anzahl linear unabhängiger Pfade durch den Quellcode eines Programms. Thomas J. McCabe leitete sie aus der Graphentheorie ab: Jedes Programm lässt sich als Kontrollflussgraph darstellen, wobei die Knoten Anweisungen und die Kanten die möglichen Abläufe zwischen ihnen repräsentieren. Die Formel lautet:

CC = E - N + 2P

Kennzahlen:

  • E = Anzahl der Kanten im Kontrollflussgraphen
  • N = Anzahl der Knoten
  • P = Anzahl der Zusammenhangskomponenten (üblicherweise 1 für eine einzelne Funktion)

Für praktische Berechnungen gibt es eine einfachere Entsprechung: CC = Anzahl der Entscheidungspunkte + 1. . Every Jeder if, else if, while, for, case, catch, && und || Fügt einen Entscheidungspunkt hinzu. Der anfängliche CC jeder Funktion ist 1.

Java

// CC = 1: no decision points
public String greet(String name) {
    return "Hello, " + name;
}

// CC = 3: two decision points (two if statements)
public String classify(int score) {
    if (score >= 90) return "Excellent";
    if (score >= 70) return "Satisfactory";
    return "Needs improvement";
}

// CC = 5: four decision points (three conditions + one loop)
public double calculateTotal(List<Item> items, boolean isMember, boolean isHoliday) {
    double total = 0;
    for (Item item : items) {          // +1
        total += item.getPrice();
    }
    if (isMember) total *= 0.9;        // +1
    if (isHoliday) total *= 0.95;      // +1
    if (total > 100) total -= 5;       // +1
    return total;
}

Schwellenwerte: Welche Punktzahl ist akzeptabel?

McCabes ursprüngliche Richtlinie, die nach wie vor die am häufigsten zitierte ist, definiert vier Risikostufen:

CC-PunktzahlRisikostufeDolmetschen
1 – 10NiedrigEinfach, gut strukturiert, leicht zu testen
11 – 20ModeratKomplexer; erhöhter Testaufwand erforderlich
21 – 50HochKomplex und schwer zu testen; Refactoring empfohlen
> 50Sehr hochIn der Praxis nicht testbar; erhebliches Qualitätsrisiko

Der Schwellenwert von 10 ist die am häufigsten angewendete Grenze bei CI/CD-Qualitätsprüfungen. Der Standard-Schwellenwert für kognitive Komplexität in SonarQube beträgt 15 (eine verwandte, aber eigenständige Metrik). Die NIST-Richtlinien für sicherheitskritische Systeme empfehlen maximal 10 pro Modul.

Eine wichtige Nuance: CC misst die strukturelle, nicht die semantische Komplexität. Eine Funktion mit CC = 8, die eine komplexe Finanzberechnung implementiert, kann schwerer verständlich sein als eine Funktion mit CC = 15, die aus einfachen Sicherheitsprüfungen besteht. Nutzen Sie CC als Anhaltspunkt für weitere Untersuchungen, nicht als endgültiges Urteil.

Zyklomatische Komplexität in verschiedenen Sprachen

Python

Pythons Entscheidungskonstrukte, die zu CC beitragen: if, elif, else (wird nicht gezählt, da keine Bedingung besteht), for, while, try/except (jede except zählt), with (zählt nicht) und boolesche Operatoren and/or unter Bedingungen.

python

# CC = 1
def format_name(first: str, last: str) -> str:
    return f"{first} {last}"

# CC = 4: three decision points
def calculate_discount(price: float, is_member: bool, is_holiday: bool) -> float:
    discount = 0.0
    if is_member:        # +1
        discount += 0.10
    if is_holiday:       # +1
        discount += 0.05
    if price > 100:      # +1
        discount += 0.02
    return price * (1 - discount)

# CC = 6: five decision points (list comprehension counts as a loop)
def process_orders(orders: list[dict]) -> list[dict]:
    return [
        {**order, "total": order["qty"] * order["price"]}  # +1 (comprehension)
        for order in orders
        if order["qty"] > 0                                 # +1 (filter condition)
        if order["price"] > 0                              # +1 (second filter)
    ]

Tools zur Python-CC-Messung: radon (radon cc src/ -s), flake8-cognitive-complexity, pylint mit Komplexitäts-Plugin, SonarQube Python-Analyse.

Javac

Java

// CC = 7: complex authentication with multiple conditions
public AuthResult authenticate(String userId, String password, boolean isMfa) {
    if (userId == null || password == null) return AuthResult.INVALID;  // +2 (||)
    User user = userRepository.findById(userId);
    if (user == null) return AuthResult.NOT_FOUND;                      // +1
    if (!user.checkPassword(password)) return AuthResult.WRONG_PASSWORD;// +1
    if (isMfa && !user.hasMfaEnabled()) return AuthResult.MFA_REQUIRED; // +2 (&&)
    return AuthResult.SUCCESS;
}
// CC = 1 + 2 + 1 + 1 + 2 = 7

Reduzierung:

Java

// After refactoring: CC = 3 (main method) + small helpers with CC = 2 each
public AuthResult authenticate(String userId, String password, boolean isMfa) {
    if (hasInvalidInputs(userId, password)) return AuthResult.INVALID;
    User user = findVerifiedUser(userId, password);
    if (user == null) return AuthResult.WRONG_PASSWORD;
    if (requiresMfa(user, isMfa)) return AuthResult.MFA_REQUIRED;
    return AuthResult.SUCCESS;
}

private boolean hasInvalidInputs(String userId, String password) {
    return userId == null || password == null;  // CC = 2
}

private boolean requiresMfa(User user, boolean isMfa) {
    return isMfa && !user.hasMfaEnabled();      // CC = 2
}

Tools für Java CC: Checkstyle, PMD, SonarQube, IntelliJ IDEA integriert, SMART TS XL.

C# und TypeScript

C# und TypeScript folgen denselben Regeln wie Java. Die wichtigste Ergänzung: LINQ-Ausdrücke in C# und ternäre Verkettungen in TypeScript fügen jeweils zusätzliche Entscheidungspunkte hinzu.

scharf

// CC = 5: switch with four cases
public decimal GetShippingCost(string zone) => zone switch {
    "domestic"      => 5.99m,    // +1
    "eu"            => 15.99m,   // +1
    "international" => 29.99m,   // +1
    "express"       => 49.99m,   // +1
    _               => throw new ArgumentException($"Unknown zone: {zone}")
};

COBOL

COBOLs Entscheidungskonstrukte, die zu CC beitragen: IF/ELSE, EVALUATE WHEN (jede WHEN-Klausel), PERFORM UNTIL, PERFORM VARYING ... WITH TEST BEFORE/AFTER, AT END, ON EXCEPTION, NOT ON EXCEPTION, ON SIZE ERROR.

Cobol

       CALCULATE-DISCOUNT.
           IF WS-CUSTOMER-TYPE = 'GOLD'               *> +1
               IF WS-PURCHASE-AMT > 1000              *> +1
                   COMPUTE WS-DISCOUNT = 0.20
               ELSE                                    *> (no increment)
                   COMPUTE WS-DISCOUNT = 0.15
           ELSE IF WS-CUSTOMER-TYPE = 'SILVER'        *> +1
               COMPUTE WS-DISCOUNT = 0.10
           ELSE                                        *> (no increment)
               COMPUTE WS-DISCOUNT = 0.05
           END-IF
           EVALUATE TRUE
               WHEN WS-REGION = 'NORTH' PERFORM APPLY-REGIONAL-RATE  *> +1
               WHEN WS-REGION = 'SOUTH' PERFORM APPLY-SOUTHERN-RATE  *> +1
           END-EVALUATE.
           *> Total CC = 1 + 5 = 6

Die ausführliche Syntax von COBOL führt dazu, dass Absätze typischerweise länger sind als äquivalente Funktionen in modernen Sprachen. COBOL-Programme mit mehr als 50 CC pro Absatz sind in älteren Codebasen weit verbreitet und stellen die wichtigsten Ziele für Refactoring- und Modernisierungsplanungen dar.

Wie man die zyklomatische Komplexität berechnet: Drei Methoden

Methode 1: Entscheidungspunkte zählen + 1 Die schnellste manuelle Methode. Zählen Sie jedes if, else if, while, for, case, catch, &&, || in der Funktion. Addiere 1 für die Funktion selbst.

Methode 2: Kontrollflussdiagramm Stelle die Funktion als Graph dar: ein Knoten pro Anweisung oder Block, Kanten für jeden Kontrollfluss. Anwenden CC = E - N + 2.

Methode 3: Automatisiertes Tool. Die einzig praktikable Methode für komplexere Funktionen. Die meisten statischen Analysetools berechnen CC automatisch und integrieren es in CI/CD-Pipelines.

Refactoring-Techniken, die die Komplexität tatsächlich reduzieren

Schutzklauseln (Frühe Rückgaben)

Guard-Klauseln beenden die Funktion frühzeitig, wenn Vorbedingungen nicht erfüllt sind. Dadurch werden Else-Zweige vermieden und die Verschachtelungstiefe reduziert.

python

# Before: deeply nested, CC = 5
def process_order(order):
    if order is not None:
        if order.is_valid():
            if order.has_stock():
                if order.payment_cleared():
                    return fulfill_order(order)
                else:
                    return "Payment failed"
            else:
                return "Out of stock"
        else:
            return "Invalid order"
    else:
        return "No order"

# After: flat, CC = 5 (same complexity, dramatically better readability)
def process_order(order):
    if order is None: return "No order"
    if not order.is_valid(): return "Invalid order"
    if not order.has_stock(): return "Out of stock"
    if not order.payment_cleared(): return "Payment failed"
    return fulfill_order(order)

Der Code-Cost-Abstand (CC) verringert sich nicht, es bleiben dieselben Entscheidungspunkte bestehen, aber der Code wird deutlich lesbarer und testbarer. Eine echte Reduzierung des CC erfordert die Beseitigung von Entscheidungspunkten, nicht nur deren Umordnung.

Extraktionsmethoden

Durch das Auslagern logischer Entscheidungsgruppen in benannte Methoden wird der CC der aufrufenden Funktion reduziert, während die Komplexität auf kleinere, testbare Einheiten verteilt wird.

Java

// Before: one method doing everything, CC = 9
public double calculateInvoiceTotal(Invoice invoice, Customer customer) {
    double subtotal = 0;
    for (LineItem item : invoice.getItems()) {
        subtotal += item.getQuantity() * item.getUnitPrice();
        if (item.isTaxable()) subtotal += item.getPrice() * 0.1;
    }
    if (customer.isMember()) subtotal *= 0.9;
    if (customer.hasVoucher()) subtotal -= customer.getVoucherValue();
    if (subtotal < 0) subtotal = 0;
    return subtotal;
}

// After: main method CC = 4, helpers have CC = 2-3 each
public double calculateInvoiceTotal(Invoice invoice, Customer customer) {
    double subtotal = computeLineItemTotal(invoice.getItems());
    subtotal = applyCustomerDiscounts(subtotal, customer);
    return Math.max(0, subtotal);
}

Ersetzen von Konditionalsätzen durch Polymorphismus

Wenn eine Funktion sich basierend auf Typ oder Zustand verzweigt, beseitigt Polymorphismus die Verzweigung vollständig.

Java

// Before: switch on payment type, CC grows with each new type
public void processPayment(String type, double amount) {
    switch (type) {
        case "CREDIT":  processCreditCard(amount); break;
        case "PAYPAL":  processPayPal(amount);     break;
        case "CRYPTO":  processCrypto(amount);     break;
        default: throw new IllegalArgumentException("Unknown type: " + type);
    }
}

// After: new payment types require no changes to this method, CC = 1
public interface PaymentProcessor {
    void process(double amount);
}

public void processPayment(PaymentProcessor processor, double amount) {
    processor.process(amount);  // no branching
}

Zerlegung komplexer Konditionalsätze

Komplexe boolesche Ausdrücke werden in benannte Methoden extrahiert, die ihre Absicht verdeutlichen.

python

# Before: dense boolean logic, hard to understand, easy to mis-test
if user.age >= 18 and user.country in ALLOWED_COUNTRIES and not user.is_banned and user.verified:
    grant_access()

# After: named predicate, self-documenting, unit-testable independently
def is_eligible_for_access(user: User) -> bool:
    return (
        user.age >= 18
        and user.country in ALLOWED_COUNTRIES
        and not user.is_banned
        and user.verified
    )

if is_eligible_for_access(user):
    grant_access()

Zyklomatische Komplexität in CI/CD-Pipelines

Die automatisierte Durchsetzung von CC in CI/CD-Pipelines verhindert, dass sich Komplexität zwischen Code-Reviews unbemerkt anhäuft.

YAML

# GitHub Actions: fail PR if any function exceeds CC threshold
name: Code Quality

on: [pull_request]

jobs:
  complexity-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install radon (Python CC tool)
        run: pip install radon

      - name: Check cyclomatic complexity
        run: |
          radon cc src/ --min C --show-complexity
          # Fails if any function has CC grade C (11-15) or worse
          radon cc src/ --min C --total-average | grep -q "Average complexity" \
            && echo "Complexity check passed" \
            || (echo "Functions with high complexity found" && exit 1)

Für Java mit SonarQube:

YAML

# SonarQube quality gate blocks merge if CC exceeds threshold
- name: SonarCloud Scan
  uses: SonarSource/sonarcloud-github-action@master
  env:
    SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
  with:
    args: |
      -Dsonar.qualitygate.wait=true
      -Dsonar.java.complexity.Function.threshold=10

Die Qualitätsprüfung sollte sich auf neuen, hochkomplexen Code beschränken und nicht auf die gesamte bestehende Codebasis, die möglicherweise bereits einen hohen CC aufweist, der separat behandelt wird.

Zyklomatische Komplexität in COBOL und Legacy-Codebasen

Bei Unternehmenssystemen, in denen COBOL-Programme Abschnitte mit mehr als 50 oder sogar 100 Zyklomatikelementen (CC) enthalten können, dient die Analyse der zyklomatischen Komplexität einem anderen Hauptzweck als bei modernen Codebasen. Die Frage lautet nicht: „Sollten wir diese Funktion refaktorisieren?“, sondern: „Welches Migrationsrisiko birgt dieses Programm, und in welcher Reihenfolge sollten wir es modernisieren?“

Ein COBOL-Programm mit einem CC-Wert von 80 im Hauptabschnitt verfügt über 80 unabhängige Ausführungspfade, für die jeweils ein Testfall zur Validierung erforderlich ist. Fehlen dem Programm Testfälle, was bei den meisten älteren COBOL-Programmen der Fall ist, ist der CC-Wert der wichtigste Indikator dafür, wie viele Validierungsszenarien erstellt werden müssen, bevor eine Konvertierung als sicher gelten kann.

SMART TS XL statische Code-Analyse Berechnet die zyklomatische Komplexität für COBOL, JCL, RPG, PL/I und alle modernen Programmiersprachen gleichzeitig und erstellt so die Portfolio-weite CC-Verteilung, die fundierte Entscheidungen zur Modernisierungsreihenfolge ermöglicht. Programme mit den höchsten CC-Werten und den meisten Aufrufern (hoher Fan-In) stellen die risikoreichsten Migrationsziele dar, wie im Kontext von … beschrieben. Festlegung von Kennzahlen für den Wartbarkeitsindex von COBOL-AnwendungenCC ist eine Komponente eines umfassenderen Qualitätsbildes, zu dem auch das Halstead-Volumen und die Anzahl der Codezeilen gehören.

Die Auswirkungsanalysefunktion nutzt die CC-basierte Komplexitätsklassifizierung, um den Umfang der zu validierenden Maßnahmen bei Änderungen an Programmen mit hoher Komplexität festzulegen: Ein höherer CC-Wert bedeutet mehr Ausführungspfade, was wiederum mehr Testszenarien zur Bestätigung der Verhaltensäquivalenz vor und nach jeder Änderung erfordert.

Für Teams , die Legacy-Modernisierungsprogramme planen , ist die CC-Verteilung im gesamten Portfolio die Grundlage für die Festlegung der Migrationswellenreihenfolge: Programme mit niedriger CC und wenigen Aufrufern werden früh migriert; Programme mit hoher CC und vielen Aufrufern werden zuletzt migriert, nachdem das Team Expertise mit den einfacheren Komponenten aufgebaut hat und die Testinfrastruktur zur Validierung der komplexen Komponenten vorhanden ist.

Komplexität ist nicht der Feind, unsichtbare Komplexität ist es.

Die zyklomatische Komplexität ist eine der wenigen Metriken für Codequalität, die direkt mit der Testbarkeit zusammenhängt. Die Anzahl der für eine vollständige Pfadabdeckung erforderlichen Testfälle entspricht definitionsgemäß mindestens dem CC-Wert. Dieser Zusammenhang macht sie in einer Weise praktisch anwendbar, wie es bei vielen anderen Qualitätsmetriken nicht der Fall ist.

Die Disziplin des CC-Managements erfordert das Verständnis, dass sich Komplexität schrittweise aufbaut. Jede if Eine einzelne Anweisung, die einer wachsenden Funktion hinzugefügt wird, ist im Einzelfall sinnvoll. Das Ergebnis dreijähriger, einzeln sinnvoller Entscheidungen kann eine Funktion mit CC = 40 sein, die niemand mehr anfassen möchte, weil sie schlichtweg zu komplex ist, um sicher darüber zu urteilen. Die in diesem Leitfaden vorgestellten Werkzeuge und Techniken – Schutzklauseln, Methodenextraktion, Polymorphismus, bedingte Dekomposition und CI/CD-Qualitätsgates – dienen dazu, diese unbemerkte Akkumulation zu verhindern und sie systematisch anzugehen, sobald sie eingetreten ist.