:让AI编码从抽卡变成可控交付)
上个月在群里帮一个朋友看问题他们团队用AI写一个订单状态筛选的功能来来回回改了七八轮。每一轮的新提示词都写得很“专业”把需求、格式、边界都列了个遍但生成结果不是漏了取消订单的状态就是把时间字段的时区改了。最后朋友无奈地跟我说这哪是写代码这是抽卡。我回了一句你现在用的就是典型的“提示与祈祷”你以为你在控制AI编码其实你只是把提示词扔进去然后在心里默念“这次应该能行吧”。这就是规范驱动开发SDDSpecification-Driven Development想要解决的问题。它不是让你换一个更好的提示词模板而是让你把整套开发流程翻过来先写清楚规格再让AI照着规格实现。这篇文章我会把这个思路、完整落地步骤、实测效果和几个最容易翻车的坑全部讲一遍适合那些已经用AI写了不少代码、但发现结果越来越不可控的团队和个人开发者。1. 为什么“提示与祈祷”注定让AI编码不可控1.1 提示词是命令不是契约很多人以为AI编码不可控是因为“提示词写得不够好”于是不停地在提示词上做文章。这个方向从根上就错了。提示词本质上是个命令——“给我写一个函数做某某事情”——它描述的只是你想要的最终结果而不是中间必须遵守的约束。这就跟你在工地上跟施工队说“给我盖栋楼”一样施工队盖出来的可能是个能住的平房也可能是个没有楼梯的三层楼。你气急败坏地补充“要有窗户要刷外墙”但每补充一句只是多了一个命令仍然没有形成一份图纸和规范。真正的规划应该是“这里是什么用途、面积多大、承重结构是什么、验收标准是什么、哪些东西绝对不能做。”AI需要的不是更多命令而是一份可验证的约束集合。提示词是点状的规格是面状的。1.2 上下文污染每一次失败的反馈都在给下一轮埋雷“提示与祈祷”还有一个非常隐蔽的代价就是上下文污染。你看到生成结果不对很自然地会在下一轮提示词里追加一句“注意订单状态应该包含cancelled”“返回字段不要用null要用空数组”。问题是大模型会平等地记住你的原始需求、前五轮的修改意见以及上一轮生成的那段错误代码。到了第三轮以后AI的注意力已经被大量“救火式修改”占据原始需求反而成了背景噪声。我见过最夸张的情况是AI因为在某一轮被告知“不要用null”结果把所有字段都强行套上了空字符串连数字类型的金额都变成了字符串整个接口的契约直接崩了。这就是“提示与祈祷”的恶心之处每一次失败反馈都在污染下一轮的生成分布你不是在往确定性方向收敛而是在往复杂度方向发散。1.3 可控性的真正含义可预测、可验证、可回滚那“可控的AI编码”到底是什么意思我觉得就三条。可预测同样的输入、同样的规则AI生成的代码在行为上是一致的不会今天返回空数组、明天返回null。可验证每个生成结果都有明确的标准去检查不是靠人肉review“看着差不多”。可回滚当某一轮的实现有问题时能明确知道是哪一个规格条目没满足而不是推倒重来、再赌一轮prompt。对照这个标准“提示与祈祷”三条全不满足。而规范驱动开发SDD的做法本质上就是先把这三条用文档固定下来再让AI去做实现。2. 规格驱动开发(SDD)的本质把“施工蓝图”递给AI2.1 一份能直接喂给AI的最小规格长什么样我在项目里用SDD时一份规格文档通常不是长篇大论的PRD而是极度结构化的“施工图”。我用一个实际案例说明。假设我要让AI实现一个“订单导出CSV”的功能一份最小规格长这样功能订单导出CSV 输入 - 时间范围: startDate, endDate均为YYYY-MM-DD - 订单状态: status可选值为created/paid/shipped/completed/cancelled不传代表全部 输出 - CSV文件字段顺序为订单号下单时间客户名金额状态 - 金额单位为元保留两位小数CSV用UTF-8编码并带BOM 行为约束 - 时间范围超过90天时直接返回错误码RANGE_TOO_LARGE - 导出数据量超过1万行时拆分为多个文件并返回文件列表 - 不得修改数据库中的任何数据 验收标准 1. 当endDate早于startDate时返回400 INVALID_DATE_RANGE 2. 当查询结果为空时返回空列表而不是报错 3. CSV字段必须与给定顺序完全一致 4. 状态为cancelled的订单必须包含在导出行中 5. 金额字段不得出现浮点精度误差这份规格看起来很简单但注意几个关键点输入和输出被明确限定行为约束直接写死了“不能改数据”验收标准给出了具体断言。不要小看第四条“状态为cancelled的订单必须包含”真实项目里AI很容易默认过滤掉已取消的订单因为产品需求里没强调但它恰恰是用户最需要的。2.2 为什么规格比提示词更符合大模型的生成逻辑大模型的训练任务本质上是“根据前文预测下一个token”它天然擅长“在约束条件下填空”而不是“凭空构建你的意图”。提示词更像是让AI做一道开放性作文题它写得再漂亮也未必是你的意思。规格则像是一道有标准答案的填空题每个约束都在限制解空间。你写“帮我写个导出功能”AI的解空间是无限大的它可以自由选择字段顺序、时间格式、编码方式。你写“字段顺序必须为订单号/下单时间/客户名/金额/状态金额保留两位小数”AI的解空间就被压缩到了一小块区域它的每个token生成都更可能落在正确范围内。另一个容易被忽略的点是大模型对“确定性表述”比对“愿望描述”更敏感。你告诉它“这个导出可能有点慢最好别超时”它不知道该怎么办。你告诉它“超过1万行时拆分文件”它就知道这是个具体分支可以写出对应的判断逻辑。2.3 SDD不是文档驱动也不是TDD替补很多人听到“先写文档再做开发”第一反应是“这不就是文档驱动开发吗流程更重了”。这里要区分清楚。传统文档驱动是给“人”看的写需求、写设计、写接口文档目的是降低团队沟通成本。SDD的规格文档是给“AI”看的它同时承担了你的意图表达、约束定义和验收依据三重角色。换句话说SDD的文档不是挂在Wiki里的摆设而是每一段都会被AI直接读取并执行的工作指令。TDD测试驱动开发关注的是“代码应该怎样被验证”它是在实现之后或者实现的同时去写测试。SDD关注的是“需求边界是什么”它是在实现之前把业务规则、行为约束、错误路径、边界条件全部显式化。SDD写出来的规格可以直接转化成TDD的测试用例但两者层级不同。我经常用的一个比喻是TDD是给代码装质检仪SDD是先把产品说明书定义清楚。没有产品说明书质检仪也不知道该测什么。3. 我的SDD五步落地法从需求到AI实现的全流程3.1 第一步用“边界问题清单”把模糊需求问清楚SDD落地的第一件事不是写文档而是把需求访谈变成边界确认。经验不足的人在这里会直接动手写规格结果写出来的东西跟PRD没有任何区别。我每次接到一个需求会先列一个边界问题清单这个功能的核心输入是什么取值范围是什么输出给谁看格式有没有硬性要求数据为空时应该是什么行为数据量超出预期时应该怎么处理有没有绝对不能做的事情不能改数据、不能调外部接口、不能删索引错误情况下要返回什么是静默处理还是抛出异常这些问题看起来基础但每一条都能帮你筛掉大量“AI自由发挥”的空间。拿上面那个订单导出来说“数据量超出预期时怎么办”就是最容易漏问的因为需求方一开始只会说“把订单导出来”根本不会想到90天这个限制。3.2 第二步用结构化格式写规格坚持“验收标准优先”规格文档的格式我建议用YAML或JSON而不是纯文本。因为大模型对结构化格式的解析能力非常强同样的语义YAML呈现的约束比大段文字醒目得多。而且结构化格式天然能强制你把“输入”“输出”“约束”“验收”分门别类防止写成一团浆糊。写规格时我坚持一个原则验收标准先写行为约束后写。为什么因为验收标准是驱动AI行为的核心信号。你把验收标准放在前面AI生成代码时会把每条标准对应到实现上。你把验收标准藏在最后面AI的注意力已经分散了可能写完了都没有认真看。一份完整的规格验收标准的粒度要细到能直接转成代码断言。比如“时间范围超过90天时返回错误码RANGE_TOO_LARGE”这可以直接变成一行代码。如果你的验收标准还停留在“性能要好”“用户体验要流畅”这种层面说明你还没想清楚自己要什么。3.3 第三步把规格拆成独立单元一次只喂一段很多人在“提prompt”时犯的最大错误就是把整份规格一次性全部丢给AI指望它一口气生成几千行代码。结果AI确实生成了几千行但前半部分符合规格、后半部分开始飘。这不是AI不听话而是注意力窗口有限规格量太大时前面的约束会被后面的信息挤掉。我的做法是把功能拆成多个独立单元每个单元对应一份子规格每次只让AI实现一个单元。比如“订单导出”我会拆成三个单元单元A参数校验和时间范围检查单元B查询订单数据并组装CSV行单元C文件拆分与结果返回每个单元六七行规格AI实现起来压力很小出错率也低。单元内部的实现细节我也不关心AI用循环还是用map都行只要行为符合规格。拆单元的标准是每个单元都可以独立验收单元与单元之间通过明确的接口衔接。如果你的某个“单元”还需要跟别的单元共享内部状态那就是拆得不够干净。3.4 第四步让AI先交实现计划再动代码这是我用SDD之后新增的一步效果异常好。在让AI写代码之前先让它根据规格输出一份“实现计划”内容包括它打算分几个函数、每个函数做什么、边界条件怎么处理。这一步的作用相当于让AI“先复述一遍需求再动手”可以提前暴露对规格的误解。有一次我让AI实现权限判断逻辑它的计划里写“如果用户角色为guest直接拒绝所有操作”但我的规格写的是“guest只能访问公开接口”。这就是一个典型的理解偏差如果它直接动手写代码我肯定要到代码review或者测试阶段才发现但提前走一步实现计划几秒钟就暴露了。实现计划不需要多详细三五条要点就够了。关键是让AI把它准备怎么做说出来你确认之后再说“开始写代码”。3.5 第五步按验收标准逐条测试不靠肉眼review最后一步是整个SDD流程里最关键的也是很多人会偷懒的一步。AI把代码写完了你不能看一眼觉得“差不多”就收工而是要拿着规格里的验收标准逐条去验证。我的做法是给每个验收标准写一个对应的测试脚本。如果规格有5条验收标准我就要求AI同时生成5个测试用例并保证跑通。这一步就是把SDD和TDD衔接起来规格负责定义标准测试负责执行检查。以“订单导出”为例验收标准第二条“当查询结果为空时返回空列表而不是报错”对应的测试就是构造一个查不到任何订单的时间范围然后断言接口返回200和空数组。有一点需要提醒有些验收标准AI自己是测不全的比如“金额字段不得出现浮点精度误差”AI生成的测试用例很可能只测了整数金额。这种情况你要自己补一个边界测试比如传一个0.10.2的场景。规格流程只能保证有验证机制不能保证验证完备最终还是要人来补关键断言。4. 实测数据SDD把返工率降到了什么程度4.1 对照实验同一个功能两种方式的真实差异我大概在一个月前做了一次对照实验连续两周挑了5个中等复杂度的功能每个功能都用“提示与祈祷”方式写一遍再用SDD方式写一遍。这5个功能分别是列表查询、状态机流转、数据导入校验、权限点判断、定时任务重试。结果对比非常明显。纯提示词方式下单个功能平均要迭代6.2轮才能达到“看起来能用”的状态而且有3个功能在“看起来能用”之后过了两周又被发现边界漏洞。SDD方式下单个功能平均1.8轮就通过验收5个功能里只有1个在上线后发现边界情况没有覆盖到位。最有意思的是SDD模式下那唯一一个漏掉的边界其实规格里写了但我忘记把这个规格条目写进测试脚本AI实现的时候也没处理。这说明SDD不是银弹流程里任何一个环节偷懒都会漏。4.2 时间成本要算总账写规格的时间没有“浪费”有人会担心SDD前期要写规格太浪费时间。但我算完总账之后发现这是最划算的投入。5个功能加起来纯提示词方式的总耗时是大概14个小时。SDD方式前期写规格花了大概5个小时听起来很吓人但后面AI生成和调试的时间大幅缩短总耗时只有8个小时左右。换句话说多花35%的时间在规格上总时间节省了约43%。而且这只是明面上的时间账。暗面上纯提示词方式留下了3个上线后才被发现的漏洞每个漏洞平均要花2-3个小时去定位和修复这部分成本没算进上面的时间。SDD方式虽然也有1个漏网之鱼但因为它有规格文档和测试用例定位问题只花了20分钟。4.3 什么指标变得明显可控除了时间和轮次我更关注的是三个质量指标。第一是“行为一致性”。SDD模式下相同的输入、相同的规格AI在不同轮次生成的代码在行为上保持一致的比率非常高。纯提示词模式下AI很可能这轮用空数组、下轮用null搞得前端对接的人欲哭无泪。第二是“回归风险”。因为验收标准是文档化的每次改动规格后都可以重新跑一遍全部测试用例回归风险从“靠记忆力”变成了“靠脚本”。第三是“AI自作主张的概率”。纯提示词模式下AI非常容易“好心办坏事”比如自动拼接字段、自动加个排序、自动把返回结构改了。SDD模式下因为行为约束写在前、验收标准写得细AI的“自由发挥空间”被压制得很低。5. SDD落地最容易翻车的五个地方5.1 把规格写成了PRD摘抄我第一次带团队推行SDD的时候有同事写的规格大概是这样的“用户希望可以方便地导出订单导出过程要流畅数据要准确。”这种规格扔给AI它唯一的结局就是继续抽卡。规格不是需求描述是行为定义。“流畅”是主观感受“准确”是废话。合格的规格要么是一段可执行的条件判断要么是一组可枚举的输入输出映射。如果一句话没法被测试脚本验证它就不应该出现在规格文档里。5.2 验收标准写得太弱等于交给AI“良心”还有一类规格约束写得还行但验收标准写成了“程序可以正常运行无报错”。这等于根本没有标准。AI生成的代码当然不会在它自测的时候报错因为它的自测用例是你描述的那个“正常情况”。真正的验收标准应该针对危险情况。我总结了一个经验法则一条验收标准里至少要有一个“反例”在里面。比如“当查询结果为空时返回空列表”“当时间范围超过90天时返回错误码”。验收标准里如果没有“当什么什么时”这种反例句式那就说明你又在写需求描述了。5.3 单次上下文过大规格反成噪音规格写得太长也会出问题。我之前试过把一个中大型模块的完整规格大概五千字一次性丢给AI让它“按照规格实现”结果它只认真实现了前三分之一后面的功能全部默认简化处理。这不是AI偷懒而是注意力机制决定的。规格越长每条约束的权重就越低。现在我的经验是单次交给AI的规格控制在800字以内一个功能模块拆成多个单元每个单元单独生成。宁可多做几次交互也不要一次塞太多。如果你实在有很长的全局约束比如“本项目的错误码规范统一为XXX”可以在每个单元规格里都重复一遍这条约束。重复不是冗余是对注意力机制的尊重。5.4 只改规格不重跑验证循环断裂SDD流程里最重要的一条闭环是改规格 → 重跑全部测试。但实际操作中很多人改完规格就只想让AI“只改那一个地方”不想重新验证其它部分是否被影响。这个想法在传统开发里可能是合理的在AI编码里非常危险。因为AI在修改某段代码时很可能顺手改坏一个原本正确的分支。如果你没有在所有改完后重跑一遍完整验收标准这个回归问题可能直接流到线上。所以我现在的规则是只要规格文档有任何一条变更AI的代码改动完成后必须把该单元全部测试用例重新执行一遍任何依赖它的相邻单元也要一起跑。5.5 团队协作里规格没人维护个人项目用SDD很简单因为规格是我自己写的我清楚每一句话背后的意图。但到了多人协作的场景规格文档很容易变成没人维护的僵尸文件。最常见的情况是产品说“需求变了取消订单也要出现在导出里”开发觉得“这只是加个过滤条件而已”直接在代码里改了没有回头去更新规格。等下一次AI重新生成这段代码时用的还是旧规格于是“取消订单被过滤掉”这个问题又复现了。解决办法是让代码生成流程强制依赖规格文件。在我现在的工作流里不允许任何团队成员绕过规格直接跟AI对话改代码。所有变更必须先改规格再由AI基于新规格重新生成或修补代码。这个过程把规格变成了单一事实源而不是可有可无的参考文档。6. 边界判断什么时候必须SDD什么时候别折腾6.1 适合SDD的任务画像我用一个四象限来判断横轴是“业务规则的明确度”纵轴是“回归风险”。业务规则越明确越适合SDD。比如订单状态流转、导入校验、权限判断、报表计算这些场景的规则是完全可枚举的每条规则都可以转成验收标准。回归风险越高也越适合SDD。比如核心交易流程、对外API合同、数据迁移脚本这些代码一旦出错代价极高靠“提示与祈祷”就是在赌运气。具体来说我建议以下类型的任务必须用SDD接口开发、批量数据处理、数据库迁移、状态机实现、权限模型落地。这些任务有个共同点行为可以被清晰地描述和执行且错误代价高。6.2 不适合SDD的场景相应的有些场景硬套SDD会非常痛苦。最典型的是探索性原型开发。你连需求都没想清楚需要靠AI帮你头脑风暴、快速验证方案的可行性这时候写规格就是本末倒置。原型阶段就应该用宽松的提示词目的是快速试错不是稳定交付。还有一个不适合的是UI视觉效果。AI生成的页面布局、颜色搭配很难用“验收标准”去约束因为审美是主观的。这种场景更适合用截图反馈循环调样式而不是写规格。一次性工具脚本也不适合。比如我要临时把某个JSON文件转成CSV用完就删写一份规格的时间可能比手写代码还长。SDD的收益来自复用和回归一次性的东西根本不具备这个基础。6.3 不想一步到位的渐进过渡法你不需要第二天就把所有AI编码流程切换成SDD。我推荐的路径是先挑一个你最近要做的小功能按照第三部分的五步法走一遍。哪怕你这个功能只需要AI写两百行代码也要把规格、验收标准、测试脚本全套走完。走完这个流程之后你会对“规格要写到什么粒度”有切身体感这种体感是看再多文章也学不来的。然后逐步扩展到核心模块。当你发现SDD写出来的代码在后续改动中几乎不用返工就会自然形成路径依赖。我自己就是这么过来的刚开始只在导出功能上用SDD后来扩展到所有接口开发再后来连数据库迁移脚本也用规格约束。如果团队里有人觉得写规格太麻烦我的建议是让他们先统计一下自己在“提示与祈祷”上浪费了多少时间。大多数人对返工时间是麻木的因为他们把“和AI反复沟通”当成了工作常态而不是一种可避免的浪费。最后再分享一个我自己的小技巧在每份规格的末尾加一节“常见误解与禁止行为”。比如“不要修改数据库结构”“不要引入额外依赖”“不要重排输出字段顺序”“不要对金额做四舍五入以外的处理”。这一节相当于给AI戴上了一副“紧箍咒”专门压制它自作主张的冲动。实践下来这一步对降低返工率的效果有时比主规格里的验收标准还明显。你可以先试一个小功能看看加了这一节之后AI是不是变得更老实了。