56 min read

> — a maxim long current among software engineers, widely associated with Guido van Rossum's guidance for Python

Prerequisites

  • 3
  • 7
  • 8
  • Able to read a short function in Python or JavaScript (you will not write code; you only read it)

Learning Objectives

  • Explain why code documentation is writing for an audience — chiefly your future self and the next maintainer — and what that audience actually needs.
  • Apply the canonical rule 'comment the why, not the what': delete redundant narration and write comments that capture intent, constraints, and decisions.
  • Read and evaluate docstrings in Python (PEP 257) and JavaScript (JSDoc) conventions, and judge whether one documents a function's contract completely.
  • Recognize when good naming, type hints, and structure make a comment unnecessary — and when a comment is still the only place the 'why' can live.
  • Place a piece of code on the documentation spectrum (none → too much → right) and decide what to document, what to delete, and what to rewrite as a clearer name.

Chapter 24: Code Documentation: Comments, Docstrings, and the Art of Explaining Your Code

"Code is read far more often than it is written." — a maxim long current among software engineers, widely associated with Guido van Rossum's guidance for Python

Chapter Overview

Six months ago, Raj Patel wrote a forty-line function in a hurry, shipped it, and moved on. This week he's back in the same file, fixing a bug, and he is staring at his own code with the bewilderment of a stranger. There is one comment in the whole function — # increment i — sitting next to a line that reads i += 1. It tells him nothing he couldn't read for himself. What he actually needs to know is nowhere on the screen: why the loop skips the first element, why there's a * 1.0 floating in the middle of an integer calculation, why the timeout is exactly 4.7 seconds and not 5. He wrote this. He had reasons. The reasons are gone, evaporated out of his memory and never written down, because at the time they were obvious — to him, that week, in that head. The code records every what. It records not one why. And the why is the only thing he needs.

This is the problem Part V opens on, and it is the same problem the whole book has been circling, now wearing a hoodie. Earlier chapters taught you that writing is how you think (Chapter 1), that clarity means cutting the fog so meaning survives the first read (Chapter 3), that every sentence must earn its place (Chapter 6), and that the best writing is invisible — the reader notices the content, not the prose (a theme since Chapter 1). Code documentation is all of those principles aimed at a particular reader: a programmer, often you, trying to understand code without re-deriving it from scratch. The genre has its own conventions — comments, docstrings, type hints — but the underlying skill is the one you've been building all along. Documentation that narrates what the code obviously does is the software version of "it is important to note that": words that occupy space and carry nothing. Documentation that captures why is the software version of a sentence that earns its place. By the end of this chapter you will be able to look at a piece of code, delete the comments that say nothing, and write the ones that say the only thing the code can't.

The chapter has a spine, and it's the book's spine: a before/after transformation, run on code instead of prose. We start with the audience nobody plans for — your future self, your first reader (§24.1). Then the canonical lesson, the one that reorganizes how you think about every comment you'll ever write: comment the why, not the what (§24.2). From there: docstrings, the structured documentation that travels with a function, in Python and JavaScript conventions (§24.3); type hints and signatures as a form of documentation the language itself checks (§24.4); self-documenting code, where a good name does what a comment was trying to do, only better (§24.5); and the documentation spectrum — none is a disaster, too much is a different disaster, and the right amount is a skill (§24.6). The anchor throughout is Raj's neglected open-source project, the same one whose README we'll rebuild in Chapter 25 — here we work one layer deeper, inside the code itself.

In this chapter, you will learn to:

  • Explain why your future self and the next maintainer are the real audience for code documentation, and write for what they actually need.
  • Apply the rule comment the why, not the what — deleting redundant narration and writing comments that capture intent, constraints, and decisions.
  • Read and judge docstrings in Python (PEP 257) and JavaScript (JSDoc) conventions, and tell whether one fully documents a function's contract.
  • Use good names, type hints, and structure to make comments unnecessary — and recognize the cases where only a comment can hold the "why."
  • Place any piece of code on the documentation spectrum and decide what to document, what to delete, and what to rename.

📗 Software/CS track, this chapter is core — and it's the front door to Part V. If you write or read code, documentation is not optional polish; it's part of the code's interface, and badly documented code costs you (and everyone after you) hours of re-derivation. This chapter is the foundation for the rest of the part: Chapter 25 scales the same "capture the why" instinct up to READMEs, API docs, and Architecture Decision Records; Chapter 26 turns it into tutorials; Chapter 34 takes it into code reviews and design docs. One note for non-software readers passing through: every code block here is material being documented, not something you're asked to write — read the comments and docstrings, not the logic. The skill transfers to any writing where you must explain a decision someone will later question.


24.1 Your First Reader Is You, Six Months From Now

Start with the audience, because every chapter in this book starts with the audience, and code documentation has the most misunderstood audience of all. Ask a programmer who their documentation is for, and most will say "other developers," or "whoever maintains this after me," or — if they're cynical — "nobody, because nobody reads it." All of those answers miss the reader who matters most and shows up most reliably: you, six months from now, with no memory of why you did what you did.

This is not a sentimental point; it's a structural fact about how software gets written. You build a system over weeks or months, holding a vast amount of context in your head — which edge cases you handled, which approaches you rejected, why the number is 4.7 and not 5, what that weird workaround is protecting against. That context is real, and it is load-bearing, and it is also almost entirely unwritten, living only in your working memory. Then you ship, you move to the next thing, and over the following weeks that context drains out of your head completely. Memory of your own reasoning is far more perishable than you expect. When you return — to fix a bug, add a feature, answer a question — you arrive as a stranger to your own code, holding none of the context you had when you wrote it. The you who returns is, for all practical purposes, a different developer who happens to share your name.

🔄 Check Your Understanding. A developer says, "I don't need to document this — it's a personal project, I'm the only one who'll ever touch it." Why is this reasoning flawed even if the claim ("only I will touch it") is completely true?

Answer Because you are not one person over time — you're a series of people with decaying context. The you who wrote the code holds all the reasoning in working memory; the you who returns in six months holds none of it. "Only I will touch it" doesn't mean "no one needs documentation"; it means the future you needs it, and the future you knows nothing the present you knows. Personal projects are often worse-documented for exactly this reason ("it's just me, I'll remember"), and they're where people most often waste an afternoon re-deriving a decision they made deliberately and forgot completely. The audience isn't hypothetical "other developers." It's a real, specific, guaranteed reader: yourself, after the context has drained out. Write for them.

Once you accept that future-you is the reader, a useful reframing follows, and it's been attributed for years to programmers describing good documentation: documentation is a letter you write to your future self. That framing does real work. It tells you the tone (a colleague's, not a textbook's — direct, on the level), the content (what you will wish you'd known, not what a manual would list), and the test (would this note save future-you the ten minutes of re-derivation it cost present-you to learn?). When you write a comment, picture the specific moment it will be read: someone — probably you — has just opened this file, confused, mid-bug, wondering why on earth this code does the strange thing it does. The good comment is the one that answers exactly that question, at exactly that moment. Everything else is noise in the way.

