--- 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 行代码还没有运行测试 - 单个增量中包含多个不相关的改动 - "让我顺便快速把这个也加上"的范围扩张 - 为了加快速度跳过测试/验证步骤 - 在增量之间构建失败或测试失败 - 大量未提交的改动在累积 - 在第三个使用场景要求之前就构建抽象 - 改动任务范围之外的文件,"反正我在看这个文件" - 为一次性操作创建新的工具函数文件 - 在没有任何代码改动的情况下连续运行两次相同的构建/测试命令 ## 验证 完成一个任务的所有增量之后: - [ ] 每个增量都单独测试并提交了 - [ ] 完整的测试套件通过 - [ ] 构建干净无错误 - [ ] 功能按规范端到端工作 - [ ] 没有遗留未提交的改动