Module 7: Documentation That Survives
Document the stable, not the volatile
Overview
The previous lesson assembled the documentation system —C4, ADR, arc42— and ended with a warning: the system only survives if you fill it with the right information. This lesson is about which is the right information, and its answer is the most important and most ignored rule in all of documentation: document the stable, not the volatile. The stable is what changes little —the boundaries between modules, the architecture decisions, the why of things—; the volatile is what changes all the time —the exact list of endpoints, the function signatures, the configuration values, the task status—. Many people's intuition is to document the volatile, because it's the most concrete and visible ("here's the list of all our endpoints"). And it's exactly the mistake: the volatile becomes obsolete before anyone reads it, while the stable —what really helps— goes undocumented because there wasn't enough time.
The deep reason is economic, and this lesson measures it. Each piece of documentation yields (someone reads it and it saves them time) but costs to keep it synchronized every time the documented thing changes. The ROI of documenting something is benefit minus maintenance cost. And here's the twist almost no one sees: the variable that decides the ROI isn't the importance of the information, but its volatility (its churn). Something very important but very volatile —the endpoint list is important— has negative ROI, because keeping it synchronized costs more than it yields: it changes so often that either you pay a fortune in updates or the doc rots and is worth zero (the chasm of lesson 2). Something stable, even if it seems less "concrete" —why payments is separated—, has high ROI, because it barely costs to maintain and it's read for years. This lesson executes that ROI over six typical doc pieces and shows that churn is what flips the sign.
Connection with the module. It's the lesson that says what to put inside lesson 4's system. Lessons 2 and 3 said the doc must live close to the code and stay synchronized; this one explains why, even with that discipline, you shouldn't document everything by hand —the volatile has to be generated from the code or left to the code, because documenting it by hand has negative ROI—. It closes the circle with living documentation (lesson 2): "generate the volatile, write the stable" is the same frontier seen from ROI. With the module's analogy: it's the layer of the manual that says where the main valve is (stable, useful for years) and not what color the living room is painted today (volatile, obsolete in a month). Frontier with the rest: we don't re-teach the mechanics of the ADR or C4; we use the fact that the ADR captures the stable (the why) and that's why it's what's most worth maintaining.
An analogy: the house manual vs. the sticky note on the fridge
Think of two kinds of information there are in any house, and what happens when you try to document each.
The water shut-off valve: stable information. Where the main water shut-off valve is doesn't change in years —maybe never, in the whole life of the house—. That's why it's worth documenting well: writing it in the house manual, precisely ("garden, behind the rosebush, green cover"). That fact, written once, serves today's owner, the one five years from now, the plumber who comes in an emergency. The effort of documenting it is repaid many times over, because you wrote it once and it stays true for the whole life of the house. Documenting the stable is an investment that yields for years with a single payment.
The week's menu: volatile information. Now imagine you decide to also "document" the house's meal menu, sticking it on a sticky note on the fridge: "Monday pasta, Tuesday fish...". The menu changes every week. So for the sticky note to stay true, you have to rewrite it every week —and if one day you don't rewrite it, the note lies: it says "Tuesday fish" when you already switched to chicken—. Documenting the menu is a bottomless pit: it takes constant work to keep it up to date, and the value of having it written is low (you open the fridge anyway and see what's there). The effort of documenting it never finishes paying off, because every week you have to pay it again, and if you stop paying, it becomes a lie. Documenting the volatile is a recurring expense that yields little.
Here's the rule: document the shut-off valve, not the menu. Not because the menu is less important (eating matters), but because the menu is volatile —it changes so often that documenting it by hand costs more than it yields and is always about to lie—. For the volatile, the solution isn't a sticky note you rewrite every week; it's looking in the fridge (go to the source: you open it and see what's there). In software, "looking in the fridge" is reading the code: the exact list of endpoints, the function signatures, the configuration values live in the code, which is the source of truth and is always up to date; documenting them separately by hand is rewriting the menu sticky note every week. What does go in the manual —written once, useful for years— is the stable: the shut-off valve (the boundaries), why the cistern was moved (the decisions). This lesson measures, with ROI, why the shut-off valve is worth documenting and the menu isn't.
Worked example: the ROI of documenting, and the churn that flips it
We're going to measure the ROI of documenting six typical pieces of Mercado's doc. Each piece has four numbers: how much it changes per year (its churn), how many times it's read per year, how much value each read gives (hours saved), and how much each update costs to keep it synchronized. The benefit is reads times value; the maintenance cost is churn times update cost; the ROI is the difference. The question the experiment answers: which variable decides whether the ROI is positive or negative?
# Document the STABLE, not the VOLATILE. Each doc piece yields (it's read and saves
# time) but costs to keep synchronized every time the documented thing changes.
# The ROI = benefit - maintenance cost. The stable (boundaries, decisions)
# changes little: cheap to maintain, high ROI. The volatile (endpoint list, function
# signatures, task status) changes all the time: maintaining it costs more than
# it yields -> negative ROI. That is NOT documented by hand: it's generated or left to the code.
ITEMS = [
# (piece, churn=changes/year, reads/year, value_per_read_h, cost_per_update_h)
("module_boundaries (C4)", 2, 60, 0.50, 1.00),
("key_decisions (ADR)", 1, 40, 0.75, 1.00),
("how_to_run (README)", 4, 50, 0.40, 0.50),
("api_endpoint_list", 40, 30, 0.20, 0.50),
("function_signatures", 200, 20, 0.10, 0.25),
("current_task_status", 120, 10, 0.10, 0.30),
]
print(f"{'item':<26}{'churn':>7}{'benefit':>11}{'cost':>8}{'ROI':>8}")
print("-" * 60)
rows = []
for name, churn, reads, value, cost_up in ITEMS:
benefit = reads * value
maint = churn * cost_up
roi = benefit - maint
rows.append((name, churn, benefit, maint, roi))
print(f"{name:<26}{churn:>7}{benefit:>10.0f}h{maint:>7.0f}h{roi:>+7.0f}h")
print("-" * 60)
print("\nDecision (sorted by ROI):")
for name, churn, benefit, maint, roi in sorted(rows, key=lambda r: -r[4]):
verb = "DOCUMENT by hand (stable)" if roi > 0 else "NOT by hand (volatile)"
print(f" {roi:>+6.0f}h {name:<26} -> {verb}")
print("\nThe variable that flips the ROI is the CHURN (the volatility), not the importance.")
What to expect. Running the file, the output is exactly this:
item churn benefit cost ROI
------------------------------------------------------------
module_boundaries (C4) 2 30h 2h +28h
key_decisions (ADR) 1 30h 1h +29h
how_to_run (README) 4 20h 2h +18h
api_endpoint_list 40 6h 20h -14h
function_signatures 200 2h 50h -48h
current_task_status 120 1h 36h -35h
------------------------------------------------------------
Decision (sorted by ROI):
+29h key_decisions (ADR) -> DOCUMENT by hand (stable)
+28h module_boundaries (C4) -> DOCUMENT by hand (stable)
+18h how_to_run (README) -> DOCUMENT by hand (stable)
-14h api_endpoint_list -> NOT by hand (volatile)
-35h current_task_status -> NOT by hand (volatile)
-48h function_signatures -> NOT by hand (volatile)
The variable that flips the ROI is the CHURN (the volatility), not the importance.
Read the table looking at two columns together: churn and ROI. The whole story is in their relationship.
The three pieces of positive ROI are the stable ones. key_decisions (ADR) has churn 1 —architecture decisions barely change— and ROI +29h: you write it once, barely maintain it, and it's read 40 times a year for years. module_boundaries (C4) has churn 2 and ROI +28h: the boundaries between modules change little, so documenting them yields. how_to_run (README) has churn 4 and ROI +18h: how to run the project changes now and then, but little, and every new dev reads it. These three are the water shut-off valve: stable information that, documented once, yields for years with little maintenance.
The three of negative ROI are the volatile ones. api_endpoint_list has churn 40 —the endpoints change often— and ROI −14h: even though the endpoint list is useful (benefit 6h), keeping it synchronized by hand costs 20h a year, more than it yields. function_signatures has churn 200 and ROI −48h: function signatures change constantly, and documenting them by hand is a bottomless pit. current_task_status has churn 120 and ROI −35h: the task status changes daily, documenting it by hand makes no sense. These three are the fridge menu: rewriting the sticky note every week costs more than the value of having it written.
And the heart, in the last line: the variable that flips the ROI is the CHURN, not the importance. Notice something counterintuitive. api_endpoint_list has benefit 6h and key_decisions has 30h —decisions yield five times more—, but that's not the reason one has positive ROI and the other negative. The reason is the maintenance cost, which depends on churn: key_decisions costs 1h a year in maintenance (churn 1), api_endpoint_list costs 20h (churn 40). Even if the endpoints were more important than the decisions, they'd still have negative ROI, because their volatility makes keeping them synchronized cost more than they yield. The right question before documenting something isn't "is it important?" but "does it change little?". The important-and-volatile isn't documented by hand; the important-and-stable is.
So, what do you do with the important-and-volatile, like the endpoint list, which is useful? You don't document it by hand —you generate it from the code—. The endpoint list comes from the router (a generated OpenAPI); the signatures live in the code and are read there or generated; the task status lives in the tracker, not in the architecture doc. Generating from the source has zero maintenance cost (it updates itself when the code changes), so it turns the negative ROI of documenting-by-hand into a positive ROI of generating. It's the exact connection with living documentation (lesson 2): high churn doesn't mean "don't document this information", it means "don't document it by hand: generate it from the code, where it can't rot".
As a chart, the ROI against the churn looks like this:
ROI (hours/year) by the churn of what's documented
key_decisions (churn 1) +29h ############### STABLE -> document by hand
module_boundaries (churn 2)+28h ############## STABLE -> document by hand
how_to_run (churn 4) +18h ######### STABLE -> document by hand
─────────────────────────────── 0 ───────────────────────────────
api_endpoint_list (churn 40)-14h ###### VOLATILE -> generate from the code
current_task_status(churn120)-35h ########## VOLATILE -> lives in the tracker
function_signatures(churn200)-48h ############# VOLATILE -> lives in the code
More churn, more negative the ROI. Churn flips the sign.
Deep dive: why churn rules, and how to draw the line
The experiment revealed that churn —not importance— decides whether documenting something by hand is worth it. It's worth understanding why that's so and how to use that rule in practice.
The mechanics are simple but counterintuitive. The benefit of a doc depends on how much and how valuably it's read; the cost depends on how much the documented thing changes, because each change forces an update (or, if you don't do it, the doc to rot). Since the cost grows with churn and the benefit doesn't, there's a churn point beyond which the cost exceeds the benefit and the ROI turns negative. And here's the counterintuitive part: the importance of the information raises the benefit, but doesn't lower the cost. A very important endpoint list is read more (more benefit), but it still changes 40 times a year (same high cost). That's why importance doesn't save the volatile: you can have extremely important information whose by-hand ROI is negative, simply because it changes too much. Volatility is a property of the cost, and the cost is what sinks the ROI.
From there comes the practical frontier, which is the same one we saw in living documentation but now justified by ROI: the stable is written by hand; the volatile is generated from the code or left in its source. The stable —decisions (ADR), boundaries (C4), how to run it (README), known risks— changes little, so its maintenance cost is low and its by-hand ROI is positive; besides, much of the stable (above all the why) can't be generated from the code, because it's not there, so writing it by hand is the only option and luckily it's cheap. The volatile —endpoints, signatures, configuration, status— changes a lot, so its by-hand maintenance cost is prohibitive; but luckily it can be read or generated from its source (the code, the router, the tracker), where it's always up to date. Nature helps: what has to be written by hand (the stable why) is exactly the cheap-to-maintain, and the expensive-to-maintain (the volatile what) is exactly what can be generated. The rule falls into place on its own.
There's a pattern that clarifies a lot: software separates the stable from the volatile well, and the doc should respect it. What's stable in a system? The intentions and the boundaries: why payments exists, what responsibility it has, what it talks to and what it doesn't, what guarantees it must meet. That changes little because it's the system's "what for", and the what-for is slow. What's volatile? The implementation details: what functions there are today, what endpoints, what parameters. That changes fast because it's the "how" of the moment, and the how is refactored constantly. The doc that survives documents the "what for" and the boundaries (stable, by hand) and leaves the "how" of the moment to the code (volatile, generated or read directly). A new dev who understands the what-for and the boundaries can read the how in the code without trouble; a new dev who only has an obsolete endpoint list understands nothing and is sent down the wrong path on top of it.
The honest nuance, so as not to turn this into an excuse to document nothing. "Don't document the volatile by hand" is not "don't document the volatile": it's "don't document it by hand". The endpoint list should exist —it's useful—, but generated from the router, not typed into a wiki. The business rules should be documented —but ideally as tests that verify them (which can't rot), plus a note of the why—. The distinction isn't between "document" and "don't document"; it's between "write by hand" (only the stable) and "generate from the source" (the volatile). The mistake the lesson fights isn't documenting the volatile, but documenting it by hand, which is what guarantees the negative ROI and the rot. And the other, symmetric mistake is not documenting the stable —leaving payments' why only in Elena's head because "the code speaks for itself"—: the code doesn't explain the why, and that stable why is exactly the highest-ROI and what raises the bus factor (lesson 7).
Common mistakes
Documenting the volatile too much (the menu sticky note). What happens: the team, wanting to be exhaustive, documents by hand every endpoint, every signature, every configuration value —the most concrete and visible—, and since that changes constantly, the doc becomes obsolete in weeks and turns into a trap. Why it happens: the volatile is the most tangible ("here's the complete list of endpoints" feels like real documentation), while the stable (the why) feels abstract and less "documentable". How to spot it: if your doc lists implementation details that change every sprint, or if "keeping the doc up to date" feels like an infinite task, you're documenting the volatile by hand. How to fix it: generate the volatile from the code (OpenAPI for endpoints, dependency analysis for detail diagrams, tests for rules) and reserve hand-writing for the stable; the negative ROI of the volatile-by-hand turns positive when it's generated.
Documenting the stable too little (leaving the why in one head). What happens: the team doesn't document the decisions or the boundaries "because the code speaks for itself", and the why of each thing lives only in the head of whoever decided it. When that person leaves, the why goes with them, and the team undoes decisions without understanding their reason. Why it happens: the stable —above all the why— is what's not in the code, so it's easy to forget it has to be written; and since it changes little, there's no constant pressure that reminds of its absence. How to spot it: if no one can explain why payments is separated without asking Elena, or if the important decisions have no ADR, you're documenting the stable too little. How to fix it: write the ADRs of the significant decisions and document the boundaries (the C4 and the boundaries); it's the highest-ROI (low churn, read for years) and what raises the bus factor. The code shows the what; the why has to be written.
Deciding what to document by importance instead of by volatility. What happens: the team prioritizes documenting "the most important" and ends up documenting by hand important but volatile things (the endpoint list is important), spending effort on doc that rots, while leaving stable things undocumented because they seemed "minor". Why it happens: importance is the intuitive heuristic ("let's document what matters most"), but it's the wrong heuristic for this decision. How to spot it: if you justify documenting something by hand by saying "it's just very important" without considering how much it changes, you're using the wrong variable. How to fix it: before documenting something by hand, ask first "does it change little?" (volatility) and only then "does it help?" (importance); the important-and-volatile is generated, the important-and-stable is written, and the not-important isn't documented in any form. Importance decides whether the information should be available; volatility decides how (by hand or generated).
Exercises
Exercise 1 — Why the important can have negative ROI. In the example, api_endpoint_list is useful information (people read it) and yet it has negative ROI (−14h), while key_decisions has positive ROI (+29h). An engineer says: "but endpoints are more important for the day-to-day than architecture decisions, we should document them as a priority". Explain why their reasoning confuses two variables, and what they should do with the endpoints.
See solution
The engineer confuses importance with volatility, which are two distinct variables that affect distinct things about the ROI. Importance raises the benefit (important information is read more and saves more); volatility raises the maintenance cost (information that changes a lot has to be updated a lot). Endpoints can be very important —and that's why they have some benefit (6h)— but they change 40 times a year, so keeping them synchronized by hand costs 20h a year, more than they yield. The ROI is negative not despite being important, but because they're volatile: even if they were more important still, their volatility would keep making documenting them by hand cost more than it yields. Importance can't save the volatile, because it doesn't touch the variable that sinks the ROI (the cost).
What they should do with the endpoints isn't documenting them by hand nor leaving them undocumented —both extremes are mistakes—. They should generate them from the code: an OpenAPI (or equivalent) that comes from the router and updates itself when the code changes. That keeps the information available (satisfies the importance) with zero maintenance cost (eliminates the volatility problem), turning the negative ROI of documenting-by-hand into a positive ROI of generating. The rule: when something is important-but-volatile, the answer isn't the wiki or oblivion, it's generation from the source. Importance says the information should exist; volatility says it should be generated, not typed.
Exercise 2 — Classify and decide. For each of these five pieces of information about payments, say whether it's stable or volatile, and what you'd do with it (document by hand / generate from the code / leave in its source): (a) why payments charges by queue instead of direct write; (b) the exact column names of the transactions table; (c) which other modules can call payments and which can't (the boundary); (d) the current version of the payment provider's library; (e) which quality attribute payments must meet (for example, process peaks without going down).
See solution
- (a) Why payments charges by queue → STABLE, document by hand (ADR). It's a decision with its why; it changes very rarely (only if the decision is revisited) and it's not in the code (the code shows there's a queue, not why). High ROI: written once, useful for years, raises the bus factor. It goes by hand, in an ADR.
- (b) Column names of the table → VOLATILE, leave in its source (the code/schema). Columns are refactored often (added, renamed, removed). Documenting them by hand rots; they live in the database schema and the code, which are the source of truth and are always up to date. They're read there, or generated from the schema; they're not documented by hand.
- (c) The boundary: who can call payments → STABLE, document by hand (C4 + possibly an ADR). A module's boundary —its responsibility and what it talks to— is about as stable as it gets: it's the module's "what for", it changes little. And it's critical so no one violates it by accident. It goes by hand, in the C4 and the boundaries description; low churn, high ROI.
- (d) Version of the provider's library → VOLATILE, leave in its source (the dependencies file). The version changes with every update; documenting it by hand in a wiki guarantees it becomes obsolete. It lives in the dependencies file (package.json, requirements.txt, etc.), which is the source of truth. It's not documented by hand.
- (e) The quality attribute payments must meet → STABLE, document by hand (arc42 quality section / ADR). The quality attributes —derived from business goals in module 5— change little: they're guarantees the system must uphold. And they're not in the code explicitly. They go by hand, in arc42's quality section or an ADR; low churn, high value for whoever designs or modifies payments.
The pattern: what's why / what-for / boundary / guarantee is stable and goes by hand (and usually isn't in the code); what's a momentary implementation detail (columns, versions) is volatile and lives in its source. The question "does it change little?" separates the two cleanly.
Exercise 3 — The line that moves. The ROI of documenting something by hand depends on its churn. Imagine that Mercado's endpoint list, which today has churn 40 (ROI −14h by hand), stabilized —because the API was frozen for external clients and now barely changes, say churn 3—. Would the decision to generate it vs. document it by hand change? Think with the ROI logic and say what this teaches about the rule.
See solution
Yes, it would change, and this reveals something important about the rule. With churn 40, the maintenance cost of the endpoint list by hand was 40 × 0.5h = 20h a year, against a benefit of 6h → ROI −14h (not worth documenting by hand, it has to be generated). If the API is frozen and the churn drops to 3, the maintenance cost falls to 3 × 0.5h = 1.5h a year, against the same benefit of 6h → ROI ≈ +4.5h (now it would be worth documenting by hand, because it barely changes). The same information —the endpoint list— goes from "generate from the code" to "you could document it by hand" only because its volatility changed.
What this teaches is that stable and volatile aren't fixed categories of information types, but a property of the churn in a given context. "Endpoint list" isn't inherently volatile: it's volatile while the API changes often, and it becomes stable when the API freezes. The rule "document the stable, not the volatile" is applied looking at the real churn of each thing in your system, not a universal label. An endpoint of a frozen public API (a contract with external clients, can't be changed without breaking them) is stable and worth documenting; an internal endpoint refactored every week is volatile and has to be generated. The practical lesson: before deciding how to document something, measure (or estimate) how much it really changes in your context, instead of assuming. And that said, even when the volatile stabilizes and could be documented by hand, generating it from the code is still safer if possible —because the churn could rise again, and generation never rots—. The churn rule says when documenting by hand is viable; the living-documentation one says that, when it can be generated, generating is even better.
Summary and next step
In this lesson you learned the rule that saves the doc from oblivion: document the stable, not the volatile. You saw, with the house manual vs. the menu sticky note, that documenting the stable (the shut-off valve) is an investment that yields for years with a single payment, while documenting the volatile (the menu) is a recurring expense that yields little and is always about to lie. And you measured it with the ROI: the three stable pieces (decisions, boundaries, how to run it) have positive ROI; the three volatile ones (endpoints, signatures, status) have negative ROI —and you discovered that the variable that flips the sign is the churn, not the importance—. You learned the practical frontier: the stable is written by hand (cheap to maintain, and often the why isn't in the code); the volatile is generated from its source (zero maintenance cost, always up to date); and that "don't document the volatile by hand" isn't "don't document it" —it's generate it, not type it—.
Before moving on you should be able to: explain why important information can have negative ROI (volatility raises the cost, not the importance); classify doc pieces into stable/volatile and decide by-hand/generate/leave-in-source; and understand that stable/volatile depends on the real churn, not a fixed label.
Lesson 6 goes down to a concrete and stable piece every system needs and almost no one does well: the README that onboards. It's the new dev's first contact with the system —what it does, how to run it, where the pieces are, how to contribute— and it's about as stable as it gets (changes little, always read), so it has very high ROI. You're going to execute the onboarding cost —the time to the first useful commit— with a good README and without one, and see why a README that really lets you get the project running turns days of suffering into hours. This lesson's rule says the README is worth documenting; the next teaches how to write the one that works.
Resources
- Cyrille Martraire, Living Documentation (Addison-Wesley, 2019), on "stable knowledge" — the canonical development of this lesson's idea: document the stable knowledge (that changes slowly) and derive the volatile from the source. In English.
- Martin Fowler — "Who Needs an Architect?" and the architecture hub — Fowler defines architecture as "the decisions that are hard to change", which is exactly the stable: what's most worth documenting because it's what endures most. In English.
- Andrew Hunt and David Thomas, The Pragmatic Programmer, 20th Anniversary Edition (Addison-Wesley, 2019), the DRY principle and "the evils of duplication" — why documenting by hand what's already in the code (the volatile) creates a duplication that guarantees the desync. In English.
- Simon Brown — on what to document and what not (c4model.com and his talks) — the idea of documenting the structure and the stable intentions, and leaving the volatile detail to the code. In English.
- OpenAPI Specification — the quintessential example of generating the volatile from the source: the endpoint list derived from the code, always up to date, instead of a hand-typed list that rots. In English.