Skip to main content

software_engineering/
code_churn.rs

1//! # Code Churn and Hotspot Analysis
2//!
3//! **Code churn** measures how frequently a file or module changes over
4//! time — lines added, modified, and deleted across successive commits. On
5//! its own, churn is a fairly weak signal: some files change often because
6//! they are under active, healthy development, and some rarely change
7//! because they are stable, not neglected. The diagnostic power comes from
8//! combining churn with complexity: a file that is both frequently changed
9//! and highly complex — a **hotspot** — is disproportionately likely to be
10//! a source of defects and a drag on team velocity.
11//!
12//! ## Formula
13//!
14//! ```text
15//! Code churn    = lines added + lines modified + lines deleted
16//! Hotspot score = churn × complexity
17//!
18//! churn      = change volume for a file over a window (commonly 6-12 months)
19//! complexity = a static-complexity measure for the same file (chapter 4.1)
20//! ```
21//!
22//! ## Why it matters
23//!
24//! Hotspot analysis requires no manual survey: version control history
25//! already contains everything needed to compute churn, and combined with
26//! static analysis tooling, complexity, for every file automatically. Rank
27//! files by the combination — commonly the product of churn and
28//! complexity — rather than by either metric alone, since research
29//! consistently associates that combination with elevated defect rates and
30//! maintenance cost.
31//!
32//! ## Example
33//!
34//! The topic doc's government example: a licensing system's hotspot
35//! analysis identified a cluster of files representing under 3% of the
36//! total codebase that accounted for nearly 40% of all reported system
37//! defects over the previous three years — a small, high-churn,
38//! high-complexity cluster outranking the rest of the codebase combined.
39//!
40//! ```rust
41//! use software_engineering::code_churn::{code_churn, hotspot_score};
42//!
43//! // A small cluster with heavy churn (many changed lines)...
44//! let cluster_churn = code_churn(220.0, 140.0, 60.0);
45//! assert_eq!(cluster_churn, 420.0);
46//!
47//! // ...and high complexity outranks a large, low-churn, low-complexity
48//! // file, exactly the "churn combined with complexity" ranking method.
49//! let cluster_score = hotspot_score(cluster_churn, 12.0);
50//! let quiet_file_score = hotspot_score(code_churn(30.0, 10.0, 5.0), 3.0);
51//! assert!(cluster_score > quiet_file_score);
52//! ```
53//!
54//! ## Pitfalls
55//!
56//! - **Using churn alone without complexity** — a weak signal on its own
57//!   that can flag healthy, actively developed code as a false positive.
58//! - **Treating a hotspot ranking as an automatic action list** with no
59//!   human judgement — misses whether the change is essential or accidental
60//!   complexity.
61//! - **Prioritizing refactoring by the loudest complaint** rather than the
62//!   evidence, which frequently misdirects effort away from where the data
63//!   shows the problem actually lives.
64//! - **Never cross-referencing hotspots against incident or defect data**,
65//!   missing the validation step that strengthens the case for acting.
66//! - **Running the analysis once and never repeating it**, missing whether
67//!   remediation is actually working over time.
68//!
69//! ## Sources
70//!
71//! - Chapter 4.3, Code churn and hotspot analysis.
72//! - Tornhill, Adam, *Your Code as a Crime Scene*.
73//! - Nagappan, Nachiappan, and Thomas Ball, "Use of Relative Code Churn
74//!   Measures to Predict System Defect Density," *ICSE* (2005).
75//!
76//! Topic doc: software-engineering-metrics/locales/en-001/chapters/04-03-code-churn-and-hotspot-analysis.md
77
78/// Code churn: lines added + lines modified + lines deleted over a window.
79///
80/// Churn on its own is a weak signal — pair it with a complexity measure
81/// via [`hotspot_score`] to identify genuine hotspots rather than merely
82/// actively developed files.
83///
84/// # Arguments
85///
86/// * `lines_added` — lines added across the commits in the window.
87/// * `lines_modified` — lines modified across the commits in the window.
88/// * `lines_deleted` — lines deleted across the commits in the window.
89///
90/// # Returns
91///
92/// The total churn (sum of added, modified, and deleted lines).
93///
94/// # Examples
95///
96/// ```rust
97/// use software_engineering::code_churn::code_churn;
98///
99/// // "lines added, modified, and deleted across successive commits."
100/// assert_eq!(code_churn(220.0, 140.0, 60.0), 420.0);
101/// ```
102#[must_use]
103pub fn code_churn(lines_added: f64, lines_modified: f64, lines_deleted: f64) -> f64 {
104    lines_added + lines_modified + lines_deleted
105}
106
107/// Hotspot score: churn × complexity, the combined ranking signal.
108///
109/// Rank files by this combination, not by either churn or complexity
110/// alone — the underlying research consistently associates the combination
111/// with elevated defect rates and maintenance cost. A hotspot ranking is a
112/// prioritization signal, not an automatic verdict; investigate top-ranked
113/// files with human judgement before acting.
114///
115/// # Arguments
116///
117/// * `churn` — a file's code churn over the analysis window (see
118///   [`code_churn`]).
119/// * `complexity` — a complexity measure for the same file (chapter 4.1).
120///
121/// # Returns
122///
123/// The hotspot score (higher indicates a stronger hotspot candidate).
124///
125/// # Examples
126///
127/// ```rust
128/// use software_engineering::code_churn::hotspot_score;
129///
130/// // "rank files by the combination, commonly the product of churn and
131/// // complexity, rather than by either metric alone."
132/// assert_eq!(hotspot_score(420.0, 12.0), 5_040.0);
133/// ```
134#[must_use]
135pub fn hotspot_score(churn: f64, complexity: f64) -> f64 {
136    churn * complexity
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    // "lines added, modified, and deleted across successive commits."
144    #[test]
145    fn code_churn_sums_added_modified_and_deleted_lines() {
146        assert!((code_churn(220.0, 140.0, 60.0) - 420.0).abs() < 1e-9);
147    }
148
149    // "rank files by the combination, commonly the product of churn and
150    // complexity, rather than by either metric alone."
151    #[test]
152    fn hotspot_score_is_the_product_of_churn_and_complexity() {
153        assert!((hotspot_score(420.0, 12.0) - 5_040.0).abs() < 1e-9);
154    }
155
156    // "a small cluster of files, representing under 3% of the total
157    // codebase, ... accounted for nearly 40% of all reported system
158    // defects" — a small, high-churn, high-complexity cluster outranks a
159    // larger, quieter file.
160    #[test]
161    fn small_high_churn_high_complexity_cluster_outranks_a_quiet_file() {
162        let cluster_score = hotspot_score(code_churn(220.0, 140.0, 60.0), 12.0);
163        let quiet_file_score = hotspot_score(code_churn(30.0, 10.0, 5.0), 3.0);
164        assert!(cluster_score > quiet_file_score);
165    }
166
167    // "Churn alone is a weak signal" — churn with zero complexity produces
168    // a zero hotspot score, illustrating that churn by itself does not
169    // drive the ranking.
170    #[test]
171    fn churn_alone_without_complexity_scores_zero() {
172        assert!((hotspot_score(code_churn(500.0, 200.0, 100.0), 0.0) - 0.0).abs() < 1e-9);
173    }
174}