software_engineering/feature_adoption.rs
1//! # Feature Adoption and Usage Metrics
2//!
3//! **Feature adoption** measures whether the people a feature was built for
4//! actually use it, at what rate, and whether that use persists over time.
5//! Initial adoption and sustained adoption are different signals: a spike
6//! from curiosity or forced exposure is not the same as genuine, lasting
7//! value delivery. Track them separately, and measure both against the
8//! specific target audience the feature was built for, not your entire user
9//! base indiscriminately.
10//!
11//! ## Formula
12//!
13//! ```text
14//! Initial adoption % = tried_at_least_once / target_audience × 100
15//! Retained adoption % = still_using_after_n_weeks / initially_tried × 100
16//!
17//! tried_at_least_once = people in the target audience who tried the
18//! feature at least once
19//! target_audience = the specific population the feature was built
20//! for (not the whole user base)
21//! still_using_after_n_weeks = of those initial triers, how many are still
22//! using the feature after a meaningful period
23//! (e.g. four or eight weeks)
24//! initially_tried = the initial-trial count (the denominator for
25//! retention, distinct from target_audience)
26//! ```
27//!
28//! ## Why it matters
29//!
30//! A feature with high initial trial and low retention suggests
31//! discoverability worked but the feature itself did not deliver enough
32//! value to keep people coming back — a very different diagnosis, and a
33//! very different fix, than low initial trial with high retention, which
34//! suggests a genuinely valuable feature that not enough people know about.
35//! Reporting only one of the two numbers hides exactly this distinction.
36//!
37//! ## Example
38//!
39//! The topic doc's enterprise example: a collaborative-editing feature
40//! launch reported "an impressive 60% initial trial rate within the first
41//! two weeks," but "a follow-up retention read at eight weeks showed only
42//! 8% of those initial triers were still using the feature regularly" —
43//! revealing the high trial rate had been driven by a hard-to-dismiss
44//! onboarding tooltip rather than genuine, sustained interest.
45//!
46//! ```rust
47//! use software_engineering::feature_adoption::{
48//! initial_adoption_percent, retained_adoption_percent,
49//! };
50//!
51//! // 600 of a 1,000-person target audience tried the feature: 60% initial trial.
52//! let initial = initial_adoption_percent(600.0, 1_000.0).unwrap();
53//! assert!((initial - 60.0).abs() < 1e-9);
54//!
55//! // Of those 600 initial triers, only 48 (8%) were still using it at 8 weeks.
56//! let retained = retained_adoption_percent(48.0, 600.0).unwrap();
57//! assert!((retained - 8.0).abs() < 1e-9);
58//! ```
59//!
60//! ## Pitfalls
61//!
62//! - **Reporting only initial trial, never retention** — cannot distinguish
63//! curiosity or forced exposure from genuine, lasting value.
64//! - **Measuring adoption against the wrong denominator** — a feature built
65//! for a specific segment, measured against the whole user base, will
66//! always look like it has terrible adoption regardless of how well it
67//! actually serves its intended audience.
68//! - **Concluding a feature failed without investigating the specific
69//! cause** of low adoption — it may be poorly discovered, poorly
70//! explained, or simply too new for the measurement window.
71//! - **Celebrating adoption inflated by forced exposure or dark patterns**
72//! — a hard-to-dismiss modal or intrusive default is not genuine, voluntary use.
73//!
74//! ## Sources
75//!
76//! - Chapter 5.2, Feature adoption and usage metrics.
77//!
78//! Topic doc: software-engineering-metrics/locales/en-001/chapters/05-02-feature-adoption-and-usage-metrics.md
79
80/// Initial adoption percentage: target audience who tried a feature at
81/// least once.
82///
83/// Measure against the feature's specific, precisely defined target
84/// audience, not your entire user base — a feature for enterprise
85/// administrators measured against a mostly individual-user base will
86/// always look like it has terrible adoption, regardless of how well it
87/// actually serves the people it was built for.
88///
89/// # Arguments
90///
91/// * `tried_at_least_once` — count of the target audience who tried the
92/// feature at least once.
93/// * `target_audience` — size of the specific population the feature was
94/// built for.
95///
96/// # Returns
97///
98/// `Some(percentage)` (e.g. `60.0` for 60%), or `None` when
99/// `target_audience` is zero (no audience — adoption undefined).
100///
101/// # Examples
102///
103/// ```rust
104/// use software_engineering::feature_adoption::initial_adoption_percent;
105///
106/// // 600 of 1,000 in the target audience tried the feature: 60% initial trial.
107/// assert_eq!(initial_adoption_percent(600.0, 1_000.0), Some(60.0));
108/// assert_eq!(initial_adoption_percent(1.0, 0.0), None);
109/// ```
110#[must_use]
111pub fn initial_adoption_percent(
112 tried_at_least_once: f64,
113 target_audience: f64,
114) -> Option<f64> {
115 if target_audience == 0.0 {
116 None
117 } else {
118 Some(tried_at_least_once / target_audience * 100.0)
119 }
120}
121
122/// Retained adoption percentage: initial triers still using the feature
123/// after a meaningful period (e.g. four or eight weeks).
124///
125/// A feature with high initial trial and low retention suggests
126/// discoverability worked but the feature itself did not deliver enough
127/// value to keep people coming back. Always report this alongside initial
128/// adoption, never in isolation.
129///
130/// # Arguments
131///
132/// * `still_using_after_n_weeks` — count of initial triers still using the
133/// feature after the chosen follow-up period.
134/// * `initially_tried` — count of people who tried the feature at least
135/// once (the retention denominator).
136///
137/// # Returns
138///
139/// `Some(percentage)` (e.g. `8.0` for 8%), or `None` when `initially_tried`
140/// is zero (no initial triers — retention undefined).
141///
142/// # Examples
143///
144/// ```rust
145/// use software_engineering::feature_adoption::retained_adoption_percent;
146///
147/// // Only 48 of 600 initial triers (8%) were still using it at 8 weeks.
148/// assert_eq!(retained_adoption_percent(48.0, 600.0), Some(8.0));
149/// assert_eq!(retained_adoption_percent(1.0, 0.0), None);
150/// ```
151#[must_use]
152pub fn retained_adoption_percent(
153 still_using_after_n_weeks: f64,
154 initially_tried: f64,
155) -> Option<f64> {
156 if initially_tried == 0.0 {
157 None
158 } else {
159 Some(still_using_after_n_weeks / initially_tried * 100.0)
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 // "an impressive 60% initial trial rate within the first two weeks."
168 #[test]
169 fn collaborative_editing_feature_had_60_percent_initial_trial() {
170 let initial = initial_adoption_percent(600.0, 1_000.0).unwrap();
171 assert!((initial - 60.0).abs() < 1e-9);
172 }
173
174 // "a follow-up retention read at eight weeks showed only 8% of those
175 // initial triers were still using the feature regularly."
176 #[test]
177 fn only_8_percent_of_initial_triers_were_retained_at_8_weeks() {
178 let retained = retained_adoption_percent(48.0, 600.0).unwrap();
179 assert!((retained - 8.0).abs() < 1e-9);
180 }
181
182 // "Adoption should be measured against the audience it was built for,
183 // not against your entire user base indiscriminately" — a zero-size
184 // audience or zero initial triers leaves the rate undefined.
185 #[test]
186 fn adoption_is_undefined_with_a_zero_denominator() {
187 assert!(initial_adoption_percent(10.0, 0.0).is_none());
188 assert!(retained_adoption_percent(10.0, 0.0).is_none());
189 }
190}