资讯详情

资讯详情

SDD+TDD组合拳:让AI生成代码从抽盲盒变成开火箭

团队里最近流传一个段子以前用 AI 写代码像抽盲盒运气好出一段能跑的代码运气差就陷入“生成-报错-再生成”的死循环半天提交不了几行。但这两周他们用了一套叫 SDD TDD 的打法直接把效率拉起来了连需求评审都比以前快了用他们的话说——“这回是开着火箭写代码”。这套打法其实不玄乎核心就是把“先写代码再补测试”的惯性思维彻底翻过来先用规格文档SDDSpec-Driven Development把需求钉死再用测试用例TDDTest-Driven Development把行为钉死最后才让 AI 在两头都钉死的框架里填实现。AI 不再是那个替你拍板的人而是变成了一个执行力极强的施工队。本篇就是把这套组合打法从头到尾拆开结合一个完整示例讲清楚每一步为什么这么做、怎么做、遇到问题怎么排。1. 为什么直接甩需求给 AI 像是在“抽盲盒”先说个扎心的事实你把需求直接扔给 AI本质上是在用一个模糊的句子去换一段概率生成的代码。AI 编码工具看着智能但它并不懂你们的业务上下文、隐式约定和历史包袱。它对“做一个用户列表”这个需求的理解大概率是一堆通用模板的加权平均——有可能给你返回带权限管理的大而全版本也可能返回一个只打印数据的教学代码。同一个提示词换个时间问出来的东西可能完全不一样。这种不确定性就是“抽盲盒”的根源。我见过不少开发者的真实状态需求一句话丢给 AIAI 生成一段看似完整的代码直接粘进项目里编译不过就让它修修完这边又破坏了那边像打地鼠。代码能跑通但过两天需求微调发现当时 AI 把一些业务规则硬编码在了深处改起来像拆炸弹。最隐蔽的问题是“假信心”——生成的代码不报错但边界处理、异常逻辑、权限校验全是缺口测试一上就漏气。用 AI 编码不是原罪原罪是让 AI 在没有约束的状态下替你做了设计决策。它不知道哪些条件分支要有哪些边界必须兜住哪些行为属于“关键路径”。它只知道“看起来像那么回事”。所以真正的解法不是埋怨 AI 不行而是把决策权收回来让 AI 只做翻译和执行。SDD TDD 的组合拳就是干这件事的。2. SDD先写规格文档把“要什么”钉死2.1 什么是规格文档它和需求文档有什么区别很多人一听到“写文档”就头疼下意识觉得又是重流程。这里说的规格文档SDDSpec-Driven Development不是几十页的 PRD而是一份面向实现的、结构化、可验证的行为描述。它比需求文档更接近代码但又不等于代码本身。需求文档回答“我们要解决什么问题”规格文档回答“系统在什么输入下应该产生什么输出、有什么行为”。比如需求文档会说“用户需要能重置密码”规格文档就会写提供/api/reset-password接口POST 方法入参为{ email }和{ callbackUrl }若邮箱存在生成一次性 token有效期 30 分钟并发送邮件若邮箱不存在仍然返回 200防止用户枚举但日志标记 warn同一个邮箱在 60 秒内不能重复触发邮件发送。你看规格文档其实是把模糊业务翻译成无歧义的契约。这就是 SDD 的精华——AI 最擅长的就是在明确契约下生成实现最怕的就是替你想契约。2.2 写完规格之后才是真正的关键动作很多时候大家写完规格文档AI 就开始出代码了——这不对。毕竟是测试驱动规格文档写完后第一件事是写测试用例。先别急着写实现把规格里的每一条行为拆成一个或多个测试。这些测试刚开始必然全部失败RED你甚至看不全代码结构但测试给了你一个非常明确的“完工定义”。有了这个“完工定义”你再去调用 AI 写实现GREEN心里就踏实了——它写得对不对拿测试一跑就知道。最后再统一做重构和微调REFACTOR清理重复代码、补充注释、提炼公共逻辑。整个过程走完就是一次标准的 TDD 循环。这样做的额外好处当你把规格文档和测试用例都备齐之后给 AI 的指令就不再是“帮我写个用户列表”而是变成了“这是一份规格这是对应的测试请在满足测试的前提下实现规格要求”。AI 的发挥空间被限定在了一条窄道上它的“创造力”被用于实现契约而不是凭空制定契约效果天差地别。2.3 规格文档的颗粒度怎么定避免过度设计总结一个经验只要测试能验证到的行为就应该写进规格测试验证不了的东西尽量别写进规格。比如“代码要有注释”“接口响应时间要小于 100ms”——前者测试很难直接感知后者如果还没有性能测试环境写了也是空头支票。宁可先把验证不了的拿掉保持规格干净让每一条都落地到测试。颗粒度上我偏爱一个返璞归真的标准规格文档约等于“把测试用例用自然语言说一遍”。如果一条规格无法映射到至少一个测试用例那它就是花瓶条款。宁可少几条宽泛的描述也不要堆砌无法验证的愿望。该写进规格的写进去比如字段校验规则、状态码约定、超时阈值不该写的先砍掉比如“UI 要好看”这种主观感受。3. TDD用测试给 AI 戴上“紧箍咒”3.1 测试用例是第一手上下文比提示词管用十倍写测试用例为什么对 AI 编码这么关键因为它改变了 AI 接收的信息类型。当你只说“实现一个用户注册接口”AI 收到的是一个宏观指令但当你把测试用例喂给 AI 时它收到的其实是一份可执行的行为约束清单。每个断言都是明确目标AI 的实现就会被这些断言牵引。我做过对比实验同一个需求一半时间用“直接生成”一半时间用“测试先行 AI 实现”结果差异非常明显——测试先行的情况下生成的代码平均少返工 3 次而且边界处理的完整性远超直接生成。这不是玄学说白了就是上下文质量不同。测试就是把“隐含需求”显性化的最佳载体。3.2 让 AI 补测试而不是人肉写全部测试但要设置护栏有些朋友会说我也知道测试重要但让我先写测试再写代码工作量岂不是翻倍了这里有个不太优雅但很实用的技巧第一版测试可以由人写主干让 AI 补细节分支。你写出正常路径happy path和核心规则然后让 AI 补边界值、错误输入、并发场景这些穷举类的东西不加快吗确实能省不少事。但护栏必须立住AI 补的测试必须和规格文档逐条对应凡是测试描述的行为超出了规格范围的要么改测试要么改规格——不能默认让 AI 自己发明新契约。一旦放行规格就形同虚设盲盒体验就回来了。你只需要控制住这个环节整个流程就不会失控。4. 完整实战某跨平台系统核心模块改造实录光讲方法论没意思拿一个真实模拟场景走一遍完整流程。假设现在要做的是“某跨平台系统”的“待办事项管理模块”后端接口需求描述是用户能创建待办事项、标记完成、按状态筛选。4.1 第一步写规格文档明确行为和边界规格文档我控制在 10 条以内避免过度膨胀创建待办POST/api/todos入参{ title: string, dueDate?: string }title 必填长度 1~200 字符超过返回 400dueDate 可选格式必须为 ISO 8601 日期格式错误返回 400创建成功后返回 201响应体包含新记录的完整信息含 id、createdAt列表查询GET/api/todos?statusactive|completed|all默认 allactive 状态包含未完成的事项completed 包含已完成的标记完成PATCH/api/todos/:id/complete返回 200 和更新后的记录标记不存在的 id 返回 404严禁删除已完成超过 30 天记录出现在任何列表里历史数据清理规则并发标记同一 id 时以最后请求为准不引入分布式锁保证接口幂等即可。这里我特意放了几条容易“翻车”的边界规则进去title 长度限制、日期格式校验、404 处理、幂等语义——这些正是直接丢给 AI 时最容易出的幺蛾子。现在它们被明确契约锁死AI 想错都难。4.2 第二步先写测试构造红绿转换的轨道接下来写测试。为了不过度展开代码我提炼每个测试的核心意图实际工程中你可以直接用 Jest / Vitest 套件落地用例 1title 合法时创建成功返回 201 且响应包含 id用例 2title 为空时返回 400用例 3title 超过 200 字符时返回 400用例 4dueDate 非法格式时返回 400用例 5插入不存在的 id 执行 complete 操作时返回 404用例 6创建 3 条不同状态记录用 status 参数过滤active 列表只出现未完成项用例 7并发发送两个 complete 请求到同一 id最终状态为已完成且响应均为 200幂等用例 8创建已完成超过 30 天记录的场景任何列表查询结果都不包含该记录。把规格逐条翻译成断言一条规格最少一个测试多了更好。写完先跑一遍——全红正常。红的时候别慌这正是你要的“开工信号”。4.3 第三步把上下文喂给 AI让它“戴着镣铐跳舞”这一步是最见功力的。别一上来就贴代码先把上下文分层准备好第一层技术栈说明比如“使用 TypeScript Express测试框架用 Jest数据用内存数组模拟存储不引入数据库”第二层规格文档全文第三层测试用例全文可以直接贴测试文件内容也可以贴经过整理的断言列表。然后提示语可以这样写根据以下规格文档和测试用例实现一个符合规格的待办事项模块。测试用例是最高优先级契约你的实现必须让所有测试通过。不要添加规格之外的额外行为不要改变现有测试。实测下来这个提示语的关键点在于“测试用例是最高优先级契约”这句话。它会把 AI 的注意力从“创新”拉回到“满足约束”生成结果的质量提升非常明显。第一次生成的代码大概率接近正确但别指望 100% 全绿。跑测试看哪些用例挂了把报错信息原样丢回给 AI 让它修。记住一个铁律不要让 AI 直接改测试来迎合实现永远改实现去满足测试。一旦破例TDD 的防护墙就塌了。4.4 第四步重构与优化不破坏绿洲当测试全绿之后进入 REFACTOR 阶段。这个时候你有测试网兜底可以放心让 AI 做结构优化把路由处理函数里的校验逻辑抽成独立中间件将状态过滤逻辑封装成纯函数便于后续单元测试统一错误响应格式让所有异常都走同一个 error handler删掉 AI 生成但并未被测试覆盖的死代码——这一刻你会真正体会到测试的作用它告诉你哪些代码是“被需要的”哪些只是 AI 的“自我发挥”。重构完成后再次全量跑测试。绿的说明本次变更安全落地。这一步千万别省这是从“能跑”到“好维护”的分水岭。4.5 第五步Commit 信息与文档同步闭环收尾最后一步容易被忽视代码提交时Commit 信息里把“规格文档变更点”和“测试覆盖范围”简明写上。比如feat(todo): 实现待办清单核心接口依据规格 v1.2测试覆盖创建/校验/筛选/幂等/清理规则全 8 项通过。这样做的好处有两个一是后续回溯需求时立刻能找到对应的规格和测试二是让 Code Review 的人不用从零猜上下文。一套组合拳下来从“需求”到“测试”到“实现”到“维护”形成了完整闭环。5. 常见问题与排查技巧实录5.1 规格文档写得挺好AI 生成的代码还是不符合预期卡在哪了这是最常见的问题基本都出在“上下文割裂”上。一个高概率盲区是你把规格写在需求文档里测试放在另一个目录AI 只看到了某一段上下文。解决方案也很简单在每一步 AI 交互时把规格核心条款和对应测试同时粘贴在一个提示语里不要依赖 AI “记住”你很久以前发的消息。上下文的即时性和相关性远大于历史消息的长度。5.2 AI 补的测试“加了料”把规格之外的行为也写进去了怎么防这个我栽过跟头。当时让 AI 补边界测试它补了一条“当 title 包含 HTML 标签时应该返回 422”——规格里压根没有这条还引出了一个需求层面从未讨论过的决策。后来我定了规矩凡是 AI 补的测试Review 时必须逐条对照规格多出来的一律打回。打回的时候顺便把缺漏的规格条款补齐测试从红线变成需求输入来源之一反而拓展了需求思考的维度。5.3 测试全绿但心里没底总感觉漏了什么绿测试不代表没有问题它只代表“契约覆盖范围内的行为符合预期”。这时候可以做两件事一是用变异测试工具检验测试本身的质量故意改坏一行实现逻辑看测试能不能抓到抓不到就说明测试断言的覆盖有漏洞二是把测试列表贴给 AI让它扮演“恶意评审者”尝试找出规格文档里还没被任何测试覆盖的路径。我个人实践下来AI 在扮演挑刺角色时表现得相当不错因为它没有“保护自己代码”的心理包袱。5.4 团队协作时规格和测试该放在哪里才不显得格外沉重轻量团队别搞复杂的文档系统直接在代码仓库里建一个specs/目录用 Markdown 按模块命名测试代码紧挨着实现代码放并建立 CI 约束规格文档变更必须有对应测试变更测试变更必须触发全量测试。用最朴素的文件结构把这套流程固定下来比引入昂贵的流程平台更接地气。如果实在想省事在 Commit 模板里备注 section 也行但效果会打折。5.5 什么时候不该用 SDD TDD什么时候必须用说句公道话这不是万能银弹。做一次性脚本、临时排查、原型验证时直接让 AI 放开跑用完就扔搞完整套流程反而是负担。但凡是会进生产环境、会被长期维护、会对接多个模块的代码我强烈建议至少把规格和核心测试搭起来。改代码的时候你就会知道这套流程省的力气远比写的时候花的力气多。6. 从 Tools 到 Workflow让团队真正“开火箭”单人使用 SDD TDD 只是开了一辆快车团队一起用好这套逻辑才真正算是“开火箭”。核心是把“人与 AI 的分工”梳理清楚人负责定规格、写核心测试、做最终评审AI 负责翻译实现、补边界测试、执行重构建议。每当新增需求或修改行为先改规格和测试再让 AI 动实现代码。时间久了这套顺序变成条件反射AI 的不确定性影响会越来越小。我见过最丝滑的团队状态是规格评审会上大家盯着测试用例讨论行为开发阶段反而话不多因为实现路径已经被契约限定了。这种感觉和“打开盲盒等结果”完全是两个物种。如果你们团队还在为 AI 生成代码的稳定性头疼不妨从下周的一个小模块开始试着把完整套路走一遍——你先写规格再写测试然后让 AI 填空。真正飞起来之后你会发现之前不少时间其实都耗在“和模糊的需求搏斗”上了而不是花在写代码。最后补一个私藏小技巧做重构时让 AI 先列出“它认为可以删除但没有测试覆盖的代码”你自己 Review 一遍这些代码再决定是补测试还是删代码。很多时候比你自己盯着代码找死代码高效得多。这套流程跑顺了AI 就不再是那个让你抽盲盒的魔术师而是你手底下最听话的施工队。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →