Nobody reads documentation. That's what we tell ourselves when our docs fail. But some docs get read obsessively. What's the difference?
Start With Why
Don't start with what. Start with why someone would care.
# Bad
UserManager is a class that manages users.
# Good
Need to create, update, or delete users? UserManager handles all user
lifecycle operations with automatic validation and audit logging.Answer the question: "Why should I keep reading?"
Show, Don't Tell
Working code beats explanations:
# Bad
Call the authenticate method with credentials to get a token.
# Good
```python
from mylib import authenticate
token = authenticate(
username="admin",
password="secret123"
)
print(token.access_token)
# => "eyJhbGciOiJIUzI1NiIs..."
The example teaches faster than description.
## The README Formula
1. **One-sentence description.** What is this?
2. **Quick start.** Get something working in 60 seconds.
3. **Installation.** Copy-pasteable commands.
4. **Basic usage.** The most common use case.
5. **API reference.** Link to detailed docs.
6. **Contributing.** How to help.
Most people stop at step 4. That's fine. Put the important stuff first.
## Write for Scanning
Developers don't read—they scan.
**Use headers liberally:**
```markdown
## Installation
## Quick Start
## Configuration
## Troubleshooting
Bold key terms:
Use **--verbose** for detailed output. Pass **--dry-run** to preview changes.Bullet points over paragraphs:
# Bad
The function accepts three parameters. The first is the user ID, which
should be a string. The second is the options object, which can contain...
# Good
Parameters:
- `user_id` (string) — The user's unique identifier
- `options` (object) — Configuration options
- `timeout` (int) — Request timeout in seconds
- `retry` (bool) — Whether to retry on failureCode Comments
Comment the why, not the what:
# Bad
# Increment counter by 1
counter += 1
# Good
# Rate limit: max 100 requests per window
counter += 1Comment tricky parts:
# OAuth spec requires state parameter to prevent CSRF
# See: https://tools.ietf.org/html/rfc6749#section-10.12
state = generate_random_string(32)Don't comment obvious code:
# Bad
# Get the user from the database
user = db.get_user(user_id)
# Good (no comment needed)
user = db.get_user(user_id)Keep It Current
Outdated docs are worse than no docs. They teach the wrong thing.
Date your docs:
*Last updated: March 2024 for version 2.3*Test your examples: Run code samples in CI. Broken examples break trust.
Delete stale content: If a feature is removed, remove its docs. Don't let old sections rot.
Structure for Discovery
Progressive disclosure:
- Quick start on the homepage
- Tutorials for common workflows
- Reference for every detail
- Guides for advanced topics
Cross-link generously:
For authentication details, see [Authentication Guide](./auth.md).Search matters: If your docs aren't searchable, they're not usable.
What Great Docs Have
- Working examples for every feature
- Copy-pasteable commands
- Consistent formatting
- Versioned alongside code
- Quick wins in the first 5 minutes
- Troubleshooting for common errors
- Visual hierarchy that guides the eye
The Test
Can someone new:
- Install your thing in under 5 minutes?
- Get a basic example working in under 10?
- Find the API reference for a specific function?
- Troubleshoot a common error?
If any answer is no, improve that part first.
Write Like You Talk
Documentation isn't a legal document. Write like you're explaining to a colleague:
# Bad
The initialization procedure must be invoked prior to utilizing
the primary functionality.
# Good
Call `init()` before using anything else. It sets up the database
connection and loads your config.Clear beats formal. Every time.