Most Documentation Is Dead on Arrival
Every engineering team I've worked with has a documentation problem. Either there's no documentation, or there's too much — sprawling Confluence pages last updated in 2022, README files that describe a codebase three refactors ago, onboarding guides that reference tools the team stopped using.
The fix isn't "write more documentation." It's writing the right types of documentation and accepting that some things shouldn't be documented at all.
Architecture Decision Records (ADRs)
ADRs are the single most underused documentation practice in software engineering. They capture why a technical decision was made — the context, the options considered, and the tradeoffs accepted.
Six months from now, someone (possibly you) will look at an architectural choice and wonder "why did we do it this way?" Without an ADR, the answer is "ask Sarah, except Sarah left the company." With an ADR, the answer is in the repo.
Format (keep it simple, one page max):
# ADR-007: Use PostgreSQL for the analytics database
## Status
Accepted (2026-03-15)
## Context
We need a database for our analytics pipeline that handles
~50M rows/month of event data with complex aggregation queries.
Current MySQL instance struggles with window functions and
the query optimizer makes poor choices on our workload.
## Options Considered
1. PostgreSQL - Strong analytics support, window functions,
CTEs, team has experience
2. ClickHouse - Purpose-built for analytics, columnar storage,
very fast aggregations
3. BigQuery - Managed, scales infinitely, pay-per-query
## Decision
PostgreSQL with TimescaleDB extension.
Rationale: Team already runs PostgreSQL for the main app.
TimescaleDB adds time-series optimizations without a new
operational burden. ClickHouse would be faster for pure
analytics but adds a new system to manage. BigQuery has
unpredictable costs at our query volume.
## Consequences
- Need to set up TimescaleDB replication for the analytics workload
- Continuous aggregates handle most dashboard queries
- May need to revisit if data volume exceeds 500M rows/month
Store ADRs in the repo, not in Confluence. They're part of the codebase — they should live with the code and be reviewed in PRs.
READMEs That Actually Get Read
A README should answer one question: "I just cloned this repo, now what?"
Here's what belongs in a README and what doesn't:
Include:
- One sentence describing what the project does
- How to set up the development environment (exact commands, not "install dependencies")
- How to run the application locally
- How to run tests
- Link to more detailed docs if they exist
Skip:
- Architecture overview (put this in an ADR or a separate architecture doc)
- API documentation (generate from code or use a separate spec)
- Contributing guidelines (separate CONTRIBUTING.md)
- Detailed configuration reference (separate doc or generate from code comments)
Test your README by having a new team member follow it literally. Every step they get stuck on is a gap to fill. I've seen READMEs that say "install Docker" without mentioning you need Docker Compose, or "run the migrations" without specifying which command in which directory.
# Project Name
Brief description of what this does and who uses it.
## Quick Start
```bash
# Prerequisites: Docker, Node.js 20+
git clone git@github.com:org/project.git
cd project
cp .env.example .env # Edit with your local values
docker compose up -d # Start PostgreSQL and Redis
npm install
npm run db:migrate
npm run dev # http://localhost:3000
```
## Running Tests
```bash
npm test # Unit tests
npm run test:integration # Requires Docker services running
```
## Deployment
Merges to `main` auto-deploy to staging.
Production deploys via `/deploy` Slack command.
See [Deployment Guide](docs/deployment.md) for details.
Runbooks
Runbooks are step-by-step procedures for operational tasks — especially incident response. They're written for someone who's sleep-deprived at 3 AM and needs to fix something they've never seen before.
Good runbook characteristics:
- Copy-pasteable commands — No "run the appropriate migration command." Give the exact command with the exact flags.
- Decision trees, not narratives — "If the error is X, do A. If the error is Y, do B." Not "the system might exhibit several different failure modes depending on..."
- Verification steps — After each action, tell the reader how to confirm it worked before moving to the next step.
- Escalation paths — "If this doesn't resolve it, page @oncall-senior in Slack."
# Runbook: Database Connection Pool Exhaustion
## Symptoms
- Application returns 500 errors
- Logs show "connection pool exhausted" or "too many connections"
- PgBouncer stats show waiting clients > 0
## Diagnosis
```bash
# Check active connections
psql -c "SELECT count(*), state FROM pg_stat_activity GROUP BY state;"
# Check PgBouncer pool status
psql -p 6432 -U pgbouncer pgbouncer -c "SHOW POOLS;"
```
## Fix: Quick (mitigate)
```bash
# Kill idle connections older than 5 minutes
psql -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity
WHERE state = 'idle' AND query_start < now() - interval '5 minutes';"
```
Verify: Connection count should drop. App errors should stop within 30s.
## Fix: If quick fix doesn't help
Restart PgBouncer (causes brief connection drop):
```bash
systemctl restart pgbouncer
```
Verify: `SHOW POOLS` should show reset counters.
## Escalation
If neither fix works, page database-oncall in #incidents.
What Not to Document
Don't document things that are better expressed in code. Don't write a document explaining your API responses — use OpenAPI/Swagger and generate the docs. Don't document coding conventions — encode them in linter rules. Don't document deployment steps — automate them in a CI/CD pipeline.
The best documentation is the documentation that doesn't need to exist because the system is self-explanatory. The second best is documentation that captures decisions and context you can't get from the code alone.