文档与 ADR
概览
记录的不只是代码,更要记录决策。最有价值的文档,保存的是 why,也就是做出这个决定时的背景、约束和权衡。代码展示了 what 被构建出来,文档解释了 why it was built this way,以及 what alternatives were considered。这些背景信息,对未来继续维护代码库的人类和 agent 都至关重要。
何时使用
- 做重要的架构决策
- 在多个方案之间做选择
- 新增或修改公开 API
- 发布会改变用户可见行为的功能
- 帮助新成员或新 agent 理解项目
- 当你发现自己在重复解释同一件事时
不适用的场景: 不要为显而易见的代码写文档;不要写只是复述代码内容的注释;也不要给一次性原型写厚重文档。
架构决策记录(ADR)
ADR 用于记录重要技术决策背后的推理过程。它们是你能写出的最高价值文档之一。
什么时候该写 ADR
- 选择框架、库或关键依赖
- 设计数据模型或数据库 schema
- 确定认证策略
- 决定 API 架构,例如 REST、GraphQL 或 tRPC
- 在构建工具、托管平台或基础设施之间做选择
- 任何将来回滚代价很高的决策
ADR 模板
将 ADR 按顺序编号,存放在 docs/decisions/ 中:
# ADR-001: Use PostgreSQL for primary database
## Status
Accepted | Superseded by ADR-XXX | Deprecated
## Date
2025-01-15
## Context
We need a primary database for the task management application. Key requirements:
- Relational data model (users, tasks, teams with relationships)
- ACID transactions for task state changes
- Support for full-text search on task content
- Managed hosting available (for small team, limited ops capacity)
## Decision
Use PostgreSQL with Prisma ORM.
## Alternatives Considered
### MongoDB
- Pros: Flexible schema, easy to start with
- Cons: Our data is inherently relational; would need to manage relationships manually
- Rejected: Relational data in a document store leads to complex joins or data duplication
### SQLite
- Pros: Zero configuration, embedded, fast for reads
- Cons: Limited concurrent write support, no managed hosting for production
- Rejected: Not suitable for multi-user web application in production
### MySQL
- Pros: Mature, widely supported
- Cons: PostgreSQL has better JSON support, full-text search, and ecosystem tooling
- Rejected: PostgreSQL is the better fit for our feature requirements
## Consequences
- Prisma provides type-safe database access and migration management
- We can use PostgreSQL's full-text search instead of adding Elasticsearch
- Team needs PostgreSQL knowledge (standard skill, low risk)
- Hosting on managed service (Supabase, Neon, or RDS)
ADR 生命周期
PROPOSED → ACCEPTED → (SUPERSEDED or DEPRECATED)
- 不要删除旧 ADR。 它们保存了历史上下文。
- 如果决策发生变化,写新的 ADR,并引用与替代旧 ADR。
内联文档
什么时候该写注释
注释要解释 why,而不是 what:
// BAD: Restates the code
// Increment counter by 1
counter += 1;
// GOOD: Explains non-obvious intent
// Rate limit uses a sliding window — reset counter at window boundary,
// not on a fixed schedule, to prevent burst attacks at window edges
if (now - windowStart > WINDOW_SIZE_MS) {
counter = 0;
windowStart = now;
}
什么时候不要写注释
// 不要给一看就懂的代码加注释
function calculateTotal(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
// 不要留下其实现在就该做掉的 TODO
// TODO: add error handling ← 直接补上
// 不要保留被注释掉的旧代码
// const oldImplementation = () => { ... } ← 删掉,git 有历史
记录已知陷阱
/**
* IMPORTANT: This function must be called before the first render.
* If called after hydration, it causes a flash of unstyled content
* because the theme context isn't available during SSR.
*
* See ADR-003 for the full design rationale.
*/
export function initializeTheme(theme: Theme): void {
// ...
}
API 文档
对于公开 API,例如 REST、GraphQL 或库接口:
和类型写在一起(TypeScript 首选)
/**
* Creates a new task.
*
* @param input - Task creation data (title required, description optional)
* @returns The created task with server-generated ID and timestamps
* @throws {ValidationError} If title is empty or exceeds 200 characters
* @throws {AuthenticationError} If the user is not authenticated
*
* @example
* const task = await createTask({ title: 'Buy groceries' });
* console.log(task.id); // "task_abc123"
*/
export async function createTask(input: CreateTaskInput): Promise<Task> {
// ...
}
REST API 用 OpenAPI / Swagger
paths:
/api/tasks:
post:
summary: Create a task
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateTaskInput'
responses:
'201':
description: Task created
content:
application/json:
schema:
$ref: '#/components/schemas/Task'
'422':
description: Validation error
README 结构
每个项目都应该有一个 README,至少覆盖:
# Project Name
One-paragraph description of what this project does.
## Quick Start
1. Clone the repo
2. Install dependencies: `npm install`
3. Set up environment: `cp .env.example .env`
4. Run the dev server: `npm run dev`
## Commands
| Command | Description |
|---------|-------------|
| `npm run dev` | Start development server |
| `npm test` | Run tests |
| `npm run build` | Production build |
| `npm run lint` | Run linter |
## Architecture
Brief overview of the project structure and key design decisions.
Link to ADRs for details.
## Contributing
How to contribute, coding standards, PR process.
Changelog 维护
对于已经发布的功能:
# Changelog
## [1.2.0] - 2025-01-20
### Added
- Task sharing: users can share tasks with team members (#123)
- Email notifications for task assignments (#124)
### Fixed
- Duplicate tasks appearing when rapidly clicking create button (#125)
### Changed
- Task list now loads 50 items per page (was 20) for better UX (#126)
面向 Agent 的文档
对 AI agent 还要额外考虑以下文档:
- CLAUDE.md / rules files:记录项目约定,让 agent 按你的方式工作
- Spec files:保持 spec 最新,让 agent 构建正确的东西
- ADRs:帮助 agent 理解决策背景,避免它重复讨论旧结论
- Inline gotchas:防止 agent 掉进已知坑里
常见自我安慰
| 自我安慰 | 现实 |
|---|---|
| “代码本身就是文档” | 代码只能说明做了什么,不能说明为什么做、拒绝了什么方案、受哪些约束限制。 |
| “等 API 稳定了再写文档” | API 往往会因为文档而更快稳定下来。文档本身就是设计的第一轮测试。 |
| “没人看文档” | Agent 会看。未来的工程师会看。三个月后的你自己也会看。 |
| “ADR 是额外负担” | 一份 10 分钟的 ADR,能省掉 6 个月后一次 2 小时的重复争论。 |
| “注释会过时” | 写 why 类型注释通常很稳定;写 what 类型注释才容易过时,所以只写前者。 |
危险信号
- 架构决策没有任何书面理由
- 公开 API 没有文档或类型说明
- README 没解释项目怎么跑起来
- 用注释掉的旧代码代替删除
- TODO 注释挂了几周都没处理
- 一个有明显架构选择的项目却没有 ADR
- 文档只是复述代码,而不是解释意图
验证
写完文档后,确认:
- 所有重要架构决策都有 ADR
- README 覆盖 quick start、commands 和 architecture overview
- API 函数有参数与返回值说明
- 重要 gotcha 已在真正相关的位置用内联文档记录
- 没有残留被注释掉的旧代码
- 规则文件(CLAUDE.md 等)内容是最新且准确的