Skip to main content

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}