software_engineering/code_complexity.rs
1//! # Code Complexity Metrics
2//!
3//! **Cyclomatic complexity**, introduced by Thomas J. `McCabe` in 1976, counts
4//! the number of independent paths through a piece of code's control flow:
5//! each `if`, loop, and branch adds to the count. Code with more independent
6//! paths through it is harder to fully test, harder to reason about, and,
7//! in decades of empirical research, measurably more likely to contain
8//! defects.
9//!
10//! ## Formula
11//!
12//! ```text
13//! Cyclomatic complexity = edges - nodes + 2
14//!
15//! edges = control-flow graph edges
16//! nodes = control-flow graph nodes
17//! ```
18//!
19//! ## Why it matters
20//!
21//! Complexity metrics predict testing and defect difficulty; they do not
22//! measure quality directly. For large teams they earn their keep as a
23//! triage tool: a way to find, among thousands of files, the small subset
24//! most likely to reward a closer look, not as a standalone verdict on code
25//! quality.
26//!
27//! ## Example
28//!
29//! ```rust
30//! use software_engineering::code_complexity::cyclomatic_complexity;
31//!
32//! // A straight-line function with no branches: 1 node, 0 back-edges
33//! // beyond the single entry/exit edge — McCabe's minimum score is 1.
34//! // Graph: 2 nodes (entry, exit), 1 edge: 1 - 2 + 2 = 1.
35//! assert_eq!(cyclomatic_complexity(1, 2), 1);
36//!
37//! // A single `if` adds one more independent path: 3 edges, 3 nodes.
38//! assert_eq!(cyclomatic_complexity(3, 3), 2);
39//! ```
40//!
41//! ## Pitfalls
42//!
43//! - **Treating a complexity score as a direct quality verdict** — it
44//! measures one specific property, not overall code quality.
45//! - **Decomposition gaming**: splitting a function to lower the score
46//! without genuinely simplifying anything, sometimes scattering the logic
47//! across more files and making it harder to follow.
48//! - **Applying a universal threshold without calibrating to your own
49//! codebase** — a parser or rules engine may have legitimately higher
50//! baseline complexity than a typical CRUD service.
51//! - **Using complexity metrics to individually evaluate engineers**
52//! invites gaming and misapplies a metric meant for triage, not judgement.
53//!
54//! ## Sources
55//!
56//! - Chapter 4.1, Code complexity metrics.
57//! - `McCabe`, Thomas J., "A Complexity Measure," *IEEE Transactions on
58//! Software Engineering* (1976).
59//!
60//! Topic doc: software-engineering-metrics/locales/en-001/chapters/04-01-code-complexity-metrics.md
61
62/// `McCabe` cyclomatic complexity: independent paths through control flow.
63///
64/// `edges - nodes + 2`, computed on the function's control-flow graph. A
65/// straight-line function with no branches scores 1 (the minimum); each
66/// additional decision point (`if`, loop, `case` arm, and similar) adds one.
67///
68/// # Arguments
69///
70/// * `edges` — number of edges in the control-flow graph.
71/// * `nodes` — number of nodes in the control-flow graph.
72///
73/// # Returns
74///
75/// The cyclomatic complexity score (an integer; can be negative for a
76/// malformed or disconnected graph, which the caller should treat as
77/// invalid input).
78///
79/// # Examples
80///
81/// ```rust
82/// use software_engineering::code_complexity::cyclomatic_complexity;
83///
84/// // Straight-line function: 2 nodes, 1 edge -> complexity 1.
85/// assert_eq!(cyclomatic_complexity(1, 2), 1);
86/// ```
87#[must_use]
88pub fn cyclomatic_complexity(edges: i64, nodes: i64) -> i64 {
89 edges - nodes + 2
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 // "each `if`, loop, and branch adds to the count" — a straight-line
97 // function (no branches) has McCabe's documented minimum score of 1.
98 #[test]
99 fn straight_line_function_has_complexity_one() {
100 assert_eq!(cyclomatic_complexity(1, 2), 1);
101 }
102
103 // Adding one decision point (one `if`) adds exactly one independent
104 // path, per McCabe's formula.
105 #[test]
106 fn single_if_branch_adds_one_independent_path() {
107 assert_eq!(cyclomatic_complexity(3, 3), 2);
108 }
109
110 // "a single transaction-validation function with a cyclomatic
111 // complexity score more than ten times the codebase's median" — a
112 // sanity check that larger graphs produce proportionally larger scores.
113 #[test]
114 fn more_decision_points_yield_higher_complexity() {
115 let simple = cyclomatic_complexity(1, 2);
116 let complex = cyclomatic_complexity(21, 12); // 11 decision points
117 assert!(complex > simple * 10);
118 }
119}