8.5 KiB
8.5 KiB
name, description
| name | description |
|---|---|
| incremental-implementation | 增量式交付变更。适用于实现任何涉及多个文件的特性或改动,以及当你准备一次性编写大量代码,或觉得某任务难以一步到位时使用。 |
增量实现
概述
以薄垂直切片的方式构建 —— 实现一小块、测试、验证、再扩展。避免一次性完成整个功能的实现。每个增量都应让系统处于可工作、可测试的状态。这是让大型功能变得可控的执行纪律。
何时使用
- 实现任何涉及多个文件的改动
- 根据任务分解构建新功能
- 重构现有代码
- 任何时候你忍不住要在测试前写出超过约 100 行代码
何时不使用: 单文件、单函数的改动,且范围已经极小。
增量循环
┌──────────────────────────────────────┐
│ │
│ 实现 ──→ 测试 ──→ 验证 ──┐ │
│ ▲ │ │
│ └───── 提交 ◄─────────┘ │
│ │ │
│ ▼ │
│ 下一个切片 │
│ │
└──────────────────────────────────────┘
对于每个切片:
- 实现 最小的完整功能片段
- 测试 —— 运行测试套件(若不存在则编写测试)
- 验证 —— 确认切片按预期工作(测试通过、构建成功、手动检查)
- 提交 —— 用描述性信息保存进度(参见
git-workflow-and-versioning关于原子提交的指导) - 进入下一个切片 —— 向前推进,不要重头开始
切片策略
垂直切片(推荐)
构建一条贯穿完整技术栈的路径:
切片 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:不完整功能使用特性开关
如果一个功能还不能对用户开放,但你需要合并增量:
// 进行中工作的特性开关
const ENABLE_TASK_SHARING = process.env.FEATURE_TASK_SHARING === 'true';
if (ENABLE_TASK_SHARING) {
// 新的分享界面
}
这让你可以将小增量合并到主分支,而不会暴露未完成的功能。
规则 4:安全默认值
新代码应默认采用安全、保守的行为:
// 安全:默认关闭,主动选择启用
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 行代码还没有运行测试
- 单个增量中包含多个不相关的改动
- "让我顺便快速把这个也加上"的范围扩张
- 为了加快速度跳过测试/验证步骤
- 在增量之间构建失败或测试失败
- 大量未提交的改动在累积
- 在第三个使用场景要求之前就构建抽象
- 改动任务范围之外的文件,"反正我在看这个文件"
- 为一次性操作创建新的工具函数文件
- 在没有任何代码改动的情况下连续运行两次相同的构建/测试命令
验证
完成一个任务的所有增量之后:
- 每个增量都单独测试并提交了
- 完整的测试套件通过
- 构建干净无错误
- 功能按规范端到端工作
- 没有遗留未提交的改动