And there is a second reader, close behind future-you: the next maintainer — a teammate, a contributor, the person who inherits the project when you change jobs. They arrive with even less context than future-you, because they were never in your head at all. Everything that's true about writing for future-you is more true for them. The good news is that you don't write two different sets of documentation; the note that saves future-you the re-derivation is the same note that lets a new maintainer understand the code without interrogating you across Slack. Write for the confused reader with no context, and you've written for both.

Here's what the failure costs, made concrete. Raj's project — a small open-source library called chronoparse that turns human date strings ("next Tuesday," "in 3 days") into machine dates — has a function at its core that everyone, including Raj, dreads touching:

def parse(text, base=None):
    text = text.strip().lower()
    if base is None:
        base = datetime.now()
    for pattern, handler in PATTERNS:
        m = pattern.match(text)
        if m:
            result = handler(m, base)
            if result < base - timedelta(days=3650):
                continue
            return result
    raise ValueError(text)

Read it as a stranger — which, in six months, is what you are. You can follow what each line does; Python is readable, and the steps are plain. But the questions that actually block a maintainer are all why questions the code refuses to answer. Why lowercase the text? (Is that load-bearing, or just tidy?) Why fall back to datetime.now() and not, say, midnight today? Why does a matched result that's more than 3,650 days in the past get skipped (continue) instead of returned or rejected — and why 3,650, which is suspiciously close to ten years? Why does the final error throw the raw text and nothing else? Every one of these had a reason when Raj wrote it. None of the reasons is on the screen. A maintainer has exactly two options: track Raj down and ask, or guess. Both are expensive; the second is dangerous. This is the function we'll document across the chapter — and the point of documenting it is to make a third option exist: read it and understand.

🔍 Why Does This Work? Why is "write for your future self" such a reliable rule of thumb — more reliable, in practice, than "write for other developers"? Because future-you is a reader you can actually imagine accurately. You don't know who the next maintainer is, what they know, or how they think, so "write for other developers" stays abstract and easy to wave away. But future-you is concrete: you know exactly how much context you'll have lost (all of it), exactly what kind of confusion you'll be in (mid-bug, wondering "why did I…"), and exactly what would help (the reason, not the restatement). Picturing a specific confused reader at a specific moment is what turns "I should document this" into "I should write this sentence, because that's the one I'll need." Abstract audiences produce abstract, useless documentation. The specific reader — your own confused future self — produces the note that actually saves time. It's Chapter 2's lesson (write for a specific reader) with the specific reader handed to you for free.

[📍 Good stopping point — the rest of the chapter is how to write for that reader, starting with the single most important rule.]


24.2 Comment the Why, Not the What

Here is the rule that reorganizes everything. It is the most important sentence in the chapter, possibly in the genre, and once it clicks you cannot unsee it: comment the why, not the what.

The reasoning is almost embarrassingly simple. The code already says what it does — that's what code is. A line that reads i += 1 already states, completely and unambiguously, that i is incremented by one. A comment that says # increment i adds exactly zero information; it translates the code into English, and the English is no clearer than the code. Worse than useless, it's negatively useful: it takes up a line, it makes the file longer, and — the failure mode that makes redundant comments genuinely dangerous — it can fall out of sync with the code and start actively lying (more on that in §24.6). A comment that restates the code is the software version of the bloat you learned to cut in Chapter 3: words that carry no information, occupying space the reader has to wade through.

What the code cannot say is why. Code is a precise record of what happens; it is silent on what you intended, what you decided, what you rejected, and what constraint forced your hand. Those live only in your head — until you write them down. That is the entire job of a comment: to hold the why that the code structurally cannot.

Watch the canonical transformation, the one this whole chapter turns on:

❌ Before (comments the what — useless): ```python

loop through the items

for i in range(len(items)): # increment the counter count += 1 # check if i is greater than zero if i > 0: # add the item to results results.append(items[i]) ```

✅ After (comments the why — useful): ```python

Skip items[0]: it's the header row, not data (see the CSV format note in README).

for i in range(len(items)): count += 1 if i > 0: results.append(items[i]) `` **Why it's better:** Every "before" comment translates the adjacent line into English —# increment the counternext tocount += 1tells a reader who can already read the code precisely nothing. Deleting all four loses *no information*, because the information was already in the code. The single "after" comment carries something the code *cannot*: the reason the loop skips the first element.if i > 0` is visible to anyone; why the first item is special — it's a header row — is invisible, unguessable, and exactly what a maintainer needs. One real comment replaced four fake ones, the file got shorter, and the only thing future-you can't reconstruct got written down. That's the whole rule in one diff.

Notice what just happened to the comment count. Documenting the why almost always means writing fewer comments, not more — and that surprises people who think "well-documented" means "lots of comments." It doesn't. Most lines of code don't need a comment, because most lines are self-explanatory whats that explain themselves. The lines that need a comment are the few where the why isn't obvious from the what: a non-obvious decision, a constraint, a workaround, a surprising choice. A file dense with comments is usually a file full of what-comments (noise), or a file whose code is so unclear it needs constant translation (a different problem — fix the code, §24.5). A well-documented file is often sparse with comments, each one earning its place by holding a real why.

So what does a why-comment actually capture? It's worth naming the categories, because "why" is broad, and these are the ones that recur:

  • Intent — what this code is trying to accomplish, when that's not obvious from the mechanics. "Debounce the search: wait until the user stops typing before hitting the API." The code shows a timer; the comment shows the goal.
  • Constraints and the reason behind a magic value — why this number, this limit, this exact configuration. "Timeout is 4.7s, not 5: the upstream gateway hard-cuts at 5s, and we need margin to log the failure before it does." A bare timeout = 4.7 is a mystery; the comment is the answer.
  • Decisions and rejected alternatives — why you did it this way and not the obvious other way. "Using a list, not a set, here: order matters downstream in the export, even though a set would dedupe for free." This pre-empts the maintainer who "helpfully" swaps it and breaks the export.
  • Workarounds and warnings — what non-obvious thing this code is protecting against, especially when it looks wrong. "Do not remove the * 1.0: it forces float division; without it Python 2-style integer division silently truncated the rate to 0. Bug #214." This is the comment that stops someone from "cleaning up" a line that looks redundant and is actually load-bearing.
  • Links to the wider context — a ticket, an issue, an RFC, a standard, the reason that lives outside the code. "See RFC 5322 §3.4 for why the local-part can contain quoted strings." The comment doesn't reproduce the reason; it tells you where the reason lives.

Now apply this to Raj's parse function from §24.1 — the one that was a wall of unanswerable why questions. Here it is with why-comments added, and nothing else changed:

