Worked examples — Documentation — inline comments, docstrings, README, ADRs
Before any symbol or rule appears, here is the single decision every example turns on:
The symbol legend (defined once, used in every example)
Every worked example below scores its decision with the same five plain-English quantities. Fix their meaning here so nothing is ad-hoc later:

Read the figure now — it is the map for the whole page. The four coloured rungs are the four layers. The blue arrow on the left shows scope growing (one line at the bottom → the whole project/decision at the top); the yellow arrow on the right shows the audience widening (one maintainer → many newcomers/architects). Every example below is a single point on this ladder: solving the layer-picker question means "which rung does my (scope, audience, question) triple land on?". Keep glancing back at it — Examples 1–2 sit on the bottom blue rung, 3 on the green rung, 4 on the yellow rung, 5/7 on the red rung, and Example 9 sweeps all four rungs with one fact.
The scenario matrix
Each row is one case class (a kind of situation) this topic can throw at you. The examples afterwards each fill one cell (labelled Cell A…Cell J).
| Cell | Case class | Concrete trigger | Correct layer / verdict |
|---|---|---|---|
| A | Comment restates code (redundant, ) | i = i + 1 # increment i |
Delete comment → self-document |
| B | Comment encodes external knowledge (the "why") | vendor bug, incident number | Keep inline comment |
| C | Function contract for a caller | bounds, units, raised errors | Docstring |
| D | Docstring that only repeats the name | """Withdraws money.""" |
Rewrite to add contract |
| E | Newcomer front-door | first 5 lines of a repo | README |
| F | One significant decision + rejected option | Postgres vs Mongo | ADR |
| G | Zero / degenerate input — trivial code, self-evident | one-line pure helper, obvious name | No documentation (the "" case) |
| H | Limiting / boundary — decision reversed later | ADR superseded by a newer ADR | New ADR that supersedes, old one immutable |
| I | Real-world word problem | onboarding a new hire fast | README + ADRs together (bus factor) |
| J | Exam-style twist | comment that has gone stale and lies | Fix by deletion or by moving intent into code |
We will also cover the quadrant sweep (Example 9): a single snippet run through all four layers so you see how the same fact changes shape as scope grows.
Example 1 — Cell A: the redundant comment (the "" case)
Forecast: Does the comment add information, or just echo the code? Guess before reading.
- Count (facts in the comment). Why this step? Using the fact-counting rules from the legend, the comment "add x to total" is one true/false statement → .
- Count (facts already knowable from code). Why this step? The line
total = total + xstates exactly the same single fact, so a reader can already discover it → . - Compute the information delta . Why this step? . The comment adds no new fact — it is the "same sentence in two languages".
- Apply rot risk and read the verdict. Why this step? First, define the term: rot risk (also "bit-rot" / "staleness") is the chance that, when the code changes but the comment does not, the comment ends up describing code that no longer exists — i.e. silently lying. Because this comment mirrors the code, is high (change
+to-and it lies). With and high the verdict from the legend is delete.
Verify: , , ; verdict = delete. (Checked numerically below.)
Example 2 — Cell B: the comment that carries external knowledge
Forecast: The loop retries 3 times. Is "why 3?" visible in the code?
- Ask the layer-picker question. Why this step? Scope = a block; audience = a maintainer who might "simplify" the retry away; question = "why does this weird retry exist?". That triple lands us on the bottom blue rung → inline comment.
- Count using the splitting rule. Why this step? A good comment here states three separable facts: {"gateway flakes under load"}, {"the incident is INC-4821"}, {"3 attempts is the agreed cap"}. By the splitting rule these are 3 distinct facts → .
# Retry up to 3×: gateway returns spurious 503s under load (see INC-4821) - Count . Why this step? None of those why-facts is knowable from the code — the loop shows that we retry, never why. So .
- Compute and . Why this step? . And because the facts live outside the code (an incident report), is low — the code cannot desync them. with low → keep.
Verify: , , ; verdict = keep. (Checked below.)
Example 3 — Cells C & D: docstring contract vs the empty docstring
Forecast: Out of 3 caller questions, how many does each version answer?
- Derive the rubric (why exactly three questions, weighted equally). Why this step? A caller who never reads the body can only be blocked by three things: what may I pass in? (preconditions), what comes back? (postcondition), how can it fail? (raised errors). These are the complete, non-overlapping set of a function's contract — there is no fourth independent question, and missing any one can equally cause a bug, so we weight them equally (each worth 1). Score = number answered, out of 3.
- Score Version D. Why this step? "Withdraws money" states no bounds (preconditions unknown), no description of the return beyond the name, no errors. Answered = 0 → score .
- Score Version C. Why this step?
Argsanswers preconditions (1),Returnsanswers the postcondition (1),Raisesanswers failures (1). Answered = 3 → score . - State Cell D's fix. Why this step? Cell D's cure is not "more words" but "add the contract you can't see": bounds, units, side-effects, raised errors — precisely the jump from to .
Verify: Version D score , Version C score , improvement questions. (Checked below.)
Example 4 — Cell E: the README front door (the 80/20 measurement)
Forecast: Roughly what fraction of visitors ever see a command buried at the bottom?
- Reach before the move. Why this step? Under the stated model, a command at the end inherits the end's reach → .
- Reach after the move. Why this step? Placed in the first 5 lines (the What + Quickstart of a Markdown README), it inherits the top's reach → .
- Compute the ratio . Why this step? We quantify the win so restructuring is justified by data, not taste. .
Verify: before , after , ratio × more visitors reach a runnable command. (Checked below.) For docs built beyond the README see Sphinx & Doc Generation.
Example 5 — Cell F: one decision, one ADR
Forecast: Is 15 minutes now worth it?
- Cost without the ADR. Why this step? One re-debate person-hours; it recurs twice → person-hours.
- Cost with the ADR. Why this step? Write once person-hours; each later lookup ≈ (read ADR-007). Total ≈ person-hours.
- Net saving . Why this step? This is the amortization argument from the parent note — pay a little now, save a lot later. person-hours.
Verify: without ; with ; person-hours. (Checked below.) The ADR also records why Mongo felt right — see Architecture & System Design and API Design & Contracts.
Example 6 — Cell G: the degenerate / self-evident input ( again)
Forecast: Does trivial, self-documenting code still need documentation?
- Count the caller questions the code already answers. Why this step? Using Example 3's rubric: inputs? (obvious — a number), output? (
n*2, and the namedoublesays it), failures? (none). Answered by code alone = 3, so . - Count for any honest docstring. Why this step? Any docstring here can only restate the name and the doubling, so — the same three facts.
- Compute . Why this step? . The docstring adds nothing.
- Read the degenerate verdict. Why this step? When the legend says don't write it. Trivial, clearly-named code should stay bare — this is Self-documenting Code. (Earlier drafts called this the "zero-vector" case; concretely it just means .)
Verify: , , → no docs needed. (Checked below.) Contrast Example 3, where the code did not self-answer.
Example 7 — Cell H: the boundary case, an ADR that gets superseded
Forecast: What happens to the old, now-wrong decision record?
- Recall the immutability rule. Why this step? ADRs are a dated, append-only log; editing ADR-007 would erase the very history the log exists to keep.
- Represent status as a discrete state, not a continuous curve. Why this step? A decision's status is one of a finite set —
{Proposed, Accepted, Superseded}— so we model it as a step function that jumps at discrete decision events, not a smooth continuous variable. There is no "50% superseded"; the state flips instantly when a new ADR lands. At the 2025 event, ADR-007's status jumps Accepted → Superseded while its text stays frozen. - Write the superseding ADR and count the log. Why this step? Create ADR-015 "Use columnar store for analytics" (status
Accepted); ADR-007's status becomesSuperseded by ADR-015. The log grows: 7 records → 8. The count only ever increases.
Verify: records before , after superseding ; change (never decreases). (Checked below.)
Example 8 — Cell I: the real-world word problem (onboarding, bus factor)
Forecast: How many people can the module survive losing, before and after the docs?
- Define the bus factor. Why this step? The bus factor is the number of people who would have to leave suddenly ("hit by a bus") before a module becomes unmaintainable. A bus factor of means one departure paralyses the module — the danger the parent note warns of.
- Measure it before docs. Why this step? Only Ana holds the knowledge; if she is gone, no one can proceed → bus factor .
- Add the README + ADRs. Why this step? Now the knowledge lives in documents Ben can read, so Ben becomes a second maintainer independent of Ana. This is exactly the Onboarding & Bus Factor lever: written docs raise the count of people who can operate the module from to .
- Count interruptions saved . Why this step? Ben had 4 questions, each previously an interruption to Ana; the docs answer all 4 → .
Verify: bus factor before , after , improvement ; interruptions saved . (Checked below.)
Example 9 — the "quadrant sweep": one fact through all four layers
Forecast: How many separate reader-questions can four layers answer that one layer cannot?
- Inline comment (line scope). Why this step? On the retry loop, answers "why the retry?" for the maintainer. Questions answered so far: 1.
- Docstring (function scope). Why this step? On
charge(), answers "how do I call this safely, what does it raise?" for the caller. +1 → running total 2. - README (project scope). Why this step? Answers "what is this project & how do I run a charge?" for the newcomer. +1 → total 3.
- ADR (decision scope). Why this step? The Architecture Decision Record answers "why this gateway, why cap at 3?" for a future architect. +1 → total 4.
- Interpret the sweep. Why this step? Just as sweeping through all four quadrants covers every direction, sweeping this one fact up all four rungs of the ladder (figure s01) covers every reader — each layer catches a question the others miss, with no overlap.
Verify: distinct questions . (Checked below.)
Example 10 — Cell J (exam twist): the stale comment that lies
Forecast: Is the bug in the code or in the comment?
- Locate the contradiction. Why this step? The comment claims 5 retries;
range(3)does 3. A comment that disagrees with code is worse than none — it actively misleads. This is the high rot risk from Example 1, now realised. - Diagnose the root cause. Why this step? Someone changed the loop 5→3 and forgot the comment. Their facts diverged; divergence , and any nonzero divergence means the comment is lying.
- Fix option A — move intent into code. Why this step? Name the constant:
MAX_RETRIES = 3, loopfor _ in range(MAX_RETRIES). Now there is only one source of truth, so 5/3 cannot desync — the Self-documenting Code fix. - Fix option B — correct or delete the comment. Why this step? If the why still matters, rewrite it as an fact:
# Retry up to 3×: flaky gateway (INC-4821). If the why is gone, delete it (back to Cell A). Either way the lie is removed.
Verify: comment says 5, code does 3, divergence → lying comment; valid fixes . (Checked below.)
Recall Self-test (reveal after answering)
A magic number 86400 appears with no explanation. Which cell, which fix? ::: Cell B — inline comment # seconds per day () OR the Cell G-style fix: a named constant SECONDS_PER_DAY = 86400 (self-documenting, so a comment's ).
ADR-007 is now wrong. Do you edit it? ::: No — Cell H: write a new ADR that supersedes it; the old text stays immutable, status jumps Accepted → Superseded.
A docstring reads """Get user.""" for get_user. Cell? ::: Cell D — repeats the name (); rewrite to add the contract (args, return, raises).