software_engineering/cycle_time.rs
1//! # Cycle Time and Its Components
2//!
3//! Cycle time is the internal breakdown of a change's flow time (see
4//! [`crate::flow_framework`]) into its constituent engineering stages:
5//! coding, pickup (waiting for a reviewer to start), review, test, and
6//! deploy. Where flow time gives a single end-to-end number, cycle time
7//! tells you where that time actually goes once work reaches engineering —
8//! the diagnostic layer underneath the summary number.
9//!
10//! ## Formula
11//!
12//! ```text
13//! Cycle time = coding + pickup + review + test + deploy
14//!
15//! coding = first commit → pull request opened
16//! pickup = pull request opened → first review
17//! review = first review → approval
18//! test = time spent in automated/manual verification
19//! deploy = approval → production
20//! ```
21//!
22//! ## Why it matters
23//!
24//! "Lead time is too long" is not actionable on its own. A team whose lead
25//! time is dominated by coding time needs a different intervention than a
26//! team whose lead time is dominated by a three-day review queue, which
27//! needs a different intervention again than a team losing most of its time
28//! to a flaky, slow test suite. Without cycle-time decomposition, teams
29//! guess at the bottleneck, and the guess is wrong often enough that fixing
30//! the wrong stage wastes real effort while the actual constraint stays
31//! untouched. Decomposition by stage also reveals shared, cross-team
32//! bottlenecks — for example a single overloaded shared review pool — that
33//! no individual team's own metrics can see on their own.
34//!
35//! ## Example
36//!
37//! A change with 2 days of coding, 0.5 days waiting for pickup, 1.5 days in
38//! review, 0.5 days in test, and 0.5 days to deploy has a cycle time of 5
39//! days; review is 30% of the total, the largest single share.
40//!
41//! ```rust
42//! use software_engineering::cycle_time::{cycle_time, stage_percent_of_cycle};
43//!
44//! let total = cycle_time(2.0, 0.5, 1.5, 0.5, 0.5);
45//! assert_eq!(total, 5.0);
46//!
47//! let review_share = stage_percent_of_cycle(1.5, total).unwrap();
48//! assert!((review_share - 30.0).abs() < 1e-9);
49//! ```
50//!
51//! ## Pitfalls
52//!
53//! - **Reacting to a lead-time regression without cycle-time diagnosis**:
54//! frequently leads to fixing the wrong stage.
55//! - **Assuming active effort, not wait time, is the dominant cost**:
56//! usually wrong — queueing dominates in most real delivery pipelines (see
57//! [`crate::flow_framework::flow_efficiency_percent`]).
58//! - **Missing a shared, cross-team bottleneck** by reviewing cycle time
59//! team by team only, rather than aggregating across teams.
60//! - **Stage-boundary definitional drift**: marking a stage "started" or
61//! "finished" earlier or later than its documented definition flatters a
62//! number without real improvement. Audit stage-boundary instrumentation
63//! periodically against its documented definition.
64//!
65//! ## Sources
66//!
67//! - Chapter 2.6, "Cycle time and its components."
68//!
69//! Topic doc: software-engineering-metrics/locales/en-001/chapters/02-06-cycle-time-and-its-components.md
70
71/// Cycle time: the sum of a change's five named engineering stages.
72///
73/// # Arguments
74///
75/// * `coding` — first commit to pull request opened.
76/// * `pickup` — pull request opened to first review.
77/// * `review` — first review to approval.
78/// * `test` — time spent in automated/manual verification.
79/// * `deploy` — approval to production.
80///
81/// # Returns
82///
83/// The total cycle time, in whatever unit the stage durations are expressed.
84///
85/// # Examples
86///
87/// ```rust
88/// use software_engineering::cycle_time::cycle_time;
89///
90/// let total = cycle_time(2.0, 0.5, 1.5, 0.5, 0.5);
91/// assert_eq!(total, 5.0);
92/// ```
93#[must_use]
94pub fn cycle_time(coding: f64, pickup: f64, review: f64, test: f64, deploy: f64) -> f64 {
95 coding + pickup + review + test + deploy
96}
97
98/// A single stage's share of total cycle time, as a percentage.
99///
100/// Use this to set stage-specific improvement targets ("reduce median review
101/// wait time from two days to four hours") rather than a vague overall
102/// "reduce lead time by 20%" goal that gives a team no guidance on where to
103/// focus.
104///
105/// # Arguments
106///
107/// * `stage_duration` — the duration of one stage.
108/// * `total_cycle_time` — the total cycle time across all stages.
109///
110/// # Returns
111///
112/// `Some(percentage)` (e.g. `30.0` for 30%), or `None` when
113/// `total_cycle_time` is zero.
114///
115/// # Examples
116///
117/// ```rust
118/// use software_engineering::cycle_time::stage_percent_of_cycle;
119///
120/// let review_share = stage_percent_of_cycle(1.5, 5.0).unwrap();
121/// assert!((review_share - 30.0).abs() < 1e-9);
122/// assert_eq!(stage_percent_of_cycle(1.5, 0.0), None);
123/// ```
124#[must_use]
125pub fn stage_percent_of_cycle(stage_duration: f64, total_cycle_time: f64) -> Option<f64> {
126 if total_cycle_time == 0.0 {
127 None
128 } else {
129 Some(stage_duration / total_cycle_time * 100.0)
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 // "Cycle time is the internal breakdown of a change's flow time into its
138 // constituent engineering stages: coding time, review time, testing
139 // time, and deploy time... further split into pickup time."
140 #[test]
141 fn cycle_time_sums_the_five_named_stages() {
142 let total = cycle_time(2.0, 0.5, 1.5, 0.5, 0.5);
143 assert!((total - 5.0).abs() < 1e-9);
144 }
145
146 // Worked example: review is the largest single share of a 5-day cycle.
147 #[test]
148 fn review_stage_is_thirty_percent_of_cycle_time() {
149 let total = cycle_time(2.0, 0.5, 1.5, 0.5, 0.5);
150 let review_share = stage_percent_of_cycle(1.5, total).unwrap();
151 assert!((review_share - 30.0).abs() < 1e-9);
152 assert!(stage_percent_of_cycle(1.5, 0.0).is_none());
153 }
154}