def parse(text, base=None):
    # Normalize first: patterns are written lowercase and assume no
    # surrounding whitespace, so callers can pass " Next Tuesday ".
    text = text.strip().lower()

    # Default the reference point to "now" so relative phrases ("in 3 days")
    # resolve against the actual current time, not an arbitrary midnight.
    if base is None:
        base = datetime.now()

    for pattern, handler in PATTERNS:
        m = pattern.match(text)
        if m:
            result = handler(m, base)
            # Guard against ambiguous patterns that can resolve absurdly far back
            # (e.g. a bare "Tuesday" with a misread year). >10 years in the past is
            # never a real user intent here, so skip this match and let a later,
            # more specific pattern try. See issue #188.
            if result < base - timedelta(days=3650):
                continue
            return result

    # No pattern matched. Raise with the original text so the caller can show the
    # user exactly what we failed to understand (the message IS the diagnostic).
    raise ValueError(text)

Compare the two versions as documents. The code is byte-for-byte identical — not one line of logic changed. What changed is that every why question a maintainer would have hit is now answered in place: the lowercasing is load-bearing (patterns assume it), the now() default is a deliberate choice with a stated reason, the mysterious 3,650 is "ten years, because no real date is that far back," the continue is "let a more specific pattern try," and the raw-text error is "so the user sees what we couldn't parse." A maintainer can now read and understand instead of track down Raj or guess. That third option — the one that didn't exist in §24.1 — now exists. And the comments achieve it without narrating a single thing the code already said: there is no # strip the text, no # loop through patterns, no # return the result. Every comment is a why.

🧩 Productive Struggle. Before reading the resolution, try this yourself. Here is a single line with a comment. Decide what's wrong with the comment, then write a better one — without knowing the codebase, you'll have to imagine a plausible why: python rate = total / max(count, 1) # divide total by count What does the comment add? What would a useful comment here say instead?

One good answer The comment # divide total by count is pure narration — it translates total / count into English and adds nothing; a reader who can't tell that / divides has bigger problems. Delete it. But notice the line has a genuine why hiding in plain sight: max(count, 1), not count. Why clamp the divisor to a minimum of 1? Because if count is 0, dividing by it crashes (or yields infinity); clamping to 1 makes the rate come out 0 instead of blowing up. That's the comment worth writing: # Clamp the divisor to 1 so an empty set yields rate 0 instead of a ZeroDivisionError. The lesson in miniature: the comment narrated the boring, obvious part (the division) and ignored the one genuinely non-obvious, decision-bearing part (the clamp). Comment the why — and the why is almost always the part that made you stop and think when you wrote it. If a line made you pause, that's where the comment goes; if it wrote itself, it needs no comment.

🚪 Threshold Concept. This is the doorway of the chapter, and it changes how you read every line of code from now on. Before you cross it, you think a comment's job is to explain the code — to make the code easier to understand by describing it. So you write comments that mirror the code, and "more comments" feels like "better documented." After you cross it, you understand that the code explains itself; a comment's job is to capture what the code cannot express — the intent, the constraint, the decision, the reason. The shift is from documentation-as-restatement to documentation-as-the-record-of-thinking. Once you've crossed, redundant comments don't read as "thorough"; they read as clutter, and the absence of a why on a non-obvious line reads as a gap. You start asking a different question at every comment: not "does this describe the code?" but "does this tell me something the code can't?" That single question, applied honestly, is most of what separates documentation that helps from documentation that wastes everyone's time. (It's also why this is theme-1 territory — writing is thinking: a comment that captures a why forces you to have articulated the why, and articulating it is where you discover whether your reasoning actually held.)


24.3 Docstrings: Documentation That Travels With the Code

A comment explains a line. But there's a unit of code that needs a different kind of documentation — the function (and the class, and the module). When you call a function, you usually don't want to read its body at all; you want to know its contract: what it does, what to pass it, what it gives back, and what can go wrong. That contract has a dedicated home, and it's not a comment — it's a docstring: a structured block of documentation attached to the function itself, which tools can extract, display, and surface in editors and generated reference docs.

The distinction from a comment matters. A comment is internal — it explains the implementation to someone reading the body. A docstring is external — it explains the interface to someone who just wants to use the function without reading the body. A comment answers "why does this line do that?"; a docstring answers "what is this function, and how do I call it?" Different reader, different question, different tool.

Python docstrings (PEP 257 and the common styles)

In Python, a docstring is a string literal placed as the very first statement inside a function, class, or module. The language itself picks it up: it becomes the object's __doc__ attribute, help(function) prints it, and editors show it on hover. The baseline conventions are set by PEP 257 (the official Python docstring guide, a Tier-1 source): use triple double-quotes ("""..."""); a one-line docstring fits on one line with the closing quotes on the same line; a multi-line docstring opens with a one-line summary, a blank line, then the elaboration.

Here is Raj's parse function with a proper docstring — the external contract — sitting above the internal why-comments from §24.2:

def parse(text, base=None):
    """Parse a human date expression into a datetime.

    Converts natural-language date strings such as "next Tuesday",
    "in 3 days", or "2025-01-15" into a concrete datetime, resolving
    relative expressions against a reference point.

    Args:
        text: The date expression to parse. Leading/trailing whitespace
            and letter case are ignored.
        base: The reference datetime that relative expressions resolve
            against (e.g. "tomorrow" is base + 1 day). Defaults to the
            current local time.

    Returns:
        A datetime corresponding to the parsed expression.

    Raises:
        ValueError: If no known pattern matches `text`. The exception
            message is the original input, so callers can show the user
            exactly what could not be understood.
    """
    text = text.strip().lower()
    # ... (body and why-comments as in §24.2)

Read what that docstring does for a caller — someone who wants to use parse and never open its body. In four short blocks it tells them: the one-line summary (parse a human date into a datetime), what kinds of input work (with examples — the examples do more than the prose), exactly what text and base mean and that base defaults to now, what comes back, and the one way it can fail and what the failure tells them. A developer can now call parse correctly from the docstring alone. That is the docstring's whole purpose: to make the body unnecessary for the common case of using the function.

The structure here — Args / Returns / Raises — is Google style, one of the two or three widely used Python docstring formats. The others you'll meet are NumPy style (which uses underlined section headers and is common in scientific code) and reStructuredText / Sphinx style (which uses :param x: field markers and is what Sphinx, Python's main doc-generator, consumes natively). The book's advice is the same one it gives for any style choice (Chapter 7, Chapter 23): the specific format matters far less than picking one and using it consistently across a codebase. A project that mixes three docstring styles reads like a document with three authors and no editor — exactly the "elegant variation" failure from Chapter 7, now at the level of documentation format.

✏️ Try This. Open any Python library you have installed — requests, pandas, anything — and in a REPL type help(some_function). What prints is its docstring, rendered. Read three or four. Notice that you're learning to use the function without reading a line of its source, and notice which docstrings tell you everything you need (summary, args, returns, the gotcha) versus which leave you guessing and force you to go read the code. That gap — "I can use it from the docstring" vs. "I have to read the source" — is exactly the gap a good docstring closes. The libraries you find pleasant to use are, almost always, the ones whose docstrings close it.

