software_engineering/documentation_and_knowledge_metrics.rs
1//! # Documentation and Knowledge Metrics
2//!
3//! This module measures whether the knowledge needed to safely maintain
4//! a codebase is actually documented and findable, not just whether
5//! documentation technically exists somewhere: does a new engineer, or
6//! an existing one working on unfamiliar code, have what they need to
7//! make a safe change, or does that knowledge live only in the heads of
8//! a shrinking number of tenured people. A system maintained for years
9//! by the same two engineers can function perfectly well with almost no
10//! written documentation, right up until both of those engineers leave
11//! within the same year — at which point the knowledge is discovered to
12//! have never been captured anywhere durable. That risk has a standard
13//! name in the industry: the **bus factor** (or truck factor).
14//!
15//! ## Formula
16//!
17//! ```text
18//! Bus factor = the minimum number of people whose combined knowledge
19//! share meets or exceeds a critical threshold (commonly 50%)
20//!
21//! At risk when bus_factor <= minimum_safe_bus_factor
22//! ```
23//!
24//! ## Why it matters
25//!
26//! Documentation existence is not the same as documentation usefulness —
27//! counting wiki pages or READMEs tells you almost nothing about whether
28//! knowledge is actually accessible when needed. Bus factor measures the
29//! underlying risk directly: how concentrated is the knowledge needed to
30//! safely change a system. A low bus factor can hide behind apparent
31//! stability — a system that has not changed in years is not necessarily
32//! low-risk, it may simply not have needed its sole expert yet — and
33//! discovering the gap only during an emergency staff transition is
34//! exactly the expensive, avoidable failure mode this module exists to
35//! surface in advance.
36//!
37//! ## Example
38//!
39//! ```rust
40//! use software_engineering::documentation_and_knowledge_metrics::{
41//! bus_factor, is_bus_factor_at_risk,
42//! };
43//!
44//! // One person holds 60% of the knowledge share for a system: a single
45//! // departure alone crosses the 50% critical threshold.
46//! let concentrated = bus_factor(&[60.0, 25.0, 15.0], 50.0).unwrap();
47//! assert_eq!(concentrated, 1);
48//! assert!(is_bus_factor_at_risk(concentrated, 2));
49//!
50//! // Five people each hold an even 20% share: it takes three departures
51//! // to cross the same threshold.
52//! let spread_out = bus_factor(&[20.0, 20.0, 20.0, 20.0, 20.0], 50.0).unwrap();
53//! assert_eq!(spread_out, 3);
54//! assert!(!is_bus_factor_at_risk(spread_out, 2));
55//! ```
56//!
57//! ## Pitfalls
58//!
59//! - **Counting documentation existence rather than usefulness** — tells
60//! you almost nothing about whether knowledge is actually accessible
61//! when needed.
62//! - **Never checking documentation staleness relative to how much the
63//! system has changed** — risks actively misleading, out-of-date
64//! content.
65//! - **Mistaking apparent stability for low risk** — a system that has
66//! not changed in years can mask a severe, undocumented bus-factor
67//! problem behind a system that simply has not yet needed its sole
68//! expert.
69//! - **Discovering critical undocumented knowledge only during an
70//! emergency staff transition** — the expensive, avoidable failure mode
71//! this chapter is built to prevent.
72//!
73//! ## Sources
74//!
75//! - Chapter 4.6, Documentation and knowledge metrics.
76//! - The "bus factor" (or "truck factor") is a widely used, informally
77//! named industry concept for knowledge-concentration risk.
78//!
79//! Topic doc: software-engineering-metrics/locales/en-001/chapters/04-06-documentation-and-knowledge-metrics.md
80
81/// The bus factor: the minimum number of people whose combined knowledge
82/// share meets or exceeds `critical_threshold_percent` (commonly `50.0`).
83///
84/// Sorts a copy of `knowledge_shares_percent` in descending order and
85/// accumulates from the largest share down, counting how many people it
86/// takes to reach the threshold. A low result means knowledge is
87/// dangerously concentrated in a few people; a high result means it is
88/// spread widely.
89///
90/// # Arguments
91///
92/// * `knowledge_shares_percent` — each person's percentage share of
93/// understanding or ownership of the system. Values are assumed to be
94/// finite, non-NaN percentages (they need not sum to exactly `100.0`).
95/// * `critical_threshold_percent` — the combined share (e.g. `50.0`)
96/// whose loss is considered critical.
97///
98/// # Returns
99///
100/// The number of people whose combined share reaches the threshold, or
101/// `None` if `knowledge_shares_percent` is empty, or if the shares never
102/// reach `critical_threshold_percent` even after summing all of them.
103///
104/// # Examples
105///
106/// ```rust
107/// use software_engineering::documentation_and_knowledge_metrics::bus_factor;
108///
109/// // One person alone holds 60%, past the 50% threshold.
110/// assert_eq!(bus_factor(&[60.0, 25.0, 15.0], 50.0), Some(1));
111///
112/// // Five even 20% shares need three of them to cross 50%.
113/// assert_eq!(bus_factor(&[20.0, 20.0, 20.0, 20.0, 20.0], 50.0), Some(3));
114///
115/// assert_eq!(bus_factor(&[], 50.0), None);
116/// // Shares that never reach the threshold even combined.
117/// assert_eq!(bus_factor(&[10.0, 10.0], 50.0), None);
118/// ```
119#[must_use]
120pub fn bus_factor(knowledge_shares_percent: &[f64], critical_threshold_percent: f64) -> Option<usize> {
121 if knowledge_shares_percent.is_empty() {
122 return None;
123 }
124 let mut shares: Vec<f64> = knowledge_shares_percent.to_vec();
125 // total_cmp gives a full ordering even for NaN, so this never panics.
126 shares.sort_by(|a, b| b.total_cmp(a));
127
128 let mut running_total = 0.0;
129 for (index, share) in shares.iter().enumerate() {
130 running_total += share;
131 if running_total >= critical_threshold_percent {
132 return Some(index + 1);
133 }
134 }
135 None
136}
137
138/// Whether a bus factor is at or below a defined minimum-safe threshold
139/// (e.g. a bus factor of 1 or 2 is commonly considered dangerously low).
140///
141/// True iff `bus_factor <= minimum_safe_bus_factor`.
142///
143/// # Arguments
144///
145/// * `bus_factor` — a bus factor computed by [`bus_factor`].
146/// * `minimum_safe_bus_factor` — the smallest bus factor considered
147/// acceptable.
148///
149/// # Returns
150///
151/// `true` if the bus factor is at or below the minimum-safe threshold.
152///
153/// # Examples
154///
155/// ```rust
156/// use software_engineering::documentation_and_knowledge_metrics::is_bus_factor_at_risk;
157///
158/// assert!(is_bus_factor_at_risk(1, 2));
159/// assert!(!is_bus_factor_at_risk(3, 2));
160/// ```
161#[must_use]
162pub fn is_bus_factor_at_risk(bus_factor: usize, minimum_safe_bus_factor: usize) -> bool {
163 bus_factor <= minimum_safe_bus_factor
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 // "does that knowledge live only in the heads of a shrinking number
171 // of tenured people" — one person holding a majority share is the
172 // clearest, most concentrated case.
173 #[test]
174 fn one_concentrated_share_gives_a_bus_factor_of_one() {
175 assert_eq!(bus_factor(&[60.0, 25.0, 15.0], 50.0), Some(1));
176 }
177
178 #[test]
179 fn evenly_spread_shares_need_more_people_to_reach_the_threshold() {
180 assert_eq!(bus_factor(&[20.0, 20.0, 20.0, 20.0, 20.0], 50.0), Some(3));
181 }
182
183 #[test]
184 fn bus_factor_is_none_for_empty_shares() {
185 assert_eq!(bus_factor(&[], 50.0), None);
186 }
187
188 #[test]
189 fn bus_factor_is_none_when_threshold_is_never_reached() {
190 assert_eq!(bus_factor(&[10.0, 10.0], 50.0), None);
191 }
192
193 // "a system maintained for years by the same two engineers can
194 // function perfectly well... right up until both of those engineers
195 // leave within the same year" — a bus factor of 1 or 2 is exactly
196 // the dangerous case this chapter warns about.
197 #[test]
198 fn a_low_bus_factor_is_flagged_as_at_risk() {
199 assert!(is_bus_factor_at_risk(1, 2));
200 }
201
202 #[test]
203 fn a_high_bus_factor_is_not_flagged_as_at_risk() {
204 assert!(!is_bus_factor_at_risk(3, 2));
205 }
206}