Files
2026-07-13 21:35:37 +08:00

246 lines
8.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: incremental-implementation
description: 增量式交付变更。适用于实现任何涉及多个文件的特性或改动,以及当你准备一次性编写大量代码,或觉得某任务难以一步到位时使用。
---
# 增量实现
## 概述
以薄垂直切片的方式构建 —— 实现一小块、测试、验证、再扩展。避免一次性完成整个功能的实现。每个增量都应让系统处于可工作、可测试的状态。这是让大型功能变得可控的执行纪律。
## 何时使用
- 实现任何涉及多个文件的改动
- 根据任务分解构建新功能
- 重构现有代码
- 任何时候你忍不住要在测试前写出超过约 100 行代码
**何时不使用:** 单文件、单函数的改动,且范围已经极小。
## 增量循环
```
┌──────────────────────────────────────┐
│ │
│ 实现 ──→ 测试 ──→ 验证 ──┐ │
│ ▲ │ │
│ └───── 提交 ◄─────────┘ │
│ │ │
│ ▼ │
│ 下一个切片 │
│ │
└──────────────────────────────────────┘
```
对于每个切片:
1. **实现** 最小的完整功能片段
2. **测试** —— 运行测试套件(若不存在则编写测试)
3. **验证** —— 确认切片按预期工作(测试通过、构建成功、手动检查)
4. **提交** —— 用描述性信息保存进度(参见 `git-workflow-and-versioning` 关于原子提交的指导)
5. **进入下一个切片** —— 向前推进,不要重头开始
## 切片策略
### 垂直切片(推荐)
构建一条贯穿完整技术栈的路径:
```
切片 1:创建任务(数据库 + API + 基础界面)
→ 测试通过,用户可通过界面创建任务
切片 2:列出任务(查询 + API + 界面)
→ 测试通过,用户可看到自己的任务
切片 3:编辑任务(更新 + API + 界面)
→ 测试通过,用户可修改任务
切片 4:删除任务(删除 + API + 界面 + 确认)
→ 测试通过,完整 CRUD 完成
```
每个切片交付可工作的端到端功能。
### 契约优先切片
当后端和前端需要并行开发时:
```
切片 0:定义 API 契约(类型、接口、OpenAPI 规范)
切片 1a:按契约实现后端 + API 测试
切片 1b:使用符合契约的模拟数据实现前端
切片 2:集成并进行端到端测试
```
### 风险优先切片
先解决风险最高或最不确定的部分:
```
切片 1:证明 WebSocket 连接可工作(风险最高)
切片 2:在已验证的连接上构建实时任务更新
切片 3:添加离线支持和重连
```
如果切片 1 失败,你会在投入切片 2 和 3 之前就发现它。
## 实现规则
### 规则 0:简单优先
在编写任何代码之前,先问:"最简单可行的方式是什么?"
写完代码后,对照以下检查点进行审视:
- 能否用更少的行数完成?
- 这些抽象是否值得它们带来的复杂度?
- 资深工程师看到后会不会说"你为什么不直接……"?
- 我是在为假设的未来需求构建,还是为当前任务构建?
```
简单性检查:
✗ 为一个通知设计带中间件管道的通用 EventBus
✓ 简单的函数调用
✗ 为两个相似的组件使用抽象工厂模式
✓ 两个直接的组件加上共享工具函数
✗ 为三个表单使用配置驱动的表单构建器
✓ 三个表单组件
```
三行相似的代码好过一个过早的抽象。先实现朴素、显然正确的版本。只有在通过测试证明了正确性之后才进行优化。
### 规则 0.5:范围自律
只改动任务要求的内容。
**不要:**
- "顺手清理"改动相邻的代码
- 重构你未修改文件中的导入
- 删除你并不完全理解的注释
- 添加规范之外、只是"看起来有用"的功能
- 在你只是阅读的文件中"现代化"语法
如果你注意到任务范围之外值得改进的地方,记下来 —— 不要修改:
```
注意到但未改动:
- src/utils/format.ts 有一个未使用的导入(与此任务无关)
- 认证中间件可以有更好的错误信息(单独任务)
→ 需要我为这些创建任务吗?
```
### 规则 1:一次只做一件事
每个增量只改动一个逻辑上的东西。不要混用不同关注点:
**错误:** 一个提交同时添加新组件、重构已有组件、更新构建配置。
**正确:** 三个独立的提交 —— 每个改动一个。
### 规则 2:保持可编译
每个增量之后,项目必须能构建,已有测试必须能通过。不要在切片之间让代码库处于损坏状态。
### 规则 3:不完整功能使用特性开关
如果一个功能还不能对用户开放,但你需要合并增量:
```typescript
// 进行中工作的特性开关
const ENABLE_TASK_SHARING = process.env.FEATURE_TASK_SHARING === 'true';
if (ENABLE_TASK_SHARING) {
// 新的分享界面
}
```
这让你可以将小增量合并到主分支,而不会暴露未完成的功能。
### 规则 4:安全默认值
新代码应默认采用安全、保守的行为:
```typescript
// 安全:默认关闭,主动选择启用
export function createTask(data: TaskInput, options?: { notify?: boolean }) {
const shouldNotify = options?.notify ?? false;
// ...
}
```
### 规则 5:便于回滚
每个增量应可独立回退:
- 新增性改动(新文件、新函数)易于回退
- 对已有代码的修改应尽量少且集中
- 数据库迁移应有对应的回退迁移
- 避免在同一提交中删除某物又替换它 —— 分开处理
## 与代理协作
当指导一个代理进行增量实现时:
```
"我们来实现计划中的任务 3。
先只做数据库 schema 变更和 API 端点。
暂时不要碰界面 —— 我们在下一个增量中再做。
实现后,运行 `npm test` 和 `npm run build` 来验证
一切正常。"
```
对每个增量的范围内和范围外都要明确说明。
## 增量检查清单
每个增量之后,验证:
- [ ] 改动只做了一件事,且做完整了
- [ ] 所有已有测试仍然通过(`npm test`
- [ ] 构建成功(`npm run build`
- [ ] 类型检查通过(`npx tsc --noEmit`
- [ ] 代码检查通过(`npm run lint`
- [ ] 新功能按预期工作
- [ ] 改动已用描述性信息提交
**注意:** 每次改动可能影响某个验证命令时,再运行它。某条命令成功后,如果代码自那以后没有变化,就不要重复运行 —— 在未变化的代码上重新运行不会增加任何信息量。
## 常见辩解
| 辩解 | 现实 |
|---|---|
| "我最后再一起测试" | Bug 会叠加。切片 1 的一个 bug 会让切片 2-5 都出错。每个切片都要测试。 |
| "一次性做完更快" | 它*感觉*更快,直到有东西坏了而你找不到 500 行改动中哪一行导致的。 |
| "这些改动太小了,不值得分开提交" | 小提交不费成本。大提交隐藏 bug 并让回滚变得痛苦。 |
| "我以后再添加特性开关" | 如果功能不完整,就不应该让用户看到。现在就把开关加上。 |
| "这个重构很小,可以一起做" | 重构和功能混在一起会让两者的审查和调试都更难。分开处理。 |
| "让我再跑一次构建命令确认一下" | 某条命令成功运行后,如果代码没有变化,重复运行不会增加任何信息。在后续编辑之后再运行它,而不是为了安心。 |
## 警示信号
- 写了超过 100 行代码还没有运行测试
- 单个增量中包含多个不相关的改动
- "让我顺便快速把这个也加上"的范围扩张
- 为了加快速度跳过测试/验证步骤
- 在增量之间构建失败或测试失败
- 大量未提交的改动在累积
- 在第三个使用场景要求之前就构建抽象
- 改动任务范围之外的文件,"反正我在看这个文件"
- 为一次性操作创建新的工具函数文件
- 在没有任何代码改动的情况下连续运行两次相同的构建/测试命令
## 验证
完成一个任务的所有增量之后:
- [ ] 每个增量都单独测试并提交了
- [ ] 完整的测试套件通过
- [ ] 构建干净无错误
- [ ] 功能按规范端到端工作
- [ ] 没有遗留未提交的改动