增量实现(Incremental Implementation)
概述
以薄垂直切片的方式构建——实现一小块、测试它、验证它,然后扩展。不要在一次实现中完成整个功能。每个增量都应让系统处于可工作、可测试的状态。这是让大型功能变得可控的执行纪律。
何时使用
实现任何涉及多个文件的变更;
从任务拆解出发构建新功能;
重构现有代码;
任何时候你打算在测试前写下超过约 100 行代码。
何时不使用:单文件、单函数的变更,范围本身已经很小的情况。
增量循环
实现 → 测试 → 验证 → 提交 → 进入下一个切片
对每个切片:
实现最小但完整的一块功能;
测试——运行测试套件(若没有测试则编写一个);
验证——确认该切片按预期工作(测试通过、构建成功、手动检查);
提交——用描述性信息保存进度(原子提交指引见 git-workflow-and-versioning);
进入下一个切片——继续推进,不要重来。
切片策略
垂直切片(首选)
构建一条贯穿整个技术栈的完整路径:
切片 1:创建任务(数据库 + API + 基础 UI),测试通过,用户可通过 UI 创建任务;
切片 2:列出任务(查询 + API + UI),测试通过,用户可看到自己的任务;
切片 3:编辑任务(更新 + API + UI),测试通过,用户可修改任务;
切片 4:删除任务(删除 + API + UI + 确认),测试通过,完整 CRUD 完成。
每个切片都交付可端到端工作的功能。
契约优先切片
当后端与前端需要并行开发时:
切片 0:定义 API 契约(类型、接口、OpenAPI 规范); 切片 1a:按契约实现后端 + API 测试; 切片 1b:按符合契约的模拟数据实现前端; 切片 2:集成并做端到端测试。
风险优先切片
先处理风险最高或最不确定的部分:
切片 1:验证 WebSocket 连接可用(最高风险); 切片 2:在已验证的连接上构建实时任务更新; 切片 3:添加离线支持与重连。
如果切片 1 失败,你会在投入切片 2、3 之前就发现。
实现规则
规则 0:简单优先
在写任何代码前,先问:“能工作的最简单做法是什么?”
写完之后,对照以下检查:
能否用更少的行完成?
这些抽象是否值得其复杂度?
资深工程师看到会不会说“你为什么不直接……”?
我是在为假设的未来需求构建,还是为当前任务构建?
简单性检查示例:
✗ 为一条通知引入带中间件管道的通用 EventBus ✓ 简单函数调用
✗ 为两个相似组件使用抽象工厂模式 ✓ 两个直白的组件加共享工具
✗ 为三个表单使用配置驱动的表单构建器 ✓ 三个表单组件
三行相似代码好过过早抽象。先实现朴素、明显正确的版本。只有在测试证明正确之后再优化。
规则 0.5:范围纪律
只碰任务需要的东西。
不要:
“清理”你改动旁边的代码;
重构你并未修改文件中的 import;
删除你并不完全理解的注释;
因为“似乎有用”而添加规范之外的功能;
在你只是阅读的文件中现代化语法。
如果你在任务范围之外发现值得改进的地方,记录下来——不要动手:
注意到但未改动:
- src/utils/format.ts 有一个未使用的 import(与本任务无关)
- auth 中间件可以改进错误信息(另一个任务) → 要我为此创建任务吗?
规则 1:一次只做一件事
每个增量只改变一件逻辑上的事。不要混合关注点:
坏:一个提交里添加新组件、重构现有组件并更新构建配置。
好:三个独立提交——每个变更一个。
规则 2:保持可编译
每个增量之后,项目必须能构建,现有测试必须通过。不要在切片之间让代码库处于损坏状态。
规则 3:未完成功能使用功能开关
如果功能尚未对用户就绪,但你需要合并增量:
// 进行中工作的功能开关 const ENABLE_TASK_SHARING = process.env.FEATURE_TASK_SHARING === 'true';
if (ENABLE_TASK_SHARING) { // 新的分享 UI }
这样可以把小增量合并到主分支,而不暴露未完成的工作。
规则 4:安全默认值
新代码应默认采用安全、保守的行为:
// 安全:默认关闭,需显式开启 export function createTask(data: TaskInput, options?: { notify?: boolean }) { const shouldNotify = options?.notify ?? false; // ... }
规则 5:便于回滚
每个增量都应可独立回退:
新增性变更(新文件、新函数)易于回退;
对现有代码的修改应尽量小且聚焦;
数据库迁移应有对应的回滚迁移;
避免在同一个提交中删除某物并替换它——把它们分开。
与智能体协作
指导智能体做增量实现时:
“我们来实现计划中的任务 3。
先只做数据库 schema 变更和 API 端点。 先别动 UI——我们在下一个增量做。
实现之后,运行仓库的测试与构建命令,验证没有破坏任何东西。”
明确每个增量的在范围内与不在范围内的事项。
增量检查清单
每个增量之后,用仓库自身的命令验证(参见测试驱动开发技能中的“Discover the Stack First”一节):
该变更只做一件事并完整做完;
所有现有测试仍然通过(仓库测试命令:npm test、./gradlew test、pytest 等);
构建成功(仓库构建命令);
若技术栈有类型检查,则类型检查通过(npx tsc --noEmit、mypy 等);
Lint 通过(仓库 lint 命令);
新功能按预期工作;
变更已用描述性信息提交。
注意:在每个可能影响它的变更之后运行相应验证命令。成功运行后,除非代码自那以后已更改,否则不要重复同一命令——对未变更代码重复运行不会带来新信息。
常见自我合理化
“我最后再一起测”——缺陷会复合。切片 1 的缺陷会让切片 2-5 都错。每个切片都要测。
“一次做完更快”——在出问题、你无法从 500 行改动中定位原因之前,它感觉更快。
“这些改动太小,不值得单独提交”——小提交是免费的。大提交会隐藏缺陷并让回滚痛苦。
“我稍后再加功能开关”——如果功能未完成,就不应对用户可见。现在就加开关。
“这个重构足够小,可以捎带”——重构与功能混在一起会让两者都更难审查和调试。分开。
“让我再跑一遍构建命令确认一下”——成功运行后,除非代码已变更,重复同一命令没有意义。在后续编辑之后再运行,而不是当作安慰。
危险信号
未运行测试就写了超过 100 行代码;
单个增量中包含多个不相关变更;
“我顺便也快速加上这个”的范围扩张;
为了更快而跳过测试/验证步骤;
增量之间构建或测试处于损坏状态;
大量未提交变更不断累积;
在第三个用例出现之前就构建抽象;
“既然来了”就碰任务范围之外的文件;
为一次性操作用创建新的工具文件;
在两次代码变更之间没有任何改动却连续运行同一构建/测试命令。
验证
完成一个任务的所有增量之后:
每个增量都单独测试并提交;
完整测试套件通过;
构建干净;
功能按规范端到端工作;
没有遗留未提交变更。
另见
逐增量验证是局部检查。在宣布任务完成之前,应用项目级的“完成定义”(Definition of Done)作为最终关卡,即每个增量无论任务如何都要达到的长期标准。参见 ../../references/definition-of-done.md。