JavaScript docstrings (JSDoc)

JavaScript has the same need and a different convention: JSDoc, a block comment that opens with /** (two stars, which is what marks it as JSDoc rather than an ordinary /* */ comment) and uses @-tags to describe parameters, return values, and more. Editors and TypeScript read JSDoc to power autocomplete, type-checking, and on-hover documentation, so it pulls double duty as both human-readable docs and machine-readable type information.

Here is the same parse contract as JSDoc, on a JavaScript version of the function:

/**
 * Parse a human date expression into a Date.
 *
 * Converts natural-language date strings such as "next Tuesday",
 * "in 3 days", or "2025-01-15" into a concrete Date, resolving
 * relative expressions against a reference point.
 *
 * @param {string} text - The date expression to parse. Surrounding
 *   whitespace and letter case are ignored.
 * @param {Date} [base=new Date()] - Reference point that relative
 *   expressions resolve against. Defaults to the current time.
 * @returns {Date} The datetime corresponding to the parsed expression.
 * @throws {Error} If no known pattern matches `text`; the error message
 *   is the original input, so callers can show what failed.
 */
function parse(text, base = new Date()) {
  // ...
}

The shape is the same as the Python docstring — summary, parameters, return, what it throws — just expressed in JSDoc's @param / @returns / @throws vocabulary. Two JSDoc-specific conventions worth reading off the example: the {string} and {Date} in curly braces declare each parameter's type (which TypeScript will check), and the square brackets in [base=new Date()] mark base as optional with a default. So the JSDoc carries information the Python docstring left to a separate mechanism — which is the perfect bridge to the next section, because types are documentation too.

🔄 Check Your Understanding. What's the difference between a comment and a docstring, and why does the distinction matter for who reads each one?

Answer A comment is internal documentation aimed at someone reading the implementation — it explains why a particular line or block does what it does, and you only see it if you open the function body. A docstring is external documentation aimed at someone using the function without reading its body — it states the contract (summary, parameters, return value, errors) and tools surface it on hover, in help(), and in generated reference docs, so the caller never has to open the source. The distinction matters because the two readers want opposite things: the implementer wants the why behind a line (a comment's job, §24.2); the caller wants the interface and explicitly does not want the implementation details (the docstring's job). Put implementation reasoning in a comment, put the calling contract in a docstring, and don't swap them — a docstring clogged with internal why-details fails the caller, and a why that's buried in a docstring is invisible to the person editing the line it explains. Right documentation, right place, right reader.


24.4 Type Hints and Signatures: Documentation the Language Checks

There is a form of documentation so good that the language can verify it for you, and most developers under-use it: the types in a function's signature. A type hint (Python's term; also called a type annotation) declares what kind of value a parameter takes and what kind the function returns. It is documentation — it tells a reader the shape of the data — with a superpower ordinary documentation lacks: a tool can check that it's true, and your editor can use it to catch mistakes before you run the code.

See the difference an annotated signature makes. Here's Raj's parse signature with no type information:

❌ Before (untyped signature — the reader must guess or read the body): python def parse(text, base=None): What is text — a string? A list of tokens? What is base — a date? A timestamp? A string? What does the function return — a datetime, a tuple, None on failure? You cannot tell from the signature. You have to read the body, or the docstring, or guess and run it.

✅ After (typed signature — the contract is in the signature itself): python def parse(text: str, base: datetime | None = None) -> datetime: Why it's better: Now the signature is documentation, and it's documentation a type checker enforces. text: str says it's a string. base: datetime | None = None says it's a datetime or omitted. -> datetime says it returns a datetime — always one, never None, which also tells you failure is signaled by an exception, not a None return. A reader knows how to call parse from the signature alone, before reading a word of prose. And unlike a comment, this can't silently rot: if someone changes the function to return a tuple and forgets to update the annotation, a type checker flags the mismatch. It's the one kind of documentation that fights back when it goes stale.

That last property is the quiet reason type hints matter so much as documentation: they cannot drift undetected. A prose comment that says "returns a datetime" can become a lie the moment someone changes the return type, and nothing complains (§24.6 is largely about this hazard). A type hint that says -> datetime is checked against what the function actually does; let it fall out of sync and the tooling objects. It is documentation with a built-in truth-test — the same reason this book trusts a runnable example over a prose description (a thread that runs through Chapter 25's API docs and Chapter 26's tutorials). Documentation a machine can verify is documentation that stays honest.

The same idea appears across languages, with the "documentation" framing strongest where types are optional and you opt in to them: TypeScript annotations on JavaScript, Python's type hints, type signatures in gradually-typed code generally. Where types are mandatory (as in Java, Rust, Go, or C++), the signature documents the data shapes by default — which is part of why statically typed code can sometimes carry fewer comments about what the data is: the types already said it.

A crucial boundary, though, and it's the bridge back to §24.2: type hints document the what, not the why. base: datetime | None tells you what base is; it cannot tell you why it defaults to "now" rather than midnight — that's still a comment's or docstring's job. Types are a powerful slice of documentation, but a strict one: they capture the shape of the data and nothing about intent. So the full picture is layered, and each layer does what only it can:

Layer Documents… Read by Can a tool check it?
Type hints / signature the shape of inputs and output (the what of the data) caller, at a glance; the type checker Yes — checked against the code
Docstring the contract: summary, params, return, errors caller, on hover / in help() / in generated docs Partly (presence/format; not truth)
Comment the why: intent, constraints, decisions, workarounds implementer, reading the body No — must be kept honest by humans
The code itself the exact what — the precise behavior implementer It is the truth

Read top to bottom, that table is the whole chapter in one frame. The code is the ground truth of what. Type hints lift the what of the data into the signature where a tool keeps it honest. Docstrings state the contract for callers. Comments hold the why, which nothing but a human can supply or verify. Each layer documents what the layer below it can't — and the why, at the very bottom of what tools can check, is exactly why §24.2 is the irreplaceable skill.

🔍 Why Does This Work? Why are type hints such uniquely trustworthy documentation, when an ordinary comment saying the same thing ("text is a string") is not? Because of where the information lives and who checks it. A comment lives in prose, parallel to the code, with nothing tying the two together — so the code can change while the comment stays frozen, and the comment quietly becomes false. A type hint lives in the signature, part of the code's own interface, and a type checker continuously compares it against how the function is actually used and what it actually returns. The hint and the reality are coupled: you can't change one without the tool noticing the other is now inconsistent. This is the deep reason the book keeps favoring documentation that's mechanically tied to the thing it describes — runnable examples, type signatures, generated reference docs — over free-floating prose about the code. Coupled documentation can't rot in silence; decoupled documentation always can. When you have the choice, push a fact down into a layer that's checked.


