> "Documentation is a love letter that you write to your future self."
Prerequisites
- 22
- 24
- 4
- 3
- Basic familiarity with what a README, an API, and a code repository are (you do not need to write code in this chapter)
Learning Objectives
- Explain why a README is the front door of a project and identify the four questions a reader arrives with (understand).
- Rewrite a wall-of-text README into a scannable structure with a working quick-start a stranger can run (apply).
- Document an API endpoint completely—purpose, request, response, error codes, and a runnable example—so a developer can call it without reading the source (apply).
- Write an Architecture Decision Record that captures the why—context, decision, and consequences—rather than only the what (apply).
- Evaluate a project's documentation set (README, API reference, ADRs, changelog, contributing guide) against the standard of a developer who lands on it cold (evaluate).
In This Chapter
- Chapter Overview
- 25.1 The README Is the Front Door
- 25.2 The Quick-Start: Time-to-First-Success Is the Metric
- Quick start
- Documentation
- Contributing
- License
- 25.6 Changelogs: Telling Users What Changed
- 25.7 Contributing Guides and the Open-Source Documentation Standard
- Running tests
- Code style
- Submitting a change
- Where things live
- 25.8 Common Mistakes & Practical Considerations
- Frequently Asked Questions
- Chapter Summary
- Spaced Review
- What's Next
Chapter 25: README Files, API Documentation, and Developer-Facing Writing
"Documentation is a love letter that you write to your future self." — Damian Conway
Chapter Overview
Raj Patel maintains an open-source library called chronoparse—a small, genuinely useful tool that turns messy human date strings ("next Tuesday," "3 days ago," "2024-Q3") into structured timestamps. He built it for a side project two years ago, open-sourced it on a whim, and forgot about it. Then one Monday he opened his repository's issue tracker and found the same question, filed eleven times by eleven different people over eighteen months: "How do I install this?" Below it, a twelfth issue, more pointed: "Is this project dead? I wanted to use it but I couldn't figure out how to even get started, so I wrote my own." Raj's library wasn't dead. It worked perfectly. But its README—the first and often only thing a developer reads before deciding whether to use a tool—was a single 800-word paragraph he'd dashed off at 1 a.m., explaining the philosophy of date parsing and never once showing how to install or call the thing. Eleven people had wanted his code. None of them could get in the door.
This chapter is about the writing that lives next to code and decides whether anyone uses it: the README that is a project's front door, the API documentation a developer reads to call your service without reading your source, the Architecture Decision Record (ADR) that tells a future maintainer why the code is shaped the way it is, the changelog that tells users what changed between versions, and the contributing guide that turns a stranger into a collaborator. It is developer-facing writing, and it sits at the intersection of two earlier chapters: it's instructions (Chapter 22—the reader is a doer who runs your steps on a real machine) and it's documentation (Chapter 24—you explain the why, not just the what). What's new here is the stakes and the standard. In open source and in any shared codebase, the documentation is the interface. A brilliant library with an unusable README is, for practical purposes, a library that does not exist—because the developer who can't get started in five minutes leaves and builds their own, exactly as Raj's twelfth visitor did. This is theme 2 (audience is everything) at industrial scale: your audience is a busy developer with twelve browser tabs open and no patience, and theme 5 (structure serves the reader) made concrete, because the order of a README is the order of the questions a newcomer asks.
Here is the threshold this chapter asks you to cross: documentation is the product's user interface for developers. Not an afterthought you write once the "real" work (the code) is done—the actual surface through which other humans encounter and judge your work. If they can't get started, the code doesn't exist to them. Once you see your docs as UI rather than as a chore, the priorities flip: the quick-start matters more than the architecture essay, the runnable example matters more than the prose description, and "did a stranger get it working in five minutes?" becomes the test that matters—the same "someone who has never done this" test from Chapter 22, now run on a clean machine. By the end of this chapter, you will be able to take a neglected wall-of-text README and rebuild it into a front door a stranger can walk through, document an API endpoint so completely that a developer never needs to read your code, and record an architecture decision so a maintainer two years from now understands why—and you'll watch Raj do all three.
In this chapter, you will learn to:
- Structure a README around the four questions every visitor arrives with—what is this, how do I install it, how do I use it, how do I contribute—with a quick-start a stranger can actually run.
- Document an API endpoint completely: purpose, request format, response format, status and error codes, and a runnable example a developer can copy and execute.
- Write an Architecture Decision Record that captures the why—context, decision, and consequences—so a future maintainer understands a choice instead of reverse-engineering it.
- Write a changelog and a contributing guide that meet the open-source documentation standard.
- Evaluate a project's whole documentation set against the test that matters: can a developer who lands on it cold get started without help?
📗 Software/CS track: This is a core chapter for you, and it builds directly on Chapter 24 (Code Documentation) and Chapter 22 (Instructions). The README quick-start is Chapter 22's "test your instructions on a clean machine" applied to your own setup steps; the ADR is Chapter 24's "comment the why" scaled up to a design decision. This chapter pairs with Chapter 26 (Tutorials, which adds the Diátaxis framework that explains why a README, a tutorial, and an API reference are different documents) and Chapter 34 (Writing for Computer Science, which goes deeper on ADRs, PR descriptions, and postmortems). Prioritize §25.1–§25.3 (README), §25.4 (API docs), and §25.5 (ADRs).
25.1 The README Is the Front Door
Every project has a front door, and for software that door is the README. It is the file a code-hosting platform displays automatically on the project's landing page; it is the first thing a developer reads after a search result or a recommendation brings them to your repository; and for a huge fraction of visitors, it is the only thing they read before deciding to use your tool or close the tab. The README is where a stranger forms a thirty-second judgment about whether your project is alive, trustworthy, and worth their time. Most READMEs fail that judgment not because the code is bad but because the writing never answers the questions the visitor actually has.
Start, as always, with the failure. Here is the README that left eleven people unable to use Raj's library—reproduced in full, because its length is part of the problem.
❌ Before — Raj's original
chronoparseREADME (a wall of text; fictional but realistic):
# chronoparse
Parsing dates is one of the most deceptively difficult problems in software
engineering. Human beings express time in an astonishing variety of ways —
absolute dates, relative offsets, named periods, fiscal quarters, colloquial
references like "a week from Thursday" — and the gap between how people write
dates and how machines store them (typically as an integer offset from an
epoch) is enormous. Most date libraries handle the easy cases (ISO 8601
strings) and fall apart on the messy reality of how dates actually appear in
user input, log files, scraped web content, and natural language. chronoparse
was born out of frustration with exactly this gap. The core philosophy behind
chronoparse is that date parsing should be forgiving by default but strict
when you ask it to be, and that the library should make the common case trivial
while still exposing the full power of the underlying grammar engine for the
rare case. Under the hood, chronoparse uses a tokenizer that breaks an input
string into candidate temporal tokens, followed by a grammar-driven parser
that assembles those tokens into a normalized representation, which is then
resolved against a reference time (defaulting to now) to produce a concrete
timestamp. The grammar supports a wide range of locales and can be extended
with custom rules. Performance was a major consideration during development and
the parser has been benchmarked extensively against alternatives. There is also
support for time zones, although this is an area where the semantics get
genuinely complicated and you should read the section on time zone handling
carefully before relying on it in production. The project is MIT licensed and
contributions are welcome, although please open an issue first to discuss any
substantial changes so that we can make sure they align with the project's
direction before you invest significant effort in a pull request.
Read it as Raj wrote it and it's not wrong—it's thoughtful, even well-written prose. The problem is that it answers a question nobody asked ("what is your philosophy of date parsing?") and ignores every question a visitor actually has. A developer who lands here wants to know, in roughly this order: What does this do? Does it solve my problem? How do I install it? How do I use it—show me one example I can run? Is it maintained? Raj's README answers none of those in a form the reader can act on. There is no install command. There is no usage example. The one concrete fact a hurried developer needs—"here is how you parse a date with this"—does not appear anywhere in 800 words. So the reader does what readers do when the front door is locked: they leave.
🚪 Threshold Concept: Documentation is the product's user interface for developers.
How most developers think about docs: the code is the real work; the README is paperwork you write afterward, reluctantly, to "explain" the thing you already built. Its purpose is to describe the project. A good README, in this view, is a thorough one—it covers the design, the philosophy, the internals. Length signals effort.
What documentation actually is: the interface through which other humans encounter your work. A developer never sees your elegant code first; they see your README, run your install command, and copy your example. Those are the surfaces they touch. If the install command isn't there, the elegant code behind it might as well not exist—they can't reach it. Documentation isn't a description of the product added to the product; for everyone who isn't you, the documentation is the product's usable surface. A library is exactly as good as a stranger's ability to get it working, and that ability is created entirely by your docs.
Once you cross this threshold, your priorities invert. You stop optimizing the README for completeness (covering everything you know) and start optimizing it for time-to-first-success (how fast a stranger gets a working result). The quick-start jumps to the top; the architecture essay moves to a separate document or the bottom. You measure the README not by how much it explains but by whether someone you've never met can go from "found your repo" to "it works on my machine" without asking you a single question. That is the same standard as Chapter 22's beginner test—and for the same reason: you, who built it, are the worst judge of whether a newcomer can get in.
The fix is not to write more. It's to write what the visitor needs, in the order they need it, and to move the philosophy essay out of the doorway. A README has a conventional structure precisely because visitors arrive with the same questions every time, and a structure that mirrors those questions lets a reader scan to the part they need—the scanning reader of Chapter 4, now holding a keyboard. We'll build Raj's new README section by section across the next two sections; here is the skeleton it will follow, which is close to a community standard:
# Project name + one-line description ← what is this? (in one sentence)
badges (build, version, license) ← is it alive and trustworthy?
## What it does / Features ← does it solve my problem?
## Installation ← how do I get it?
## Quick start / Usage ← show me it working (the most important part)
## Documentation / API reference (link) ← where's the detail?
## Contributing ← how do I help?
## License ← can I use it?
🔄 Check Your Understanding Raj's original README is 800 words of genuinely informative prose about how date parsing works and why it's hard. Why does it fail as a README despite containing no false statements and showing real thought?
Answer
Because a README's job is not to describe the project but to get a visitor to first success, and it answers none of the questions a visitor actually arrives with. The reader wants, fast: what does this do (one line), does it solve my problem, how do I install it, show me one usage example I can run, is it maintained. Raj's README answers a question nobody asked—his philosophy of date parsing—and never gives an install command or a single runnable example. Quality of prose is irrelevant if it's the wrong content in the wrong order. This is the audience principle (Chapter 2): he wrote what he found interesting (the internals) instead of what the reader needs (a way in). The 800 words are a wall the reader has to climb before reaching anything actionable—and they won't.
25.2 The Quick-Start: Time-to-First-Success Is the Metric
If a README has one job, it is this: get a stranger from "I found this" to "it works" as fast as possible. Everything else—the feature list, the architecture, the contribution guidelines—is secondary to that first working result, because a developer who gets a quick win trusts the tool and reads on, while one who gets stuck leaves and rarely comes back. The single most important section of any README is therefore the quick-start: the shortest path from nothing to a working example. The metric is time-to-first-success, and your job is to make it small.
A quick-start has two parts: how to install the thing, and how to use it once installed—each shown as something the reader can run, not described in prose. Let's watch Raj build each.
Installation: the command, not the concept
Raj's original README had no installation section at all—the eleven "how do I install this?" issues are the direct result. The fix is almost insultingly simple, and that's the point: an installation section should be the exact command, in a copy-pasteable code block, with nothing between the reader and it.
❌ Before (no install section — or, when pressed in an issue comment, Raj wrote this): "You can install it through the usual Python package manager, or from source if you prefer. It supports Python 3.8 and above. There are a couple of optional dependencies for the locale features if you need those."
✅ After (a real Installation section):
## Installation
```bash
pip install chronoparse
Requires Python 3.8+. For non-English locale support, install the extras:
pip install "chronoparse[locales]"
**Why it's better:** The "before" *describes* installation ("the usual package manager," "from source if you prefer," "a couple of optional dependencies") and forces the reader to translate that description into an actual command they can run—which is exactly the description-instead-of-instruction failure from [Chapter 22](../../part-04-professional-workplace-writing/chapter-22-instructions-procedures/index.md), where the reader is a *doer* who needs the command, not a summary of the command. The "after" gives the literal command in a code block the reader copies with one click. The version requirement is one short line, not a clause buried in a paragraph. The optional extras are shown as a second runnable command, only for the reader who needs them. The reader is installed in ten seconds instead of guessing for two minutes.
### Usage: the runnable example is the best documentation
Now the heart of it. A developer evaluating a library does not want a paragraph about how to use it; they want to *see it used*, in code they can copy, run, and adapt. A **runnable example**—a complete, minimal snippet that does one real thing and shows the result—is worth more than any amount of prose description, because it answers the reader's deepest question ("what does using this actually look like?") in the form they think in. Raj's original README, remember, contained zero examples.
> **❌ Before (Raj describing usage in prose, as his README implicitly did):**
> *"To use chronoparse, you pass your input string to the parser, which tokenizes it and resolves it against a reference time to produce a timestamp. By default the reference time is the current moment, but you can override it. The result is a normalized datetime object you can then use in your application."*
>
> **✅ After (a runnable quick-start example):**
```markdown
## Quick start
```python
from chronoparse import parse
# Parse a human date string into a datetime
parse("next Tuesday")
# → datetime.datetime(2024, 6, 11, 0, 0)
parse("3 days ago")
# → datetime.datetime(2024, 6, 5, 14, 30)
# Resolve relative dates against a fixed reference time
parse("in 2 weeks", reference="2024-01-01")
# → datetime.datetime(2024, 1, 15, 0, 0)
That's it. parse() accepts any string chronoparse understands and returns a
standard datetime. See the full reference for
locales, time zones, and custom grammars.
**Why it's better:** The "before" *tells* the reader about usage in abstract terms ("you pass your input string to the parser… resolves it against a reference time"); the "after" *shows* it, in three lines the reader can copy into a Python prompt and watch work. Crucially, each line shows both the call *and* the result (the `# →` comment), so the reader sees exactly what they'll get—no guessing. The example is minimal (no imports they don't need, no configuration, no edge cases) and covers the one thing 90% of users want: parse a string, get a datetime. It ends with "That's it"—a deliberate signal that the common case really is this simple—and a *link* to the full reference for the 10% who need locales and time zones, keeping the complexity out of the doorway. A reader who copies these three lines and sees them work is now a user. That conversion is the README's entire purpose.
> 🔍 **Why Does This Work?**
> Why is a three-line runnable example more persuasive and more useful than three paragraphs accurately describing how the library works? Think before reading on.
>
> Because the reader's real question is "what will it be like to use this in *my* code?"—and an example answers that question in the exact medium they'll use (code), while a description answers a *different* question (how it works internally) in a medium they then have to translate. A runnable example does several things at once that prose can't: it shows the precise syntax (no ambiguity about argument order or names), it shows the actual return value (so they can see if it fits their need), it's *verifiable* (they can run it and confirm it's true—prose they have to trust), and it's *adaptable* (they copy it and change the string to their case). It also signals competence and care: a maintainer who shows a clean, working example is telling the reader "I've thought about your first five minutes." Description engages the reader's analytical reading; an example engages their *doing*—and the reader of a README, like the reader of any instructions ([Chapter 22](../../part-04-professional-workplace-writing/chapter-22-instructions-procedures/index.md)), would rather do than read. The example collapses "understand, then translate, then try" into just "try."
A few rules make quick-start examples reliable rather than aspirational:
- **It must actually run.** The fastest way to destroy trust is a quick-start example that errors on the first line—a wrong import, a renamed function, an argument that no longer exists. README examples drift out of sync with the code as the code changes, so they need the same defense as any instruction: run them on a clean setup before you ship. (Some projects test their README examples automatically in CI; that's the gold standard.)
- **Minimal, then a link to more.** The quick-start shows the *one common case*, stripped to the fewest lines. Resist showing every option—that belongs in the reference. The quick-start's job is the first success; the reference's job is completeness.
- **Show the output.** An example that shows input but not result leaves the reader unsure what they'll get. The `# →` comment (or a printed output block) closes that gap.
- **Copy-pasteable.** No `$` prompts mixed into code the reader will copy (they'll paste the `$`), no "replace this with your value" without making clear what's a placeholder. The reader should be able to copy the block and run it with minimal edits.
> ✏️ **Try This**
> Find a README for a library you've used (or open any popular project on a code-hosting site). Time yourself: from landing on the README, how many seconds until you find a command or example you could actually run? If it's more than thirty seconds, the quick-start is buried. Now look at *what* sits above the quick-start—is it a feature list and one-line description (good, that's orientation) or paragraphs of background and motivation (bad, that's a wall)? You're learning to see the README the way a hurried stranger does.
> [📍 Good stopping point — you now have the most important half of the README: a front door that orients, and a quick-start that gets the reader to a working result. The next sections cover the rest of the README, then API reference docs and ADRs.]
---
## 25.3 The Rest of the README: Features, Contributing, and the Whole Makeover
A quick-start gets the reader working; the rest of the README helps them decide whether to bother and how to go further. Four more sections do most of that work, and none of them should be long.
**The one-line description and the feature list answer "is this for me?"** Before a reader will run your install command, they need to know in one sentence what the project *is*, and in a short list what it *does*—so they can decide in fifteen seconds whether to keep reading. The one-liner sits right under the project name; the feature list is a handful of bullets naming concrete capabilities, not adjectives.
> **❌ Before:** *"chronoparse is a powerful, flexible, and performant date-parsing solution designed to handle the full complexity of real-world temporal data with a clean and intuitive API."*
>
> **✅ After:**
> *One-liner: **chronoparse turns human date strings ("next Tuesday," "3 days ago") into Python `datetime` objects.***
> *Features:*
> - *Parses relative ("in 2 weeks"), absolute ("2024-03-15"), and colloquial ("a week from Friday") dates*
> - *Resolves against any reference time, not just "now"*
> - *Time-zone aware*
> - *Extensible grammar for custom formats*
> - *Zero dependencies for the core parser*
**Why it's better:** The "before" is all adjectives—"powerful, flexible, performant, clean, intuitive"—which are claims the reader can't verify and every project makes, so they carry no information (this is the empty-intensifier problem from [Chapter 7](../../part-02-building-blocks/chapter-07-word-choice-tone-voice/index.md), in marketing form). The "after" leads with a concrete one-liner that includes *examples in the sentence itself* ("next Tuesday," "3 days ago"), so the reader instantly knows whether this matches their problem, then lists specific capabilities a reader can map to their need ("resolves against any reference time"—do I need that? yes/no). Concrete beats laudatory: the reader is shopping, and they want a spec, not a sales pitch.
**Badges answer "is this alive and trustworthy?"** The little status images at the top of many READMEs—build passing, latest version, test coverage, license—are a compressed credibility signal. A green "build: passing" badge and a recent version number tell a wary visitor the project is maintained without their having to dig through commit history. Use a few that carry real information (build status, version, license); don't paper the top of the README with twenty decorative badges, which is noise.
**The contributing section answers "how do I help?"**—briefly, with a pointer to the details. In the README itself, a short paragraph suffices: how to report a bug, how to propose a change, and a link to the full `CONTRIBUTING.md` (covered in §25.7). Raj's original buried this in his closing paragraph ("contributions are welcome, although please open an issue first…"); it deserves its own heading so a would-be contributor can find it.
**License answers "can I legally use this?"**—in one line naming the license, with the full text in a `LICENSE` file. A project with no stated license is, legally, *not* open for others to use, no matter how public the code; the one-line license statement removes that doubt.
Here is Raj's `chronoparse` README, fully rebuilt—the same project, the same facts, reorganized around the reader's questions:
> **✅ After — Raj's rebuilt README (the makeover):**
```markdown
# chronoparse
[]()
[]()
[]()
**chronoparse turns human date strings ("next Tuesday," "3 days ago") into
Python `datetime` objects.**
Most date libraries handle ISO strings and give up on the messy way dates
actually appear in user input, logs, and scraped text. chronoparse handles both.
## Features
- Parses relative ("in 2 weeks"), absolute ("2024-03-15"), and colloquial
("a week from Friday") dates
- Resolves against any reference time, not just "now"
- Time-zone aware
- Extensible grammar for custom formats
- Zero dependencies for the core parser
## Installation
```bash
pip install chronoparse
Requires Python 3.8+. For non-English locales: pip install "chronoparse[locales]".
Quick start
from chronoparse import parse
parse("next Tuesday") # → datetime(2024, 6, 11, 0, 0)
parse("3 days ago") # → datetime(2024, 6, 5, 14, 30)
parse("in 2 weeks", reference="2024-01-01") # → datetime(2024, 1, 15, 0, 0)
parse() accepts any supported string and returns a standard datetime.
Documentation
- Full API reference — every function and option
- Supported formats — the complete grammar
- Time-zone handling — read before relying on TZ in production
Contributing
Bug reports and pull requests welcome. Please open an issue to discuss substantial changes first. See CONTRIBUTING.md for setup, tests, and style.
License
MIT — see LICENSE.
**Why the makeover works:** Every fact from the original 800-word paragraph survives somewhere, but now it's *placed*. The philosophy ("most date libraries give up on messy input") shrank to two sentences under the one-liner, where it motivates without blocking. The internals (tokenizer, grammar engine) moved out of the README entirely, into the linked reference where the rare reader who cares can find them. The install command and the runnable example—absent before—are now front and center, so the eleven "how do I install this?" issues never get filed again. A visitor can scan the headings (Features, Installation, Quick start, Contributing) and jump to exactly what they need, because the headings name *the reader's questions* ([Chapter 4](../../part-01-writing-is-thinking/chapter-04-structure/index.md)'s informative-header lesson, applied to a README). The rebuilt README is shorter than the original and infinitely more useful—because usefulness was never about length; it was about answering the right questions in the right order.
> 🧩 **Productive Struggle**
> Before reading on: a colleague says, "I don't have time to write a whole fancy README for my internal tool—it's just for our team, everyone here already knows roughly what it does." They have fifteen minutes. What are the *two or three* highest-value things they should put in those fifteen minutes, and what can they safely skip? Think about who actually reads an internal tool's README and when.
>
> <details><summary>One good answer</summary>Highest value, in order: **(1) A one-line description plus the install/run command.** Even teammates forget how to set up a tool they haven't touched in six months; the future reader of an internal README is usually *your own team, later*, including the version of you who's forgotten the details (this is the chapter's epigraph—docs are a love letter to your future self). One command that gets it running is the biggest fifteen-minute win. **(2) One runnable usage example.** The single most common way to invoke it, copy-pasteable. **(3) Where to get help / who owns this**—a name or channel, so a stuck teammate knows where to go. Safely skip, for now: the philosophy, the architecture, exhaustive options, badges, a polished feature list, contribution guidelines. The "it's just internal" instinct is exactly backwards: internal tools often have *worse* docs and *higher* turnover of who remembers them, so a five-minute quick-start pays off more, not less. "Everyone already knows" is true for about three months and the curse of knowledge talking.</details>
---
## 25.4 API Documentation: Letting Developers Use Your Code Without Reading It
When your software exposes an interface other programs call—a web API, a library's public functions, a command-line tool—the documentation for that interface has one defining goal: **let a developer use it correctly without reading your source code.** That's the whole game. Source code is the implementation; API documentation is the *contract*, the promise of what the interface accepts and returns, written so the caller never has to open your files to find out. When the contract is documented well, a developer integrates in an afternoon. When it isn't, they read your code, guess, get it wrong, and file a bug that's really a documentation gap.
API documentation is **reference documentation**: the reader does not read it front to back: they arrive needing one specific thing—how do I call *this* endpoint?: what does *that* error mean?—and they need to find it and leave. ([Chapter 26](../chapter-26-technical-tutorials/index.md) names this precisely with the Diátaxis framework: a reference is information-oriented and consulted, not learning-oriented and read. A README's quick-start is a little tutorial; the API docs are reference. Mixing the two—burying the one fact a developer needs inside a narrative tutorial—fails both readers.) So API docs are organized for lookup, and each documented unit must be *complete on its own*, because the reader landed on it directly and won't read the rest.
For a single endpoint or function, complete means five things: **what it does, what it takes (request), what it returns (response), how it fails (errors), and an example you can run.** Miss any one and the reader has to go read the source. Here's the failure, then the fix.
> **❌ Before (an under-documented endpoint — the kind that generates support tickets):**
```markdown
### POST /users
Creates a new user. Pass the user's details in the body. Returns the created
user. May return an error if the request is invalid.
That tells the reader almost nothing they can act on. Which details, in what format, which are required? What exactly comes back? What does "invalid" mean—what status code, what error shape, how do they handle it? A developer reading this still has to experiment or read the source. Now the same endpoint, documented completely:
✅ After (a complete endpoint reference):
### POST /users
Creates a new user account.
**Request**
| Field | Type | Required | Description |
|----------|--------|----------|--------------------------------------|
| email | string | yes | Must be a valid, unused email address |
| name | string | yes | Display name, 1–100 characters |
| role | string | no | One of `member`, `admin`. Default: `member` |
```json
POST /users
Content-Type: application/json
{
"email": "raj@example.com",
"name": "Raj Patel",
"role": "admin"
}
Response — 201 Created
{
"id": "usr_8f2k9d",
"email": "raj@example.com",
"name": "Raj Patel",
"role": "admin",
"created_at": "2024-06-04T10:30:00Z"
}
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_email |
The email is malformed |
| 409 | email_taken |
A user with this email already exists |
| 422 | validation_error |
A field failed validation (see details) |
Example
curl -X POST https://api.example.com/users \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"email": "raj@example.com", "name": "Raj Patel"}'
**Why it's better:** The "before" gestures at the endpoint; the "after" is a *contract* a developer can build against without ever opening the source. The request table names every field, its type, whether it's required, and its constraints—so the reader knows exactly what to send. The response shows the actual JSON they'll get back, with a real status code (`201 Created`, not just "returns the user"). The errors table is the part most documentation omits and developers need most: it names each failure by status *and* a stable error code (`email_taken`), with a plain-language meaning, so the integrator can handle each case in code instead of discovering it in production. And the runnable `curl` example lets them try the call in ten seconds. Every question the reader could have is answered on this one screen—which is the standard for reference documentation, because the reader landed here and won't go looking elsewhere.
Principles that make API reference docs trustworthy:
- **Document the errors, not just the happy path.** The single most common—and most consequential—gap in API docs is the missing error reference. Developers spend more time handling failures than successes; an endpoint that documents only the 200 response forces them to trigger every error in testing to learn the shapes. List the status codes, stable error codes, and what each means. (Stable, named error codes like `email_taken` matter: a developer can branch on `email_taken` reliably; they can't branch on a human-readable message that you might reword next release.)
- **Show real request and response bodies.** Not a schema in prose ("the response contains an id and a timestamp") but the *actual* JSON, with realistic values. The reader pattern-matches their code against your example.
- **Every example must be runnable and correct.** A `curl` or code snippet the reader copies and runs. If it's wrong—a stale field, a missing header—you've taught them a falsehood and earned a bug report. (This is the README quick-start rule again: examples drift; test them.)
- **State the auth and the base URL once, prominently.** Most endpoints share the same authentication and host; document them at the top so every endpoint reference doesn't repeat them, but make sure a reader who lands on one endpoint can still find them (a link, or a short header in each example).
- **Use a consistent shape for every endpoint.** Same order every time—description, request, response, errors, example—so a reader who's read one endpoint's docs can navigate any other instantly (the document-level parallelism of [Chapter 4](../../part-01-writing-is-thinking/chapter-04-structure/index.md)). Many teams generate this structure from a machine-readable spec (OpenAPI/Swagger), which keeps the docs in lockstep with the code and gives readers an interactive "try it" console—worth adopting for any non-trivial API.
> 🔄 **Check Your Understanding**
> A REST API's documentation thoroughly describes every endpoint's purpose, request body, and successful response—but says nothing about what happens when a request fails. Why is this the most painful possible gap for the developer integrating against the API, and what specifically should be added?
> <details><summary>Answer</summary>Because real integration code spends most of its logic handling *failure*, not success, and the failures are exactly what the developer can't guess. The happy path is often obvious from the request/response; the error behavior is invisible from outside the system. Without documented errors, the integrator must deliberately trigger every failure mode in testing (send a bad email, a duplicate, a missing field) just to learn what status codes and bodies come back—and they'll inevitably miss some, which then surface as unhandled errors in production. What's missing: a table of the failure responses—the **HTTP status code**, a **stable error code** they can branch on in code (e.g., `email_taken`, not a free-text message that might change), and a **plain-language meaning** for each—plus the shape of the error body. Documenting errors is what lets a developer write robust code instead of discovering your failure modes the hard way.</details>
---
## 25.5 Architecture Decision Records: Documenting the Why
Code shows you *what* the system does. Comments and docstrings ([Chapter 24](../chapter-24-code-documentation/index.md)) explain *why* a particular piece works the way it does. But there's a level above the line of code where the most expensive knowledge lives and the worst documentation gaps occur: *why is the system shaped this way at all?* Why a message queue instead of direct calls? Why this database and not that one? Why did we *not* use the obvious library everyone reaches for? Six months later, nobody remembers—the people who made the decision have moved teams or forgotten the alternatives they rejected—and a new engineer, seeing only the *result*, assumes it was arbitrary or wrong and "fixes" it, re-introducing the exact problem the original decision avoided. The **Architecture Decision Record (ADR)** exists to prevent that. It is a short document that captures *one* significant decision: what was decided, the context that forced the decision, the options considered, and the consequences accepted. It is [Chapter 24](../chapter-24-code-documentation/index.md)'s "comment the why" promoted from a line of code to a design choice—and it is one of the highest-leverage documents a software team writes, because it preserves the reasoning that the code itself can never show.
The format, popularized by Michael Nygard, is deliberately tiny—a page or less—so that writing one is cheap enough to actually do. The core sections are **Context, Decision, and Consequences** (often with a Status). Here's the failure mode—the decision that lives only in someone's head—and then the ADR that captures it.
> **❌ Before (the decision, undocumented — reconstructed from a Slack thread that scrolled away):**
> Raj's team switched `chronoparse`'s grammar engine from a regular-expression approach to a parser-combinator approach. There's no record of why. A new contributor, six months later, opens a pull request titled "Simplify: replace parser combinators with regexes" — proposing to undo the change, because to them the combinators look like needless complexity. They have no way to know the team *started* with regexes and abandoned them for a concrete reason. The reasoning has to be reconstructed from memory by whoever happens to still be around—if anyone is.
> **✅ After — an ADR that captures the decision:**
```markdown
# ADR 0007: Use parser combinators for the grammar engine
**Status:** Accepted (2024-03-12)
## Context
chronoparse's original grammar engine matched date formats with a large set of
regular expressions. As we added locales and colloquial formats, the regex set
grew past 80 patterns. Three problems emerged:
- **Ambiguity was unmanageable.** Overlapping patterns ("the 5th" vs. "5th of
March") matched in order-dependent, hard-to-predict ways. Fixing one format
silently broke another.
- **Errors were opaque.** When parsing failed, we could not tell the user *why*
or *where*—a regex either matches or doesn't.
- **Extensibility was effectively impossible.** Users could not add a custom
format without understanding the entire fragile regex ordering.
## Decision
We will replace the regex engine with a **parser-combinator** approach: small,
composable parsers for each temporal unit (day, month, relative offset) that
combine into the full grammar.
## Consequences
**Positive:**
- Grammar rules compose predictably; adding a format no longer breaks others.
- Parse failures can report the position and expected token.
- Users can register custom unit parsers (enables the `extras` extensibility).
**Negative:**
- Higher up-front complexity; contributors must learn the combinator model
(mitigated by CONTRIBUTING.md and the `grammar/` README).
- ~15% slower on the simplest ISO-string case than the old fast-path regex
(mitigated by keeping an ISO fast-path before the combinator engine).
**Superseded ADRs:** none. This replaces the approach assumed in ADR 0002.
Why it's better: The "before" is the default state of most software: a significant decision that exists only as a fact in the code, with its reasoning evaporated. The predictable result is the well-meaning pull request that tries to undo it, costing everyone time and risking the reintroduction of solved problems. The ADR makes the reasoning durable. Now the new contributor proposing "let's go back to regexes" reads ADR 0007 first and sees the three concrete problems regexes caused—ambiguity, opaque errors, no extensibility—and either drops the idea or makes a genuinely better-informed argument. The ADR doesn't just record the decision; it records the context (what forced it) and the consequences (including the costs the team knowingly accepted—the 15% slowdown, the added complexity), which is exactly the information a future maintainer needs to decide whether the decision still holds as circumstances change. An ADR is honest about trade-offs: a decision with no documented downsides is a decision nobody thought hard about.
🔍 Why Does This Work? Why does writing down the rejected alternatives and the costs of a decision matter as much as recording the decision itself? Wouldn't it be enough to document what the team chose? Think before reading on.
Because the value of an ADR is realized in the future, when someone reconsiders the decision—and to reconsider it intelligently, they need to know what the decision was weighed against and what it cost, not just what won. If the ADR says only "we use parser combinators," a future engineer who finds combinators annoying has no defense against re-litigating it; they don't know that regexes were tried and failed for specific reasons, so they'll likely repeat the experiment. Recording the rejected alternative (regexes) and why it lost (ambiguity, opaque errors, no extensibility) inoculates the codebase against that wasted loop. Recording the costs the team accepted (the 15% slowdown, the learning curve) does two things: it proves the decision was made with open eyes, which builds trust; and it tells a future reader the condition under which to revisit—if that 15% ever becomes critical, they know exactly which trade-off to reopen. A decision without its context and consequences is just an assertion; a decision with them is reasoning a successor can actually use. This is "comment the why" (Chapter 24) at the scale of architecture: the what is visible in the code; the why and the why-not are what only the writer can preserve.
Practical notes on ADRs:
- One decision per record, numbered, immutable. ADRs are append-only history. You don't edit ADR 0007 when you change your mind; you write ADR 0014 that supersedes it (and mark 0007's status "Superseded by 0014"). The record is a log of how the team's thinking evolved, which is itself valuable.
- Write it when the decision is made, not later. Like onboarding docs (Chapter 22), an ADR is easy to write the day you decide and nearly impossible to reconstruct a year on. The context is fresh; the alternatives are still in your head; the costs are still being argued. Capture them then.
- Keep them in the repo, near the code. Conventionally in a
docs/adr/directory, versioned with the code, so the reasoning travels with the thing it explains and shows up in code review. - Short is the point. An ADR is not a design doc or a thesis. A page. If it's long, it's probably several decisions that should be separate ADRs. The brevity is what makes the habit sustainable—and a sustainable habit is the only kind that preserves institutional memory.
25.6 Changelogs: Telling Users What Changed
Software changes, and the people depending on it need to know what changed before they upgrade—especially what might break. The changelog is that record: a human-readable, chronological list of notable changes in each released version. Not a git commit log (that's for developers of the project, and it's noise to users of it), but a curated summary written for the person deciding whether and how to upgrade. The audience distinction is the whole skill: a commit log answers "what did each developer do?"; a changelog answers "what do I, a user, need to know to upgrade safely?"
❌ Before (a raw commit log dumped as a changelog):
## v2.1.0
- fix tests
- wip
- address PR comments
- bump version
- refactor parser internals
- fix the thing from yesterday
- update deps
That's the commit history, and it's useless to a user: "fix the thing from yesterday" and "wip" mean nothing to someone deciding whether to upgrade, and the entries that would matter (did anything break? what's new?) are buried among internal noise. Now a changelog written for the user:
✅ After (a changelog entry written for the upgrading user):
## [2.1.0] — 2024-06-04
### Added
- Time-zone-aware parsing: `parse()` now accepts a `tz` argument (#142)
- Support for fiscal-quarter formats ("Q3 2024") (#138)
### Changed
- `parse()` is ~30% faster on relative-date inputs (#145)
### Fixed
- "next <weekday>" now correctly skips the current week when today is that
weekday (previously returned today) (#150)
### Deprecated
- `parse_strict()` is deprecated; pass `strict=True` to `parse()` instead.
`parse_strict()` will be removed in 3.0.
### Breaking
- The `reference` argument now requires ISO format; passing a Unix timestamp
raises a `TypeError`. Migration: wrap timestamps with
`datetime.fromtimestamp()`.
Why it's better: The "before" exposes the project's internal development noise to people who don't care about it; the "after" is curated for the reader's decision. It's grouped by the kind of change the user cares about—Added, Changed, Fixed, Deprecated, Breaking—so a cautious upgrader can jump straight to "Breaking" and "Deprecated," the two sections that can ruin their day, and decide in seconds whether this upgrade needs care. Each entry is written in user terms ("parse() now accepts a tz argument"), not developer terms ("refactor parser internals"), and the breaking change includes a migration path ("wrap timestamps with datetime.fromtimestamp()")—the single most valuable line in any changelog, because it turns "this broke my code" into "here's the one-line fix." The #142 references link to detail for anyone who wants it.
The conventions here are close to a community standard called Keep a Changelog, paired with semantic versioning (the MAJOR.MINOR.PATCH scheme, where a breaking change bumps MAJOR, a backward-compatible feature bumps MINOR, and a fix bumps PATCH). Together they let a user predict the risk of an upgrade from the version number alone—a jump from 2.1.0 to 2.2.0 is safe new features; a jump to 3.0.0 means "read the breaking changes before you upgrade." The changelog explains what the version number promises.
Three rules keep a changelog useful:
- Write for the user, curating ruthlessly. Omit the internal churn (refactors users can't see, test fixes, dependency bumps that don't affect behavior). Include what changes their experience: new capabilities, fixed bugs they might have hit, and anything that breaks.
- Lead with breaking changes and deprecations. These are the entries that cost the reader time if missed. Make them impossible to overlook—their own clearly-labeled section.
- Give migration paths for breaking changes. "X was removed" abandons the reader; "X was removed; use Y instead" rescues them. Every breaking change should tell the reader how to adapt.
25.7 Contributing Guides and the Open-Source Documentation Standard
A README invites people to use your project; a contributing guide (CONTRIBUTING.md) invites them to improve it—and turns the friction of a first contribution from "give up" into "merged." Most would-be contributors are willing but uncertain: they've found a bug or want a feature, but they don't know how to set up your project locally, how to run your tests, what your code style is, or how you want changes proposed. Every one of those uncertainties is a place they quietly abandon the contribution. A contributing guide removes them, one by one. It is instructions (Chapter 22) aimed at a specific doer: a developer who wants to help but needs to know your process.
❌ Before (no guide, or a one-liner in the README): "Contributions welcome! Feel free to open a PR."
That's an open door with no map. The willing contributor still doesn't know: How do I get this running locally? How do I run the tests so I don't submit something broken? What style do you use? Do you want an issue first? What does a good PR look like to you? Each unanswered question is friction, and friction loses contributors.
✅ After — a
CONTRIBUTING.mdthat actually onboards a contributor:
# Contributing to chronoparse
Thanks for helping! Here's how to get set up and submit a change.
## Setup
```bash
git clone https://github.com/example/chronoparse
cd chronoparse
pip install -e ".[dev]"
Running tests
pytest # full suite
pytest tests/test_parser.py # one file
All tests must pass before you open a PR. CI runs the same suite.
Code style
We use black (formatting) and ruff (linting). Run make lint before
committing; the pre-commit hook does this automatically.
Submitting a change
- For anything beyond a small fix, open an issue first to discuss the approach—it saves you wasted effort if the change doesn't fit the roadmap.
- Branch from
main, make your change, add a test. - Update CHANGELOG.md under "Unreleased."
- Open a PR describing what changed and why (see the PR template).
Where things live
chronoparse/— the librarygrammar/— the parser-combinator grammar (start here; see grammar/README.md)tests/— pytest suitedocs/adr/— architecture decisions (read before proposing structural changes) ```
Why it's better: The "before" welcomes contributors without equipping them; the "after" walks a willing stranger through the entire path from "I want to help" to "my PR is open," answering each question at the moment it arises—exactly the performance-order discipline of Chapter 22. Setup is a copy-pasteable command. Running tests is shown, with the rule ("all tests must pass") stated. The style tools are named, with the one command that applies them. The submission process is numbered steps, including the easily-missed but contributor-saving advice to open an issue first for anything substantial. And "where things live" gives a newcomer a map of the codebase—including a pointer to the ADRs, so they read the recorded reasoning before proposing to undo it. A contributor who follows this guide submits a clean, testable, well-scoped PR; a contributor left to guess submits something that needs three rounds of review or, more often, gives up. The guide is the difference.
🪞 Learning Check-In Think about the last time you wanted to use—or contribute to—an open-source project and gave up partway. What stopped you? Be specific. Was it a README with no install command? A quick-start example that errored? An API with undocumented errors you had to reverse-engineer? No contributing guide, so you didn't know how to even run the tests? Almost everyone has at least one of these stories, and the point is this: you have been the eleven people filing "how do I install this?" on Raj's repo. Now flip it. The next time you write a README or document an API, you're writing for a person exactly like you-when-you-gave-up. The empathy you feel for your past frustrated self is the most reliable guide you have to what your documentation needs. What's one project of yours whose front door you'd be embarrassed to watch a stranger walk up to?
These five document types—README, API reference, ADRs, changelog, contributing guide—together constitute what's loosely called the open-source documentation standard: the set of documents a healthy, usable open project is expected to have. The standard isn't a rule anyone enforces; it's an expectation experienced developers carry, and a project that meets it signals "this is maintained, usable, and welcoming," while one that doesn't signals "use at your own risk." Meeting the standard is also, increasingly, what tooling expects: code-hosting platforms surface your README, link your LICENSE, prompt for a CONTRIBUTING.md, and display your changelog on releases. The standard exists because, across millions of projects, these are the documents that reliably determine whether other people can use and improve your work—which is the entire point of making it public.
📐 Project Checkpoint: User Documentation, the Developer Edition (refining Piece 3)
In Chapter 22 you drafted Portfolio Piece 3—user documentation/instructions—and tested it on a beginner. This chapter gives you the chance to either refine that piece for a developer audience or add a developer-facing companion to it, depending on your field.
Recall the prior increment. Your Piece 3 from Chapter 22 was a procedure a stranger could follow—numbered steps, prerequisites, warnings, troubleshooting, tested on a real beginner. It was written for a doer performing a task.
This chapter's addition — a README (and, if relevant, an API reference or ADR). If your work involves code, a tool, a script, a dataset, or any project others might use, write a README for it using this chapter's structure: a one-line description, a feature list, an installation section with the exact command, a quick-start with at least one runnable example that you have actually run, and short Contributing and License sections. The non-negotiable standard, carried over from Chapter 22: do the clean-machine test. Clone or copy your project somewhere fresh, follow your own README's install and quick-start with literal, stupid obedience—doing exactly and only what it says—and fix every place you have to improvise. The gaps you find are the eleven "how do I install this?" issues you're preventing.
If your project exposes an interface—an API, a set of public functions, a CLI—document one endpoint or function completely (purpose, inputs, outputs, errors, runnable example) to the §25.4 standard. And if your project embodies a non-obvious design choice, write one ADR capturing the why (context, decision, consequences). These become part of your portfolio's user-documentation piece, demonstrating you can write for developers, not just end users.
Preview the next increment. Chapter 26 (Technical Tutorials) introduces the Diátaxis framework, which will sharpen the distinction this chapter relied on—why your README's quick-start (a tiny tutorial) and your API docs (a reference) are fundamentally different kinds of writing that fail when mixed. You'll take the teaching instinct behind a good quick-start and build a full tutorial on it.
25.8 Common Mistakes & Practical Considerations
The failures below recur across READMEs, API docs, and the rest of the developer-facing set. Most share a root cause: writing for yourself (who already knows the project) instead of the stranger who just arrived—the curse of knowledge (Chapter 2) in its software habitat.
No install command, no runnable example. The two most common and most fatal README gaps—exactly Raj's original failure. A README that doesn't show how to install and run the thing forces the reader to guess or leave, and they leave. If you fix only one thing in any README, make it these two.
Philosophy before practice. Leading with motivation, background, and design philosophy—the wall of text—before the reader can do anything. Interesting to you; a barrier to them. Lead with the quick-start; move the philosophy down or out.
Documenting only the happy path (API docs). Describing the successful request and response but not the errors—the gap that hurts integrators most, because failure-handling is where they spend their time. Document status codes, stable error codes, and meanings.
Examples that don't run. A quick-start or curl snippet that errors—a stale field name, a renamed function, a missing header. It destroys trust instantly and teaches the reader a falsehood. Run your examples on a clean setup; test them in CI if you can.
The decision that lives only in someone's head. A significant architectural choice with no ADR, so its reasoning evaporates and a future contributor "fixes" it by reintroducing the problem it solved. Write the ADR the day you decide.
A commit log masquerading as a changelog. Dumping raw commits ("wip," "fix tests," "address PR comments") on users who needed a curated, user-facing summary. Curate; group by Added/Changed/Fixed/Breaking; lead with what breaks.
Breaking changes with no migration path. "X was removed" with no "use Y instead." It abandons the reader at the exact moment they need help. Every breaking change gets a migration line.
"Contributions welcome!" with no guide. An open door with no map—the willing contributor still can't run your tests or learn your style, so they give up. A CONTRIBUTING.md with setup, tests, style, and process converts willingness into merged PRs.
Docs that drift out of sync with the code. The deepest, slowest failure: documentation that was right once and is now subtly wrong, which is worse than no docs because the reader trusts it. Defenses: keep docs near the code (same repo, same PR), test examples automatically where possible, generate reference docs from the source (docstrings, OpenAPI) so they can't drift, and treat "update the docs" as part of "done" for any change—the same discipline as updating the changelog in the contributing guide above.
The honest "it depends": how much documentation? A throwaway script you'll delete next week needs a one-line comment, not an ADR. A widely-used public library needs the full set. The calibration is the same audience-and-stakes judgment as everywhere in this book: who will read this, and what does it cost them if it's missing? A library with a thousand users earns a polished README, an exhaustive API reference, ADRs, a changelog, and a contributing guide, because each missing piece multiplies across a thousand frustrated developers. An internal tool used by your five-person team earns a solid quick-start and a "who owns this" line. Match the documentation investment to the size and stakes of the audience—but never skip the quick-start, because every project, internal or public, has a future reader (often you) who needs to get it running.
📚 Going Deeper: Diátaxis and the four kinds of documentation This chapter has quietly leaned on a distinction that Chapter 26 makes explicit: a README's quick-start, an API reference, a tutorial, and an architecture explanation are different kinds of documentation serving different reader needs, and they fail when you blend them. The Diátaxis framework (by Daniele Procida, a Tier 1 reference in the docs community) names four: tutorials (learning-oriented—hold the newcomer's hand to a guaranteed first success; a README quick-start is a mini one), how-to guides (task-oriented—solve one specific problem for someone who already has competence), reference (information-oriented—the complete, consultable spec, like your API endpoint docs), and explanation (understanding-oriented—the why and the design context, like an ADR or that philosophy section Raj wisely moved out of his doorway). The practical payoff for developer docs: keep your reference (API docs) free of tutorial narrative so a developer can look up one fact fast, and keep your quick-start free of exhaustive reference detail so a newcomer isn't drowned. When a piece of documentation feels muddled, it's often two Diátaxis types fighting in one document. Chapter 26 develops this fully.
Frequently Asked Questions
How do I write a good README file?
Structure it around the questions a visitor actually arrives with, in this order: a one-line description (what is this?), a short feature list (does it solve my problem?), an installation section with the exact command, and—most important—a quick-start with a runnable example you have actually run (show me it working). Then short Documentation, Contributing, and License sections. Lead with the quick-start, not your design philosophy. The test: can a stranger go from finding your repo to a working result in under five minutes without asking you anything? Run your own install and quick-start on a clean machine to find out.
How do I write API documentation?
Document each endpoint or function completely enough that a developer never has to read your source: what it does, the request (every field, its type, whether it's required), the response (the actual JSON, with the real status code), the errors (status codes, stable error codes, and what each means), and a runnable example (a curl command or code snippet). The most-skipped and most-needed part is the error documentation—developers spend most of their effort handling failures, so document the failure responses, not just the happy path. Use the same shape for every endpoint so readers can navigate by pattern.
What is an Architecture Decision Record (ADR)?
An ADR is a short document (a page or less) that captures one significant design decision and, crucially, why it was made: the Context (what problem or constraint forced the decision), the Decision itself, and the Consequences (the trade-offs accepted, including the downsides). The format was popularized by Michael Nygard. ADRs are numbered, append-only (you supersede an old one with a new one rather than editing it), and kept in the repo near the code. They exist so a future maintainer understands why the system is shaped the way it is, instead of assuming a deliberate choice was arbitrary and undoing it.
How do I write a changelog?
Write it for the user deciding whether to upgrade, not as a dump of git commits. Group changes by what the user cares about—Added, Changed, Fixed, Deprecated, Breaking—and lead with Breaking and Deprecated, the entries that cost the reader time if missed. Write each entry in user terms ("parse() now accepts a tz argument"), not developer terms ("refactored internals"), and give every breaking change a migration path ("X was removed; use Y instead"). The Keep a Changelog convention plus semantic versioning (MAJOR.MINOR.PATCH) is the common standard.
What's the difference between a README and API documentation?
A README is the project's front door—oriented to a newcomer deciding whether to use the project and getting them to a first success (orientation + a quick-start, read top-to-bottom once). API documentation is reference—consulted, not read, by a developer who already chose your tool and needs one specific fact (how do I call this endpoint? what does this error mean?). The README is a little tutorial; the API docs are a lookup table. They fail when blended: don't bury the one reference fact a developer needs inside a narrative, and don't drown a newcomer's quick-start in exhaustive reference detail. Chapter 26's Diátaxis framework explains why these are genuinely different kinds of writing.
Chapter Summary
Key Takeaways
- Documentation is the product's user interface for developers. For everyone who isn't you, the docs are the usable surface of your work; if a stranger can't get started, the code doesn't exist to them.
- The README is the front door. Structure it around the visitor's questions—what is this, install, use, contribute—and put the quick-start with a runnable example front and center. Time-to-first-success is the metric.
- A runnable example beats prose. Show the code working (call and result), minimal and copy-pasteable, and make sure it actually runs—test it on a clean setup.
- API docs are a contract. Document each endpoint completely—purpose, request, response, errors, runnable example—so a developer never reads your source. Document the errors, the part everyone skips and integrators need most.
- ADRs capture the why. A short record of one decision—context, decision, consequences (including costs)—so a future maintainer doesn't undo a deliberate choice. "Comment the why" (Chapter 24) at the scale of architecture.
- Changelogs are for the upgrading user, not a commit dump—grouped by Added/Changed/Fixed/Breaking, leading with breaking changes, each with a migration path.
- Contributing guides turn willingness into merged PRs—setup, tests, style, process. The five together are the open-source documentation standard.
Action Items
- Add an install command and one runnable quick-start example to a README that lacks them, then run them on a clean machine.
- Take one API endpoint you own and document its errors, not just its success response.
- Write one ADR for a design decision on your team that currently lives only in someone's head.
- Convert a raw commit-log "changelog" into a user-facing one grouped by change type, with migration paths for anything breaking.
Common Mistakes
- No install command or runnable example; philosophy before practice; documenting only the happy path; examples that don't run; decisions with no ADR; a commit log dumped as a changelog; breaking changes with no migration path; "contributions welcome" with no guide; docs that have drifted out of sync with the code.
Decision Framework
| Question | If yes → | If no → |
|---|---|---|
| Does the README show the exact install command and a runnable example? | Good | Add them first—nothing matters more |
| Has a stranger (or you, on a clean machine) gotten it working from the README alone? | Good | Run the clean-machine test |
| Does each API endpoint doc cover request, response, and errors? | Good | Add the error table |
| Can a developer use the API without reading the source? | Good | Find the gap they'd have to read code for |
| Is every significant design decision's why recorded (ADR or comment)? | Good | Write the ADR while it's fresh |
| Is the changelog written for users, with migration paths for breaking changes? | Good | Curate it; add migrations |
| Could a willing stranger contribute without asking how to run your tests? | Good | Write CONTRIBUTING.md |
Spaced Review
A few questions reaching back, to strengthen retention.
- (From Chapter 22) Chapter 22's central test was "watch someone who has never done this task try to follow your instructions." How does that exact test reappear in this chapter as the standard for a README, and why is a "clean machine" the software version of "a real beginner"?
- (From Chapter 4) Chapter 4 taught informative headers that name content, not form ("Recommendation: Ship the Patch," not "Discussion"). Where do informative headers do their work in a README, and how do they serve the scanning developer specifically?
- (From Chapter 24, bridging) Chapter 24's threshold concept was "comment the why, not the what." An Architecture Decision Record is that idea scaled up. Explain the parallel: what is the "what" that the code already shows, and what is the "why" that only the ADR can preserve?
Answers
1. It reappears as the **clean-machine test**: clone or copy your project somewhere fresh and follow your own README's install and quick-start with literal, stupid obedience, fixing every place you have to improvise. A clean machine is the software "beginner" because, like a human first-timer, it has *none of your assumed context*—not your installed dependencies, your environment variables, your global config, your muscle memory. Your own machine is contaminated with everything you set up months ago and forgot, exactly the way your *mind* is contaminated with the task knowledge that hides assumed steps ([Chapter 22](../../part-04-professional-workplace-writing/chapter-22-instructions-procedures/index.md)'s curse of knowledge). The clean machine stalls precisely where your README assumes something it never stated—the missing install step, the undocumented dependency—just as a beginner stalls where your instructions assume knowledge they lack. Same test, same reason: you can't judge your own front door from inside the house. 2. Informative headers are the README's navigation: **Features, Installation, Quick start, Contributing, License** each name a *question the reader has*, so a developer scanning the page ([Chapter 4](../../part-01-writing-is-thinking/chapter-04-structure/index.md): they scan, they don't read) jumps straight to the one they need—"I just want the install command" → eye goes to **Installation**—without reading the rest. A header like "Background" or "Overview" (form, not content) makes the scanner hunt; "Quick start" tells them exactly what's under it. For a developer, who is the most impatient kind of scanner, headers that map to their questions are the difference between finding the command in two seconds and giving up. The README's structure *is* the reader's question list, in order. 3. The **what** is fully visible in the code: the system *uses* parser combinators—you can read the combinator code and see exactly what it does. The code cannot, however, show the **why**: that the team started with regexes, that regexes failed for three specific reasons (ambiguity, opaque errors, no extensibility), that combinators were chosen *despite* a 15% performance cost and a steeper learning curve. None of that reasoning is recoverable from the implementation; the code shows the destination but not the road or the roads rejected. The ADR preserves exactly what the code can't—the context that forced the decision and the trade-offs accepted—so a future maintainer doesn't mistake a hard-won choice for an arbitrary one and undo it. That's "comment the why, not the what" ([Chapter 24](../chapter-24-code-documentation/index.md)): the *what* is self-evident from the artifact; the *why* exists only if the writer records it.What's Next
Chapter 26 (Technical Tutorials and How-To Content) takes the teaching instinct behind a good quick-start and builds it into a full skill. It introduces the Diátaxis framework—the distinction this chapter kept leaning on—which explains why a tutorial, a how-to guide, a reference, and an explanation are four genuinely different kinds of documentation that fail when you mix them. You'll learn why a tutorial guarantees a beginner's success (the README quick-start was a tiny one), how a how-to differs from a tutorial, and how the curse of knowledge sabotages tutorials in a particularly sneaky way—by making you silently skip the step you do automatically, the same way it stripped the install command out of Raj's original README. The doer you wrote for here—the stranger at your front door—is about to become the learner you teach, hand on the back, all the way to their first working result.
Practice: Exercises · Quiz Go deeper: Case Study · Case Study 2 Review: Key Takeaways · Further Reading