Igal kirjutataval funktsioonil on keerukusaste. Kui funktsioon ei sisalda otsustuspunkte, siis mitte ifJuures else, ei tsüklit, ei switch, selle tsüklomaatiline keerukus on 1: täpselt üks tee läbi koodi. Lisage if lause ja see saab 2. Lisa veel üks ja see saab 3. Selleks ajaks, kui funktsioon on kogunud tosin tingimuslikku haru mitmel pesastatud tasandil, võib selle tsüklomaatiline keerukus olla 15 või 20 ja selle täielikuks testimiseks on vaja vastavat arvu testjuhtumeid, millest igaüks hõlmab erinevat teostusrada.
Tsüklomaatilise keerukuse (KK) võttis kasutusele Thomas J. McCabe 1976. aastal programmi juhtimisvoo loogilise keerukuse kvantitatiivse mõõduna. See on endiselt üks praktiliselt kõige kasulikumaid koodikvaliteedi mõõdikuid, kuna selle tagajärjed on konkreetsed ja rakendatavad: keerukusskoor näitab minimaalset testide arvu, mis on vajalik täieliku tee läbimiseks, ennustab, kui keeruline on koodi mõista ja muuta, ning tuvastab funktsioonid, mis sisaldavad kõige tõenäolisemalt avastamata defekte. See juhend hõlmab valemit, läviväärtusi, keelepõhiseid näiteid, refaktoriseerimistehnikaid, mis tegelikult keerukust vähendavad, ning seda, kuidas seda automaatselt mõõta ja jälgida.
SMART TS XL
Aitab teil hallata tsüklomaatilist keerukust, optimeerida jõudlust ja vältida varjatud vigu
SAAGE LISATEAVET…Mis on tsüklomaatiline keerukus?
Tsüklomaatiline keerukus mõõdab programmi lähtekoodis lineaarselt sõltumatute teede arvu. Thomas J. McCabe tuletas selle graafiteooriast: iga programmi saab esitada juhtimisvoo graafina, kus sõlmed on laused ja servad on võimalikud vood nende vahel. Valem on:
CC = E - N + 2P
kus:
- E = servade arv juhtimisvoo graafikus
- N = sõlmede arv
- P = ühendatud komponentide arv (tavaliselt 1 ühe funktsiooni korral)
Praktiliseks arvutamiseks on olemas lihtsam vaste: CC = otsustuspunktide arv + 1. Iga if, else if, while, for, case, catch, &&ja || lisab ühe otsustuspunkti. Mis tahes funktsiooni algne CC on 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;
}
Läved: Milline tulemus on vastuvõetav?
McCabe'i algsed juhised, mida siiani enim tsiteeritakse, määratlevad neli riskitaset:
| CC-skoor | Riskitase | Tõlgendus |
|---|---|---|
| 1 - 10 | Madal | Lihtne, hästi struktureeritud, kergesti testitav |
| 11 - 20 | Mõõdukas | Keerulisem; nõuab suuremat testimispingutust |
| 21 - 50 | Kõrge | Keeruline ja raskesti testitav; refaktoreerimine on soovitatav |
| > 50 | Väga kõrge | Praktikas testimatu; tõsine kvaliteedirisk |
CI/CD kvaliteediväravate puhul on kõige sagedamini jõustatav piirang 10. SonarQube'i vaikimisi kognitiivse keerukuse lävi on 15 (seotud, kuid eraldiseisev mõõdik). NIST-i juhised ohutuskriitiliste süsteemide jaoks soovitavad maksimaalselt 10 mooduli kohta.
Üks oluline nüanss: CC mõõdab struktuurilist keerukust, mitte semantilist keerukust. Funktsiooni, mille CC = 8 ja mis rakendab keerulist finantsarvutust, võib olla raskem mõista kui funktsiooni CC = 15, mis koosneb otsestest kaitsekontrollidest. Kasutage CC-d uurimissignaalina, mitte lõpliku otsusena.
Tsüklomaatiline keerukus erinevates keeltes
Python
Pythoni otsustuskonstruktsioonid, mis aitavad kaasa CC-le: if, elif, else (ei loeta, sellel pole tingimust) for, while, try/except (iga except loeb), with (ei loe) ja tõeväärtusoperaatorid and/or tingimustes.
püüton
# 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)
]
Pythoni CC mõõtmise tööriistad: radon (radon cc src/ -s), flake8-cognitive-complexity, pylint keerukuspluginaga, SonarQube Pythoni analüüs.
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
Selle vähendamine:
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
}
Java CC tööriistad: Sisseehitatud Checkstyle, PMD, SonarQube, IntelliJ IDEA, SMART TS XL.
C# ja TypeScript
C# ja TypeScript järgivad samu reegleid nagu Java. Oluline täiendus: nii C# LINQ avaldisklauslid kui ka TypeScripti kolmikahelad lisavad otsustuspunkte.
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
COBOLi otsustuskonstruktsioonid, mis aitavad kaasa CC-le: IF/ELSE, EVALUATE WHEN (iga WHEN-klausel), PERFORM UNTIL, PERFORM VARYING ... WITH TEST BEFORE/AFTER, AT END, ON EXCEPTION, NOT ON EXCEPTION, ON SIZE ERROR.
kobol
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
COBOLi sõnaselge süntaks tähendab, et lõigud on tavaliselt pikemad kui samaväärsed funktsioonid tänapäevastes programmeerimiskeeltes. COBOLi programmid, mille CC on üle 50 lõigu kohta, on levinud vanemates koodibaasides ja esindavad nii refaktoreerimise kui ka moderniseerimise planeerimise kõrgeima prioriteediga sihtmärke.
Tsüklomaatilise keerukuse arvutamine: kolm meetodit
Meetod 1: Otsustuspunktide loendamine + 1 Kiireim käsitsi meetod. Loendage iga if, else if, while, for, case, catch, &&, || funktsioonis. Lisa funktsiooni enda väärtuseks 1.
Meetod 2: Juhtimisvoo graafik Joonista funktsioon graafikuna: üks sõlm lause või ploki kohta, servad iga juhtimisvoo jaoks. Rakenda. CC = E - N + 2.
Meetod 3: Automatiseeritud tööriist Ainus praktiline meetod kõigeks, mis ulatub kaugemale triviaalsetest funktsioonidest. Enamik staatiliste analüüside tööriistu arvutab CC automaatselt ja integreerib selle CI/CD torujuhtmetesse.
Refaktorimistehnikad, mis tegelikult keerukust vähendavad
Kaitseklauslid (ennetähtaegne tagastamine)
Kaitseklauslid väljuvad funktsioonist varem, kui eeltingimused nurjuvad, kõrvaldades muu haru ja vähendades pesastamise sügavust.
püüton
# 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 ei vähene, samad otsustuspunktid jäävad alles, kuid koodi muutub palju lihtsamini loetavaks ja testitavaks. Tõeline CC vähendamine eeldab otsustuspunktide kõrvaldamist, mitte ainult nende ümberkorraldamist.
Ekstraheerimismeetodid
Loogiliste otsustusrühmade viimine nimetatud meetoditesse vähendab kutsuva funktsiooni komplekssust (CC), jaotades samal ajal keerukuse väiksemate, testitavate üksuste vahel.
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);
}
Tingimuslausete asendamine polümorfismiga
Kui funktsioon hargneb tüübi või oleku alusel, siis polümorfism välistab hargnemise täielikult.
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
}
Komplekssete tingimuslausete lagundamine
Ekstraheeri keerulised tõeväärtusavaldised nimega meetoditeks, mis paljastavad nende kavatsuse.
püüton
# 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()
Tsüklomaatiline keerukus CI/CD torujuhtmetes
Automatiseeritud koodikontrolli jõustamine CI/CD torujuhtmetes hoiab ära keerukuse nähtamatu kuhjumise koodiülevaatuste vahel.
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)
Java jaoks koos SonarQube'iga:
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
Kvaliteedikontroll peaks blokeerima uue keeruka koodi, mitte kogu pärandkoodi baasi, millel võib juba olla kõrge CC-väärtus, mida eraldi käsitletakse.
Tsüklomaatiline keerukus COBOL-i ja pärandkoodibaasides
Ettevõtte süsteemide puhul, kus COBOL-programmide lõikudes võib olla üle 50 või isegi 100 CC-ga lõike, on tsüklomaatilisel keerukusanalüüsil teistsugune eesmärk kui tänapäevaste koodibaaside puhul. Küsimus ei ole selles, kas peaksime seda funktsiooni ümber kujundama, vaid selles, milline on selle programmi migratsioonirisk ja millises järjekorras peaksime seda moderniseerima.
COBOL-programmil, mille CC = 80 kogu põhilõigus, on 80 sõltumatut teostusrada, millest igaüks nõuab valideerimiseks testijuhtumit. Kui programmil puuduvad testijuhtumid, mis enamikul vanematel COBOL-programmidel puuduvad, on CC-skoor peamine ennustaja selle kohta, mitu valideerimisstsenaariumi tuleb luua, enne kui mis tahes konversioonitööd saab pidada ohutuks.
SMART TS XL'S staatilise koodi analüüs arvutab tsüklomaatilist keerukust COBOLi, JCLi, RPG, PL/I ja kõigi tänapäevaste keelte jaoks samaaegselt, genereerides portfelli tasemel CC-jaotuse, mis muudab moderniseerimise järjestamise otsused tõenduspõhiseks. Programmid, millel on kõrgeimad CC-skoorid ja kõige rohkem helistajaid (kõrge fan-in), on kõige suurema riskiga migratsiooni sihtmärgid, nagu on kirjeldatud kontekstis COBOL-rakenduste hooldatavuse indeksi mõõdikute loomine, CC on üks komponent laiemast kvaliteedipildist, mis hõlmab ka Halsteadi mahtu ja koodiridu.
Mõjuanalüüsi võimalus kasutab CC-põhist keerukusklassifikatsiooni, et teha kindlaks , mida tuleb valideerida mis tahes keeruka programmi muutmisel: kõrgem CC tähendab rohkem täitmisradasid, mis tähendab rohkem testistsenaariume, mida tuleb enne ja pärast mis tahes muudatust käitumusliku samaväärsuse kinnitamiseks kontrollida.
Meeskondade jaoks, kes planeerivad pärandmoderniseerimisprogramme , on migratsioonilaine järjestuse sisendiks krediidikontrolli jaotus portfellis: madala krediidikontrolliga programmid, millel on vähe helistajaid, migreeruvad varakult; kõrge krediidikontrolliga programmid, millel on palju helistajaid, migreeruvad viimasena, pärast seda, kui meeskond on omandanud oskusteabe lihtsamate komponentide osas ja keerukamate valideerimiseks on olemas testimise infrastruktuur.
Keerukus pole vaenlane, nähtamatu keerukus on
Tsüklomaatiline keerukus on üks väheseid koodi kvaliteedi mõõdikuid, mis on otseselt seotud testitavusega. Täieliku tee katmiseks vajalike testide arv on definitsiooni järgi vähemalt võrdne CC-skooriga. See seos muudab selle praktiliselt rakendatavaks viisil, mida paljud kvaliteedimõõdikud ei ole.
KK haldamise distsipliin eeldab arusaamist, et keerukus kuhjub järk-järgult. Iga if Kasvavale funktsioonile lisatud lause on individuaalselt mõistlik. Kolme aasta jooksul tehtud individuaalselt mõistlike otsuste tulemuseks võib olla funktsioon CC = 40, mida keegi ei taha puutuda, sest see on tegelikult liiga keeruline, et selle üle ohutult arutleda. Selle juhendi tööriistad ja tehnikad – kaitseklauslid, meetodite eraldamine, polümorfism, tingimuslik lagundamine ja CI/CD kvaliteedikontroll – on olemas selleks, et vältida sellise akumuleerumise nähtamatut toimumist ja sellega süstemaatiliselt tegeleda, kui see juba toimub.