24.5 Self-Documenting Code: When the Best Comment Is No Comment

Here is the move that the unwary miss entirely: sometimes the right response to "this needs a comment" is not to write a comment but to rewrite the code so it doesn't need one. Code that explains itself through clear names and clean structure is called self-documenting code, and the best version of "comment the what" is to make the what so obvious that no comment is tempted. A good name does silently and permanently what a comment does loudly and fragilely.

Start with the most common case: a comment that exists only because a name is bad.

❌ Before (a comment compensating for poor names): ```python

d is the number of days until the subscription expires

d = (exp - datetime.now()).days if d < 7: # warn if less than a week left send_renewal_reminder(u) # u is the user ```

✅ After (good names, no comments needed): python days_until_expiry = (expiration_date - datetime.now()).days if days_until_expiry < RENEWAL_REMINDER_THRESHOLD_DAYS: send_renewal_reminder(user) Why it's better: Every comment in the "before" exists to translate a cryptic name into English: d needs a note because d is meaningless, u needs a note because u is meaningless, and < 7 needs a note because the bare 7 is a mystery. The "after" deletes all three comments by fixing their causes. days_until_expiry is the comment that explained d, but it's better than the comment in three ways: it's attached to the value everywhere it's used (not just at the definition), it can't drift out of sync (it is the code), and it reads inline at every later reference. RENEWAL_REMINDER_THRESHOLD_DAYS turns the magic number 7 into a named constant that explains itself and gives you one place to change the policy. user instead of u, the same. The comments didn't get written better — they got designed out. That's the highest form of "comment the what": arrange the code so the what is self-evident.

This is where naming, a topic that sounds trivial, turns out to be a core documentation skill. An intention-revealing name — a name that states what a thing is for, not just what it is — does the work of a comment without any of a comment's fragility. days_until_expiry over d; is_eligible over flag; RENEWAL_REMINDER_THRESHOLD_DAYS over 7; retry_with_backoff() over do_it_again(). The book's word-choice lesson from Chapter 7 — choose the word that says exactly what you mean — is the same skill applied to identifiers. And it's higher-leverage in code than in prose, because a name in code is read every single place the thing appears, not once. Pick the name that documents, and you've documented every line that uses it, for free, forever.

A close cousin of the bad name is the magic number — a bare numeric (or string) literal whose meaning isn't obvious. if status == 3 (what is 3?), timeout = 4.7 (4.7 what, and why?), retries < 5 (why 5?). Two fixes, depending on which is true. If the number's meaning is unclear, name it: if status == STATUS_ARCHIVED. If the number's value is a deliberate, non-obvious decision, that's a why, and it belongs in a comment, exactly as in §24.2: timeout = 4.7 # upstream gateway hard-cuts at 5s; leave margin to log first. Often you want both — a name and a why-comment — because the name handles the what ("this is the timeout") and the comment handles the why ("4.7 specifically, because…"). Naming and commenting aren't rivals; they divide the labor along the what/why line that runs through the whole chapter.

The structural version of self-documentation is extracting a well-named function. A block of code with a comment on top — # validate the address, then normalize it, then geocode it — is often three functions wearing a trench coat. Pull each step into a named function (validate_address, normalize_address, geocode), and the calling code reads like the comment you were about to write, except it's executable, testable, and can't go stale:

❌ Before (a comment narrating a block): ```python

Validate the address, normalize formatting, then geocode it

if not re.match(ADDRESS_RE, raw): raise ValueError(raw) raw = raw.upper().strip() raw = raw.replace("STREET", "ST").replace("AVENUE", "AVE") lat, lon = geocoder.lookup(raw) ```

✅ After (named functions that read like the comment): python validated = validate_address(raw) normalized = normalize_address(validated) lat, lon = geocode(normalized) Why it's better: The comment in the "before" describes three steps the code then performs in an undifferentiated blob. The "after" names the three steps as functions, so the calling code reads as its own documentation — validate, normalize, geocode, in sequence, no comment required. The narration became structure. And structure beats narration for the same reasons a name beats a comment: it can't drift (renaming the steps without changing them is impossible), it's reusable, it's independently testable, and each function now has its own small, focused body. The comment told you what the block would do; the named functions are what it does.

Now the essential balance, because "self-documenting code" gets badly misused as "comments are bad, never write them." That is wrong, and §24.2 already told you why: self-documenting code can make the what obvious, but it can never express the why. No name, however good, can tell you that the timeout is 4.7 because of a gateway quirk, or that the loop skips item zero because of a file format, or that you chose a list over a set because order matters downstream. Names and structure document the what so well that the what needs no comment — which is exactly what frees comments to do their real job: the why. The two are partners. Good names retire the what-comments (the useless ones); that's what leaves room for the why-comments (the essential ones). The goal was never "no comments." The goal is no redundant comments and every necessary why — and self-documenting code is how you get the first half so the second half can shine.

🔄 Check Your Understanding. A teammate reviews your code and leaves the note: "Good code is self-documenting — you shouldn't need any of these comments." They're half right and half wrong. Where are they right, where are they wrong, and how do you tell which of your comments to keep?

Answer They're right about every comment that explains what the code does — narration like # increment i, or a comment that only exists because a name is cryptic (# d is days until expiry). Those should indeed be deleted, and their causes designed out: rename d to days_until_expiry, name the magic number, extract the well-named function. Self-documenting code makes the what obvious, so what-comments are clutter. They're wrong if they mean all comments should go, because self-documenting code cannot express the why — the reason the timeout is 4.7, the constraint behind skipping item zero, the decision to use a list over a set. No name or structure can hold those; only a comment can. How to tell which to keep: for each comment, ask "does this say something the code could say with a better name or clearer structure?" If yes, delete the comment and fix the code. If no — if it captures intent, a constraint, a decision, a workaround, a reason that lives outside the mechanics — keep it; that's a why, and it's irreplaceable. The teammate's rule, corrected, is: self-documenting code retires the what-comments so the why-comments can do their job.


24.6 The Documentation Spectrum: None → Too Much → Right

You now have the tools; the last skill is judgment — knowing how much to document. It's tempting to think more documentation is always better, but documentation sits on a spectrum with a failure at each end and the target in the middle. Picture it as a line: no documentation at the left (a disaster), too much documentation at the right (a different disaster), and the right amount in between (the skill).

Figure 24.1 (described): a horizontal spectrum. Far left, "None" — undocumented code; label: "every why must be re-derived; the code is a maze." Far right, "Too much" — over-documented code; label: "noise buries signal; comments rot and lie." Center, "Right" — labeled "intent, edge cases, decisions captured; the what left to the code." An arrow from both ends points inward toward the center. Caption: more is not better; calibrated is better.

