Hver funktion, du skriver, har en kompleksitetsscore. Hvis funktionen ikke indeholder nogen beslutningspunkter, er der ingen if, nr else, ingen løkke, ingen switch, dens cyklomatiske kompleksitet er 1: præcis én sti gennem koden. Tilføj en if sætning, og den bliver 2. Tilføj en anden, og den bliver 3. Når en funktion har akkumuleret et dusin betingede grene på tværs af flere indbyggede niveauer, kan dens cyklomatiske kompleksitet være 15 eller 20, og fuldstændig testning af den kræver et tilsvarende antal testcases, der hver dækker en distinkt udførelsessti.
Cyklomatisk kompleksitet (CC) blev introduceret af Thomas J. McCabe i 1976 som et kvantitativt mål for den logiske kompleksitet af et programs kontrolflow. Det er fortsat en af de mest praktisk anvendelige kodekvalitetsmålinger, fordi dens implikationer er konkrete og handlingsrettede: en kompleksitetsscore fortæller dig det minimale antal testcases, der er nødvendige for fuldstændig dækning af kodestier, forudsiger, hvor svær koden vil være at forstå og ændre, og identificerer de funktioner, der mest sandsynligt indeholder uopdagede defekter. Denne guide dækker formlen, tærsklerne, sprogspecifikke eksempler, refaktoreringsteknikker, der rent faktisk reducerer kompleksitet, og hvordan man måler og sporer den automatisk.
SMART TS XL
Hjælper dig med at mestre cyklomatisk kompleksitet, optimere ydeevnen og forhindre skjulte fejl
FÅ MERE AT VIDE…Hvad er cyklomatisk kompleksitet?
Cyklomatisk kompleksitet måler antallet af lineært uafhængige stier gennem et programs kildekode. Thomas J. McCabe udledte det fra grafteori: ethvert program kan repræsenteres som en kontrolflowgraf, hvor noder er udsagn, og kanter er de mulige strømme mellem dem. Formlen er:
CC = E - N + 2P
Hvor:
- E = antal kanter i kontrolflowgrafen
- N = antal noder
- P = antal forbundne komponenter (normalt 1 for en enkelt funktion)
Til praktisk beregning er der en enklere ækvivalent: CC = antal beslutningspunkter + 1. Hver if, else if, while, for, case, catch, &&og || tilføjer ét beslutningspunkt. Start-CC for enhver funktion er 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;
}
Tærskler: Hvilken score er acceptabel?
McCabes oprindelige vejledning, som fortsat er den mest citerede, definerer fire risikoniveauer:
| CC-score | Risikoniveau | Fortolkning |
|---|---|---|
| 1 - 10 | Lav | Enkel, velstruktureret, nem at teste |
| 11 - 20 | Moderat | Mere kompleks; øget testindsats kræves |
| 21 - 50 | Høj | Kompleks og vanskelig at teste; refaktorering anbefales |
| > 50 | Meget Høj | Utestbar i praksis; alvorlig kvalitetsrisiko |
Grænsen på 10 er den mest almindeligt håndhævede grænse i CI/CD-kvalitetsporte. SonarQubes standardgrænse for kognitiv kompleksitet er 15 (en relateret, men separat metrik). NIST-retningslinjer for sikkerhedskritiske systemer anbefaler maksimalt 10 pr. modul.
En vigtig nuance: CC måler strukturel kompleksitet, ikke semantisk kompleksitet. En funktion med CC = 8, der implementerer en kompleks finansiel beregning, kan være sværere at forstå end en CC = 15-funktion, der består af enkle defensive kontroller. Brug CC som et signal til undersøgelse, ikke som en endelig dom.
Cyklomatisk kompleksitet på forskellige sprog
Python
Pythons beslutningskonstruktioner, der bidrager til CC: if, elif, else (tælles ikke med, den har ingen betingelse), for, while, try/except (hver except tæller), with (tæller ikke med) og boolske operatorer and/or under forhold.
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)
]
Værktøjer til Python CC-måling: radon (radon cc src/ -s), flake8-cognitive-complexity, pylint med kompleksitets-plugin, SonarQube Python-analyse.
Java
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
Reducere det:
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
}
Værktøjer til Java CC: Checkstyle, PMD, SonarQube, indbygget IntelliJ IDEA SMART TS XL.
C# og TypeScript
C# og TypeScript følger de samme regler som Java. Den vigtigste tilføjelse: LINQ-udtryksklausuler i C# og ternære kæder i TypeScript tilføjer hver især beslutningspunkter.
csharp
// 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 beslutningskonstruktioner, der bidrager til CC: IF/ELSE, EVALUATE WHEN (hver WHEN-klausul), 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
COBOLs verbose syntaks betyder, at afsnit typisk er længere end tilsvarende funktioner i moderne sprog. COBOL-programmer med CC over 50 pr. afsnit er almindelige i ældre kodebaser og repræsenterer de højest prioriterede mål for både refactoring og moderniseringsplanlægning.
Sådan beregner du cyklomatisk kompleksitet: Tre metoder
Metode 1: Tæl beslutningspunkter + 1 Den hurtigste manuelle metode. Tæl hver if, else if, while, for, case, catch, &&, || i funktionen. Læg 1 til for selve funktionen.
Metode 2: Kontrolflowgraf Tegn funktionen som en graf: én node pr. sætning eller blok, kanter for hver kontrolstrøm. Anvend CC = E - N + 2.
Metode 3: Automatiseret værktøj Den eneste praktiske metode til alt ud over en triviel funktion. De fleste statiske analyseværktøjer beregner CC automatisk og integrerer det i CI/CD-pipelines.
Refactoringteknikker, der rent faktisk reducerer kompleksitet
Vagtklausuler (tidlig tilbagevenden)
Guard-klausuler afslutter funktionen tidligt, når forudsætninger fejler, hvilket eliminerer else-grene og reducerer indlejringsdybden.
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)
CC mindskes ikke, de samme beslutningspunkter eksisterer, men koden bliver langt nemmere at læse og teste. Ægte CC-reduktion kræver eliminering af beslutningspunkter, ikke blot omarrangering af dem.
Ekstraktionsmetoder
Flytning af logiske grupper af beslutninger til navngivne metoder reducerer CC'en for den kaldende funktion, samtidig med at kompleksiteten fordeles på tværs af mindre, testbare enheder.
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);
}
Erstatning af konditionalis med polymorfi
Når en funktion forgrener sig baseret på type eller tilstand, eliminerer polymorfi forgreningen fuldstændigt.
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
}
Nedbrydning af komplekse konditionaler
Uddrag komplekse boolske udtryk til navngivne metoder, der afslører deres hensigt.
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()
Cyklomatisk kompleksitet i CI/CD-rørledninger
Automatiseret CC-håndhævelse i CI/CD-pipelines forhindrer, at kompleksitet ophobes usynligt mellem kodegennemgange.
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)
Til Java med 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
Kvalitetsporten bør blokere på ny kode med høj kompleksitet, ikke på hele den ældre kodebase, som muligvis allerede har høj CC, der adresseres separat.
Cyklomatisk kompleksitet i COBOL og ældre kodebaser
For virksomhedssystemer, hvor COBOL-programmer kan have afsnit med CC, der overstiger 50 eller endda 100, tjener cyklomatisk kompleksitetsanalyse et andet primært formål end for moderne kodebaser. Spørgsmålet er ikke "skal vi refaktorere denne funktion?", men "hvad er migrationsrisikoen for dette program, og i hvilken rækkefølge skal vi modernisere?"
Et COBOL-program med CC = 80 på tværs af hovedafsnittet har 80 uafhængige udførelsesstier, som hver især kræver en testcase for at validere. Hvis programmet mangler testcases, hvilket de fleste ældre COBOL-programmer gør, er CC-scoren den primære indikator for, hvor mange valideringsscenarier der skal bygges, før konverteringsarbejde kan betragtes som sikkert.
SMART TS XL's statisk kodeanalyse Beregner cyklomatisk kompleksitet for COBOL, JCL, RPG, PL/I og alle moderne sprog samtidigt, hvilket producerer CC-fordelingen på porteføljeniveau, der gør beslutninger om moderniseringssekvensering evidensbaserede. Programmer med de højeste CC-scorer og flest callers (høj fan-in) er de migreringsmål med den højeste risiko, som beskrevet i konteksten af etablering af vedligeholdelsesindeksmålinger for COBOL-applikationer, CC er én komponent af et bredere kvalitetsbillede, der også inkluderer Halstead Volume og kodelinjer.
Effektanalysefunktionen bruger den CC-baserede kompleksitetsklassificering til at bestemme , hvad der skal valideres, når et program med høj kompleksitet ændres: højere CC betyder flere udførelsesstier, hvilket betyder flere testscenarier, der skal verificeres for at bekræfte adfærdsmæssig ækvivalens før og efter enhver ændring.
For teams, der planlægger ældre moderniseringsprogrammer , er CC-fordelingen på tværs af porteføljen input til migreringsbølgesekvensering: Programmer med lav CC og få kaldere migrerer tidligt; programmer med høj CC og mange kaldere migrerer sidst, efter at teamet har opbygget ekspertise om de enklere komponenter, og testinfrastrukturen er på plads til at validere de komplekse.
Kompleksitet er ikke fjenden, usynlig kompleksitet er
Cyklomatisk kompleksitet er en af de få kodekvalitetsmålinger, der er direkte forbundet med testbarhed. Antallet af testcases, der kræves for fuldstændig dækning af stien, er per definition mindst lig med CC-scoren. Denne forbindelse gør den praktisk brugbar på en måde, som mange kvalitetsmålinger ikke er.
Disciplinen med at styre CC kræver forståelse for, at kompleksitet akkumuleres trinvis. if En sætning tilføjet til en voksende funktion er individuelt rimelig. Resultatet af tre års individuelt rimelige beslutninger kan være en funktion med CC = 40, som ingen ønsker at røre ved, fordi den virkelig er for kompleks til at ræsonnere sikkert omkring. Værktøjerne og teknikkerne i denne vejledning, beskyttelsesklausuler, metodeudtrækning, polymorfi, betinget dekomponering og CI/CD-kvalitetsporte, eksisterer for at forhindre, at denne akkumulering sker usynligt, og for at håndtere den systematisk, når den allerede sker.