4.5.21 · D4Software Engineering

Exercises — Documentation — inline comments, docstrings, README, ADRs

3,288 words15 min readBack to topic

Before we start, one shared vocabulary picture. Every exercise refers back to this "zoom ladder": the same project seen at four zoom levels, each with its own document type.

Figure — Documentation — inline comments, docstrings, README, ADRs

The red end is the most zoomed-in (a single line) and the plum end is the most zoomed-out (the whole project's history). Keep this picture in your head — most mistakes below come from putting text at the wrong zoom level.


Level 1 — Recognition

Goal: given a snippet, correctly name which of the four documentation layers it is (or should be).

Exercise 1.1 — Name the layer

Recall Solution
  1. Inline comment — it sits next to a line and explains a why the code cannot show (an external incident). Scope = one block.
  2. ADR — dated, numbered, has a Status, records one decision. Scope = one decision.
  3. README — the project's front door, rendered first, scope = whole project.
  4. Docstring — the first string literal attached to a function object, describing its contract. Scope = one function.

Mnemonic check from the parent note: "I Do Read ADRs" = Inline → Docstring → README → ADR.

Exercise 1.2 — Which question does it answer?

Recall Solution
Layer Question
Inline comment Why this surprising line?
Docstring How do I use this?
README What is this & how do I start?
ADR Why did we choose X over Y?

The rule of thumb: comments say why, docstrings say how to use, README says what & start, ADRs say why we chose this.


Level 2 — Application

Goal: actually write correct documentation given a scenario.

Exercise 2.1 — Fix a lying comment

Recall Solution

The comment says what the code already shows (it multiplies). It adds zero information and will silently lie if the rate changes. Two acceptable fixes:

Fix A — make the code self-documenting (preferred, per Self-documenting Code):

GST_RATE = 0.18   # India standard-slab GST as of FY2024 (statute link in ADR-009)
tax = price * GST_RATE

Now the name GST_RATE explains the what, and the surviving comment carries the why the number is what it is — external knowledge the code cannot express.

Fix B — if you cannot rename, at least make the comment carry the why:

tax = price * 0.18   # 18% = India's standard GST slab; revisit if slab changes (ADR-009)

The 18 % restatement is gone; what remains is why 0.18 — the part invisible in code.

Exercise 2.2 — Write a contract docstring

Recall Solution
def split_bill(total, people):
    """Divide a bill equally and return each person's share.
 
    Args:
        total:  the full bill amount, in the same currency for all callers; must be >= 0.
        people: number of people splitting; must be an integer > 0.
    Returns:
        float: each person's share = total / people.
    Raises:
        ValueError: if people <= 0 (cannot divide a bill among zero people).
    """

Why each line earns its place: Args states the preconditions (a caller learns the bounds without reading the body), Returns states the postcondition (what comes back and its meaning), Raises states the failure mode. That is the API contract — the reader never has to open the function. See API Design & Contracts.

Numeric sanity check: split_bill(1200, 4) returns 300.0 and split_bill(1000, 8) returns 125.0.

Exercise 2.3 — Order a README

Recall Solution
  1. One-sentence what + why — the 20 % of text read by 80 % of visitors; lets a stranger decide "is this for me?"
  2. Install commands — copy-pasteable, so they can act immediately.
  3. Quickstart example — the minimal "run something and see it work."
  4. Usage / config — depth for people who stayed.
  5. Contributing — for the small subset who want to change the project.

Why this order: attention decays fast. If a reader must scroll past 200 lines to run one command, most leave. Rendered with Markdown, the top of the file is the "front door." See also Onboarding & Bus Factor.


Level 3 — Analysis

Goal: judge existing documentation — spot what's wrong and say precisely why.

Exercise 3.1 — Critique an ADR

Recall Solution

An ADR must be a dated, numbered, immutable record of one decision with its context, alternatives, and consequences. This one fails on:

  1. No number and no date → it isn't part of an append-only log; you can't reference it ("see ADR-007") or know when the context was true. Why it matters: ADRs form a numbered history; without an index they're just a floating note.
  2. No Status → you can't tell if it's Proposed, Accepted, or Superseded. Why it matters: immutability comes from Status + Date; a later ADR supersedes this one instead of editing it.
  3. No Context, no Alternatives, no Consequences → "because it's good" records nothing reusable. Why it matters: the killer feature of an ADR is capturing why the rejected option felt right and lost. Without it, the same debate ("Postgres vs Mongo?") reopens in six months from zero.

A fixed skeleton: # ADR-007: Use PostgreSQL for primary store / Date / Status: Accepted / ## Context / ## Decision / ## Alternatives considered / ## Consequences. See Architecture & System Design.

Exercise 3.2 — Comment or rename?

Recall Solution

Apply the test from the parent note: "Could I rewrite the code so this comment becomes unnecessary?"

  • A) — fix the code, delete the comment. Rename aage and the magic number 18 → a named constant LEGAL_AGE. Then if age > LEGAL_AGE: reads itself; the comment merely restates it and will rot.
    LEGAL_AGE = 18
    if age > LEGAL_AGE: ...
  • B) — keep the comment. It encodes external knowledge (a real-world IERS bulletin, a yearly maintenance obligation) that no rename can express. This is exactly the why code cannot self-document.