The left end — no documentation — is the disaster everyone recognizes. This is Raj's parse from §24.1 before we touched it: every why unwritten, every decision a mystery, every magic number a question, the next maintainer forced to re-derive or interrogate. Code with no documentation isn't "clean" or "minimal"; it's a maze that imposes its full re-derivation cost on every future reader, including its author. The cost is real and recurring, and it's why the no-docs end is genuinely the more common and more expensive failure.

The right end — too much documentation — is the disaster people miss, because it disguises itself as diligence. Over-documentation has three distinct failure modes, and each one actively harms the reader:

  • Noise that buries signal. When every line has a comment — most of them narrating the what — the few comments that carry a real why are buried in the chaff. A reader scanning a wall of # increment i comments learns to skip all comments, including the one that says "do not remove this, it prevents data loss." Redundant comments don't just waste space; they train the reader to ignore comments, which disables the documentation that matters. Signal drowns in noise.
  • The maintenance burden — comments that rot. This is the sharpest cost. Every comment is a second thing to keep in sync with the code, and when the code changes and the comment doesn't, the comment becomes stale — it now lies. This is doc drift, and a stale comment is worse than no comment: no comment makes you read the code; a wrong comment makes you trust a falsehood and stop reading. The more comments you write — especially what-comments, which track the code's every move and so rot the fastest — the more lies you'll eventually be maintaining. Over-documentation isn't just clutter; it's a growing liability.
  • Documenting the obvious instead of the subtle. Over-documenters spend their effort narrating the easy, self-evident lines (because those are easy to narrate) and skip the hard, non-obvious why (because that's hard to articulate). The result is documentation that's voluminous and useless: everything obvious is explained, nothing subtle is. Effort spent in exactly the wrong place.

⚠️ Warning — the stale comment is worse than no comment. A line with no comment forces you to read the code, and the code is always true. A line with a stale comment — "returns the user's age" above a function that now returns their date of birth — actively misleads you: you trust the comment, skip the code, and build on a false belief. This is the strongest argument for fewer, why-only comments: every comment you write is a promise to keep it true, and the fewer promises you make, the fewer you'll break. Prefer documentation that can't drift (a good name, a type hint, a runnable example) over prose that can; and when you change code, change its comments in the same edit, every time.

The center — the right amount — is everything you've learned in this chapter, applied with judgment. Document the why, not the what (§24.2). Capture intent, constraints, decisions, and workarounds — the things the code can't say. Let good names and clear structure handle the what so it needs no comment (§24.5). State the contract for callers in a docstring (§24.3). Push the what of the data into checked type hints (§24.4). And then stop — resist the urge to narrate the self-evident. The right amount of documentation is not "a lot" and not "none"; it's exactly the parts a reader can't reconstruct from the code, and not one line more.

A few practical patterns live at the right edge of this judgment, worth knowing because they trip people up:

  • TODO / FIXME comments. A # TODO: handle the timezone edge case is a legitimate, useful comment — it documents a known gap so the next reader isn't surprised and so the work is findable (most tools and editors index TODOs). The discipline: make them findable and, ideally, link a ticket (# TODO(#214): ...). A TODO that sits for three years is a different problem, but as documentation, a marked known-gap beats a silent one.
  • Commented-out code — delete it. A block of code commented out "in case we need it later" is a classic clutter that confuses every future reader (is this supposed to run? why is it here? is it safe to remove?). You have version control; the old code is in the history. Delete commented-out code, and if the reason you removed it matters, write a one-line why-comment about the decision (not the dead code itself).
  • The comment that should be a commit message or an ADR. Some whys are too big for a line-comment — a whole-architecture decision, a rejected design. Those belong in a commit message, a pull-request description, or an Architecture Decision Record (the subject of Chapter 25). The line-comment holds the local why; the larger why scales up to those documents. Knowing where a given why belongs — inline comment, docstring, commit message, ADR — is part of the judgment, and Chapter 25 picks up exactly where the line-comment leaves off.

🔄 Check Your Understanding. Two codebases. Codebase A has almost no comments. Codebase B has a comment on nearly every line. Which is better documented — and what's the trap in that question?

Answer The trap is that neither count tells you anything — "better documented" isn't measured by comment density, at either end. Codebase A might be excellent (self-documenting names, type hints, the few necessary why-comments present) or terrible (a maze of magic numbers and unexplained decisions). Codebase B is probably worse than it looks: a comment on nearly every line almost guarantees a flood of what-narration that buries the real signal, trains readers to skip comments, and rots into lies as the code changes. The right question isn't "how many comments?" but "are the whys captured and the whats left to the code?" Well-documented code is usually sparse with comments — each one earning its place with a real why — plus good names, type hints, and docstrings carrying the rest. If you must bet on the count alone, bet against the wall of comments; over-documentation is a more insidious failure than it looks, because it wears the costume of diligence. But the honest answer is: open them and check what the comments say, not how many there are.


📐 Project Checkpoint

Across the book your Communication Portfolio has grown a piece at a time — and in Chapter 22 you built Piece 3: user documentation, a tested procedure a stranger could follow. Part V now extends that piece toward its developer-facing form, and this chapter adds the innermost layer: documentation inside the code.

Here's the task. Take a real piece of code you've written — a function, a script, a module, fifty lines or so, in any language. (No code of your own? Use a small open-source function, or one of the chapter's examples, and treat it as found code you must document.) Run it through this chapter's full sequence, and keep a before/after as evidence — the before/after is the most persuasive artifact in the whole portfolio, exactly as it's been since Chapter 12:

  1. Audit for why-gaps (§24.1–24.2). Read your code as the stranger you'll be in six months. Mark every spot where you'd ask "why does it do this?" — the magic number, the non-obvious branch, the workaround that looks wrong, the decision you'd forget. Those marks are your real documentation targets.
  2. Delete the what, write the why (§24.2). Remove every comment that narrates the code (# increment i and its kin). Then write a why-comment at each marked gap: intent, constraint, decision, or workaround. Aim for fewer comments than you started with, each one load-bearing.
  3. Add a docstring (§24.3). Write one proper docstring (Python PEP 257 or JSDoc) for the main function — summary, parameters, return, errors — so a caller could use it without reading the body.
  4. Type the signature and fix the names (§24.4–24.5). Add type hints (or note where they'd go), and rename at least one cryptic identifier and one magic number into self-documenting form, deleting any comment that rename makes redundant.
  5. Place it on the spectrum (§24.6). In two sentences, judge your result: did you land in the calibrated center — whys captured, whats left to the code, no narration, no rot — or do you still have noise to cut or a gap to fill?

Keep two artifacts: the original code (the "before") and the documented version (the "after"), plus your two-sentence spectrum judgment. Together they prove the skill this chapter teaches: not "adding comments," but capturing the why a future reader can't recover — and trusting the code, the names, and the types to handle the rest.

Next increment (Chapter 25): you'll scale this same "capture the why" instinct up from the function to the project — writing the README that is your code's front door, documenting an API endpoint as a contract, and recording an architecture decision (an ADR) that preserves a why too big for any line-comment. The inside-the-code documentation you just wrote is the foundation the project-level docs sit on.


24.7 Common Mistakes & Practical Considerations

The rule is simple; the ways people get it wrong under deadline are predictable. Here are the recurring failures and their fixes.

Mistake 1: Commenting the what. The signature error of the genre — narration that restates the code (# loop through the list, # increment i, # return the result). The fix: delete it. If the line is so unclear it needs translating, the problem is the code, not the missing comment — fix the code (§24.5). Reserve comments for the why.

Mistake 2: No comment where there's a real why. The non-obvious decision, the magic number, the workaround that looks like a bug — left unexplained because at the time the reason was obvious to you. The fix: the rule of thumb from §24.2 — if a line made you pause when you wrote it, it needs a why-comment. The lines that made you think are the lines the reader will have to think about too.

Mistake 3: Letting comments rot (doc drift). You change the code and leave the comment, so the comment now lies — worse than no comment. The fix: treat a comment as part of the code it describes; change them in the same edit, always. And prefer documentation that can't drift — a good name, a type hint, a runnable example — over prose that can.

Mistake 4: Over-documenting. A comment on every line, in the belief that more is more. The fix: it isn't. Density buries the few real whys, trains readers to skip comments, and multiplies the rot. Aim for sparse and load-bearing: names and types carry the what, a few comments carry the why, nothing narrates the obvious.

Mistake 5: A comment that should have been a better name. Writing # d is days until expiry instead of just naming the variable days_until_expiry. The fix: when a comment exists only to translate a cryptic name, design it out — rename the variable, name the constant, extract the function. The best what-comment is no comment, because the what is self-evident.

Mistake 6: Leaving commented-out code. Dead code "kept in case," confusing every reader about whether it should run. The fix: delete it; version control remembers. If the decision to remove it matters, write a one-line why about the decision, not the corpse.

Mistake 7: Inconsistent docstring/comment style across a codebase. Three docstring formats, two comment conventions — the multi-author "elegant variation" failure from Chapter 7, now in the docs. The fix: pick one convention (a docstring style, a comment style) and apply it everywhere; consistency matters more than which one you pick.

An honest "it depends." How much documentation a piece of code warrants scales with its audience and lifespan, exactly as a document's revision effort scaled with its stakes in Chapter 12. A throwaway script you'll run once and delete needs almost nothing — maybe a one-line why for the one weird part. A public API that thousands will call needs complete docstrings, typed signatures, examples, the works, because its documentation is its interface (the thesis of Chapter 25). A function deep in a system that ten engineers will maintain for years sits in between, and leans toward more. The skill is not "always document heavily" or "always document lightly"; it's matching the documentation to who will read it and for how long. The one constant across all of them: wherever there's a non-obvious why, capture it — that part is never optional, because the why is the one thing no future reader, on any timeline, can recover from the code alone.


Frequently Asked Questions

What does "comment the why, not the what" mean?

It means your comments should explain why the code does something — the intent, the constraint, the decision, the reason — not what it does, which the code already states. A comment like # increment i next to i += 1 narrates the obvious and adds nothing; the code says that already. A comment like # Skip item 0: it's the header row, not data captures something the code cannot express — the reason the loop starts at one. The principle rests on a simple fact: code is a complete record of what happens but a silent record of why you chose it. Comments exist to hold the why. In practice this means writing fewer comments (most lines explain themselves) and making the few you write count — each one capturing a reason a future reader couldn't reconstruct from the code.

How do you write a good docstring in Python?

Follow PEP 257 (Python's official docstring convention): put a triple-quoted string ("""...""") as the first statement in the function, open with a one-line summary, leave a blank line, then describe the parameters, the return value, and what the function can raise. A widely used structured format is Google styleArgs:, Returns:, Raises: sections — though NumPy and Sphinx/reStructuredText styles are also common; pick one and use it consistently across the project. The test of a good docstring: a developer can call the function correctly from the docstring alone, without reading the body. Include examples where they clarify usage — a concrete example often teaches the contract faster than a paragraph of description. The docstring documents the interface for callers; keep implementation whys in comments inside the body, where the person editing those lines will see them.

Are type hints documentation?

Yes — and they're a special, trustworthy kind. A type hint (or annotation) in a signature, like def parse(text: str, base: datetime | None = None) -> datetime, tells a reader the shape of the inputs and output at a glance, so they can call the function correctly without reading its body. What makes them better than a prose comment saying the same thing is that a tool can check them: a type checker compares the hint against how the function is actually used and what it returns, so a hint can't silently rot the way a comment can. The boundary to remember: type hints document the what of the data, never the why. base: datetime | None says what base is; it can't say why it defaults to "now" — that's still a comment's job. Use types to carry the data shapes (checked, honest) and comments to carry the reasons (which only a human can supply).

Is self-documenting code real, or an excuse to skip documentation?

Both, depending on who's using the phrase. Self-documenting code is real: good names (days_until_expiry not d), named constants instead of magic numbers, and small well-named functions genuinely make the what obvious, so it needs no comment — and a name beats a comment because it's read everywhere the thing is used and can't drift out of sync. But "self-documenting code" becomes an excuse the moment someone uses it to mean "comments are unnecessary." That's false: no name or structure, however clean, can express the why — the reason the timeout is 4.7 seconds, the constraint behind a workaround, the decision to use a list over a set. Self-documenting code retires the what-comments; it cannot touch the why-comments. The correct version of the idea: write code clear enough that the what needs no comment, so that your comments are free to capture the why — which is the only thing they could ever capture that the code can't.

How many comments should code have?

There's no number, and the question itself is a trap — comment density doesn't measure documentation quality at either extreme. Code with almost no comments can be excellent (self-documenting names, type hints, the necessary whys present) or terrible (an unexplained maze). Code with a comment on every line is usually worse than it looks: a flood of what-narration buries the few real whys, trains readers to skip comments entirely, and rots into lies as the code changes. Aim instead for sparse and load-bearing: every comment earns its place by capturing a why the code can't express, while names, structure, types, and docstrings carry everything else. Well-documented code tends to be light on comments and heavy on clarity. If you're counting comments, you're measuring the wrong thing — read what they say, and ask whether each one tells you something the code can't.


Chapter Summary

Key Takeaways

  • Your first reader is you, six months from now — and the next maintainer right behind you. Both arrive with none of the context you had while writing. Document for the confused reader who has lost the reasoning, because that reader is real and guaranteed.
  • Comment the why, not the what. The code already says what it does; comments exist to capture what the code can't — intent, constraints, decisions, workarounds, the reason behind a magic value. This means fewer comments, each load-bearing.
  • Docstrings document the contract for callers — summary, parameters, return, errors — so a developer can use a function without reading its body (Python: PEP 257 / Google or NumPy style; JavaScript: JSDoc). A comment is internal (the why of a line); a docstring is external (the interface).
  • Type hints are documentation the language checks. A typed signature states the shape of inputs and output and can't silently rot — but it documents the what of the data, never the why.
  • Self-documenting code retires the what-comments. Good names, named constants instead of magic numbers, and small well-named functions make the what self-evident — freeing comments to do their real job, the why. "Self-documenting" never means "no comments"; it means "no redundant comments."
  • The documentation spectrum runs none → too much → right. None is a maze; too much buries the signal and rots into lies (a stale comment is worse than none); the target is the calibrated center — every why captured, every what left to the code, nothing narrated.
  • The threshold concept: the code says what it does; documentation exists to capture what the code cannot say. Cross that, and redundant comments read as clutter and a missing why reads as a gap.

Action Items

  1. On your next function, delete every comment that narrates the code, then write a why-comment only where a line made you pause when you wrote it.
  2. Give your most-called function a complete docstring (PEP 257 or JSDoc): summary, params, return, errors — usable without reading the body.
  3. Add type hints to one signature, and rename one cryptic variable and one magic number into self-documenting form.
  4. The next time you change code, change its comments in the same edit — never leave a comment to rot.
  5. Audit one file for the documentation spectrum: cut the narration, fill the why-gaps, delete the commented-out code.

Common Mistakes

  • Commenting the what (narration that restates the code).
  • No comment where there's a real why (the line that made you pause).
  • Letting comments rot into lies (doc drift) — change code and comment together.
  • Over-documenting — density buries the signal and multiplies the rot.
  • A comment that should have been a better name or a named constant.
  • Leaving commented-out code (delete it; version control remembers).
  • Inconsistent docstring/comment style across a codebase.

Decision Framework

For a given line or block, ask… …and do this
Does the comment just restate what the code does? Delete it. The code already says the what.
Did this line make me pause / decide / work around something? Write a why-comment: intent, constraint, decision, or reason.
Does a comment exist only because a name is cryptic? Rename it (or name the magic number / extract a function). Design the comment out.
Does a caller need to know how to use this function? Write a docstring (PEP 257 / JSDoc): summary, params, return, errors.
Is the shape of the data unclear from the signature? Add a type hint — documentation a tool keeps honest.
Did I change the code? Change its comments in the same edit. Never let one rot.
Is this why too big for a line-comment? Move it up: a commit message, a PR description, or an ADR (Chapter 25).

Spaced Review

A few questions reaching back, to strengthen retention.

  1. (From Chapter 3) Chapter 3 taught the "so what?" test and the discipline of cutting fog — words that occupy space but carry no meaning. How is a # increment i comment the exact same failure as "it is important to note that," and how does "comment the why, not the what" map onto Chapter 3's "keep what carries a fact, cut what's packaging"?
  2. (From Chapter 8) Chapter 8 taught one idea per paragraph and the given-new contract — ordering information so each piece builds on the last. A good docstring has a parallel structure (one-line summary, then elaboration; params before return before errors). How is writing a clear docstring an exercise in the same ordering discipline, and where does "one term per concept" (Chapter 7) reappear when you choose a variable name?
  3. (From Chapter 7, bridging) Chapter 7 taught that choosing the right word documents your meaning precisely, and that elegant variation (naming one concept several ways) is a bug. How does that lesson reappear twice in this chapter — once in self-documenting names, and once in the rule to keep docstring style consistent across a codebase?
Answers 1. A `# increment i` comment and "it is important to note that" are the *same failure* — words that take up space and carry zero information for the reader. The phrase "it is important to note" tells you nothing the following sentence won't; `# increment i` tells you nothing the line `i += 1` doesn't. Both are *packaging*, not *content*, and [Chapter 3](../../part-01-writing-is-thinking/chapter-03-clarity/index.md)'s move is identical for both: delete it, lose nothing, because the meaning was elsewhere (in the real sentence; in the actual code). "Comment the why, not the what" *is* [Chapter 3](../../part-01-writing-is-thinking/chapter-03-clarity/index.md)'s "keep what carries a fact, cut what's packaging," mapped onto code: the *what*-comment is packaging (the code already carries that fact), and the *why*-comment is content (it carries a fact — the reason — that exists *nowhere else*). Clarity in code documentation is the same skill as clarity in prose: cut the words that don't carry information, keep the ones that do. 2. A docstring is ordered for the reader exactly as a paragraph is: the one-line summary is the *topic sentence* (the controlling idea, stated first, that everything else serves), and the params → return → errors sequence is a given-new ordering — you learn what to pass before what you get back before how it fails, each piece building on the last, so a caller reads it top-to-bottom and is never lost. Writing a muddled docstring (return value before you know the params, errors scattered through) fails the same way a muddled paragraph does: the reader can't build understanding in order. **One term per concept** reappears the moment you name a variable: a concept should have *one* name used consistently (`user`, not `user` here and `u` there and `account` elsewhere), exactly as a document should call one thing by one term — elegant variation is a bug in identifiers as much as in prose, and worse, because a wrongly-varied name actively misleads about whether two things are the same. 3. [Chapter 7](../../part-02-building-blocks/chapter-07-word-choice-tone-voice/index.md)'s lesson — the right word documents your meaning, and naming one concept several ways (elegant variation) is a bug — reappears as the spine of two sections. In **self-documenting names** (§24.5): an intention-revealing name (`days_until_expiry`, not `d`) is [Chapter 7](../../part-02-building-blocks/chapter-07-word-choice-tone-voice/index.md)'s "choose the word that says exactly what you mean," applied to identifiers, and it's higher-leverage in code because the name is read at *every* use, not once. In **docstring-style consistency** (§24.3) and the §24.7 mistake about mixed conventions: using three docstring formats in one codebase is elegant variation at the format level — the same concept (a function's contract) dressed three different ways — and it reads like a document with three authors and no editor, exactly the failure [Chapter 7](../../part-02-building-blocks/chapter-07-word-choice-tone-voice/index.md) named. Same principle, two scales: consistency in what you *name* things, and consistency in how you *format* the documentation.

What's Next

You can now document code from the inside: comment the why and delete the what, write a docstring that states the contract, let type hints and good names carry the rest, and place any code on the spectrum between no documentation and too much. Chapter 25 steps back from the line to the project. The same instinct — capture what the reader can't reconstruct — scales up to the README that is your code's front door (Raj's, the makeover we've been building toward), to API documentation that defines a contract for everyone who calls your code, and to the Architecture Decision Record, which preserves a why too large for any inline comment. Where this chapter asked "why does this line do that?", Chapter 25 asks "what does this whole project do, how do I start, and why is it built this way?" — the same question, one level up. The why you learned to capture in a comment is about to become the why that anchors an entire project's documentation.


Practice: Exercises · Quiz Go deeper: Case Study · Case Study 2 Review: Key Takeaways · Further Reading