Skip to main content

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}