Rule: comments that restate code → rot. Comments that carry outside-world knowledge → keep. See Clean Code & Naming.


Level 4 — Synthesis

Goal: design a coherent documentation set for a whole scenario, choosing the right layer for each fact.

Exercise 4.1 — Route facts to the right layer

Recall Solution
Fact Layer Sample line
1 Docstring on charge() Raises: GatewayError — after 3 failed retries on 5xx responses.
2 ADR (context) ## Context — Acme gateway emits spurious 503s under load (INC-4821); calls must be retry-safe.
3 ADR (decision + alternatives) ## Decision — Exponential backoff. ## Alternatives — fixed 1s interval, rejected: thundering-herd on recovery.
4 README ## Quickstart\n\from retrypay import charge; charge(amount=1.00, currency="USD")``
5 Inline comment # sleep(0) on attempt 0: no delay before first try, keeps the loop uniform

Why this routing works: each fact goes to the zoom level that matches its scope — line-level quirk → comment, function contract → docstring, project onboarding → README, cross-cutting decision → ADR. This is the zoom ladder from figure s01 applied end-to-end. Notice fact 2 and fact 3 both live in an ADR: the context (the 503 problem) and the decision (backoff choice) belong to the same record.

Exercise 4.2 — Design the file tree

Recall Solution
retrypay/
├── README.md              ← front door (What / Install / Quickstart)
├── retrypay/
│   └── client.py          ← docstrings live INSIDE the code objects here
└── docs/
    ├── adr/
    │   ├── 001-use-exponential-backoff.md
    │   └── 002-drop-fixed-interval.md   (Status: Supersedes 001? — or its own decision)
    └── api/               ← GENERATED, do not hand-edit
  • Inline comments live nowhere special — they're inside client.py next to lines.
  • Docstrings also live in client.py, attached to functions/classes.
  • README sits at repo root so GitHub renders it first.
  • ADRs are numbered files in docs/adr/, append-only.
  • docs/api/ is generated from the docstrings by a tool like Sphinx — that's the payoff of docstrings being machine-readable strings, not comments. See Sphinx & Doc Generation.

Level 5 — Mastery

Goal: defend a documentation trade-off under pressure, the way a staff engineer would in review.

Exercise 5.1 — Justify the 10:1 economics

Recall Solution

Assumptions (from the parent note's economics): code is read ~10 times for every time it's written. Assume this function is non-trivial and read by 10 developers over its life.

The math: suppose a good docstring costs 30 seconds to write once. Suppose each reader who lacks the docstring must instead read the function body and trace its edge cases, costing an extra 2 minutes (120 s) per read to reconstruct the contract.

  • Cost without docstring: of reader time.
  • Cost with docstring: .
  • Net saving: seconds minutes over the function's life, for a 30-second investment.

The principle: documentation is amortized — you pay the writing cost once and save the reading cost on every future read. The break-even here is fewer than one reader: even of a saved read pays back the write. The manager optimizes for code shipped this week; the economics optimize for total engineering time over the code's life, which is what actually costs money. See Onboarding & Bus Factor.

Exercise 5.2 — The bus-factor stress test

Recall Solution

This is a bus-factor-of-1 emergency: knowledge lives in one head and must survive her absence. Rank by how much irreplaceable knowledge each layer captures per minute:

  1. ADRs first. The why behind decisions ("why backoff, why Postgres, why we don't cache X") exists only in Priya's head — the code has erased that history. It's unrecoverable by reading source. Highest leverage.
  2. README second. A newcomer covering for her needs to run and reason about the system fast. The front door prevents her replacement from being stuck at "how do I even start it?"
  3. Docstrings third. The contracts of key public functions — but these are partially recoverable by a careful reader from the code, so lower urgency than the unrecoverable why.
  4. Inline comments last. Line-level gotchas are the most local and the most recoverable by reading; they matter least in a two-hour triage.

The key insight: invest scarce time where knowledge is least recoverable from the code itself. Decisions (ADRs) and onboarding orientation (README) are irreplaceable; contracts and line-quirks can be partly reverse-engineered. This ordering directly attacks the bus factor described in the parent note.


Recall Final self-check (recall before revealing)

Cover the answers and say them aloud. Which layer records why the rejected option felt right? ::: ADR (with its Alternatives + Consequences sections). What test decides if an inline comment earns its place? ::: "Could I rewrite the code so this comment becomes unnecessary?" If yes, rename/extract instead. Why is a docstring machine-readable but a comment is not? ::: A docstring is a real string object attached to the code object (retrievable via obj.__doc__); a comment is discarded and invisible to tools. In a two-hour bus-factor triage, what do you document first and why? ::: ADRs — the why is the least recoverable knowledge from source.