MAINT001-cyclomatic — Cyclomatic complexity
| Property | Value |
|---|---|
| Dimension | Maintainability |
| Default severity | Medium |
| Languages | All |
What it detects
Flags functions whose cyclomatic complexity exceeds a configurable threshold. Cyclomatic complexity measures the number of independent execution paths through a function: higher values indicate code that is harder to test, understand, and maintain. The default threshold is 10.
Why it matters
High cyclomatic complexity correlates with both defect density and maintenance cost. Functions exceeding a reasonable threshold are candidates for refactoring into smaller, single-purpose helpers. See Thomas J. McCabe, "A Complexity Measure" for the foundational definition.
Configuration
| Parameter | Type | Default | Notes |
|---|---|---|---|
threshold | u32 | 10 | Functions at or below this value are not flagged. |
Set in zuit.toml:
[rules.MAINT001-cyclomatic]
threshold = 10
How it's counted
Rust
Starting baseline: 1 (every function has one path).
Add +1 for each of:
ifexpression (includingif let)else ifarm (each additionalelse ifis a separateifnode)while/while letloopforlooploopwith a conditionalbreakexpression (eachbreak <expr>counts)- Each
matcharm beyond the first (so a 3-arm match adds +2) ?operator (try expressions; eachExprTryin the body)- Short-circuit
&&and||binary operators (each occurrence adds +1)
Python
Starting baseline: 1 (every function has one path).
Add +1 for each of:
ifstatement (the initialiftest; eachelifbranch is a separateIfnode in the AST, so each also counts +1)whileloopforloop (includingasync for)withstatement (includingasync with)- Each
excepthandler beyond the first (first handler path is covered by baseline) BoolOpwith operatorAndorOr: each occurrence ofand/oradds one (e.g.,a and b and c= +2)ifclause inside a comprehension (e.g.,[x for x in y if cond])
Examples — flagged
Rust:
pub fn complex_classify(x: i32, flag: bool, mode: u8) -> &'static str {
if x < 0 { // +1
if flag { // +1
"negative-flagged"
} else {
"negative"
}
} else if x == 0 { // +1
"zero"
} else if mode == 1 { // +1
if x > 100 { // +1
"large-mode1"
} else {
"small-mode1"
}
} else if mode == 2 { // +1
match x % 3 { // +2 (2 arms beyond first)
0 => "div3-mode2",
1 => "rem1-mode2",
_ => "rem2-mode2",
}
} else if flag && x > 50 { // +1 for if, +1 for &&
"flagged-large"
} else {
"other"
}
}
// Total: 1 + 8 = 9 (or higher with all branches counted)
Python:
def complex_logic(a, b, c, d, e):
if a > 0: # +1
if b > 0: # +1
if c > 0: # +1
if d > 0: # +1
if e > 0: # +1
return "all positive"
elif e < -10: # +1
return "e very negative"
else:
return "e small negative"
elif d < 0: # +1
return "d negative c positive"
elif c < 0: # +1
return "c negative b positive"
elif b < 0: # +1
return "b negative a positive"
elif a < 0: # +1
return "a negative"
return "default"
# Total: 1 + 10 = 11
Examples — not flagged
Rust:
pub fn simple_classify(x: i32) -> &'static str {
if x < 0 { // +1
"negative"
} else if x == 0 { // +1
"zero"
} else {
"positive"
}
}
// Total: 1 + 2 = 3 (below threshold)
Python:
def simple_sum(a, b, c):
return a + b + c
# Total: 1 (baseline only)
Fix guidance
- Extract helpers: Break the function into smaller, single-purpose functions. Each helper should handle one branch or concern.
- Use guard clauses: Replace deeply nested
if-elsechains with early returns or guard patterns to flatten the structure. - Consider polymorphism: If a large
match(Rust) orif-elif(Python) tree exists, consider a trait or protocol with multiple implementations. - Reduce conditionals: Look for duplicate logic that can be parameterized or consolidated.
- Test incrementally: As you refactor, run tests to ensure correctness; smaller functions are easier to unit test.
Implementation
- Source:
crates/zuit-analyzers/src/cyclomatic.rs - Complexity computation:
crates/zuit-lang-rust/src/complexity.rs,crates/zuit-lang-python/src/complexity.rs
References
- McCabe, T. J. (1976). "A Complexity Measure" — foundational definition of cyclomatic complexity.
- Cyclomatic complexity on Wikipedia