Spec 驱动开发与自验证闭环:我如何让 AI 更稳定地写代码

0 阅读14分钟

最近在公司内部做了一次 AI 编程实践分享,主题叫「Spec 驱动开发与自验证闭环」。这是我这段时间在真实项目里使用 AI 开发后,总结出来比较有价值的两个点。

现在大家用 AI 写代码已经不稀奇了。把需求丢给 AI,让它帮你生成代码,很多工具都能做到。但如果只是这样用,很容易遇到几个问题:第一次生成看起来还可以,后面越改越乱;AI 忘记前面讨论过的需求;代码能跑,但和业务真实想要的不完全一致;改完以后也不知道有没有把原来的功能搞坏。

所以我现在更关心的不是「AI 能不能写代码」,而是「怎么让 AI 稳定地写对代码」。我目前觉得比较有效的方法就是两件事:

  1. 先写 Spec,再写代码
  2. 让 AI 自己验证自己写的代码

什么是 Spec 驱动开发

Spec 是 Specification 的缩写,可以简单理解为「说明书」或者「规格说明」。Spec 驱动开发对应的英文是 Spec-driven development,也可以简称为 SDD。它说白了就是:不要一上来就让 AI 改代码,而是先让 AI 把需求理解、技术方案、接口设计、测试场景写清楚,再基于这些文档去实现代码。

它有点像 AI 工具里的 Plan 模式,但比普通 Plan 模式更进一步。普通 Plan 模式通常只存在于一轮对话里,这次会话结束后,很多上下文就丢了。而 Spec 是落在项目里的文档,它会随着代码一起被维护。

也就是说,Spec 不只是给 AI 看的一段提示词,而是项目的一部分。

我原来用 AI 写代码时,经常会直接说:「帮我实现某某功能」。AI 会很快开始读代码、改代码,看起来效率很高。但这种方式有一个问题,它的理解过程是隐性的。你不知道它到底怎么理解需求,也不知道它准备用什么方案实现。等代码写完后再去 review,成本就比较高了。

Spec 驱动开发把这个过程显性化了。

我会让 AI 先输出类似这样的内容:

  • 当前业务流程是什么
  • 这次需求要改变什么
  • 哪些模块会受影响
  • 准备怎么设计数据库或字段
  • 准备新增或修改哪些接口
  • 正常流程、异常流程、边界条件分别是什么
  • 后面要写哪些测试用例

这些内容确认没有问题后,再让它进入编码阶段。

为什么要先写 Spec

AI 编程最大的问题不是它不会写代码,而是它很容易「自信地写错」。尤其是需求比较复杂、项目代码比较老、业务规则比较多的时候,只靠一段简单提示词,很难让它一次性理解清楚。

先写 Spec 有几个好处。

第一,可以提前发现需求理解偏差。

如果 AI 在 Spec 阶段就把业务流程理解错了,那它后面写出来的代码肯定也是错的。与其等它改完几十个文件后再返工,不如一开始就在文档阶段把方向纠正掉。

第二,可以减少上下文丢失。

AI 的上下文再大,也不是无限的。一次需求可能要分几天做,中间可能还会切换工具、切换会话。如果核心信息只存在于聊天记录里,后面很容易丢。Spec 放在项目里后,AI 后续可以重新读取这些文档,继续按原来的设计往下做。

第三,可以让协作更清楚。

以前我们做开发,很多信息存在人的脑子里:这块为什么这么设计、当时为什么不走另一条路、哪些边界情况已经考虑过。Spec 把这些东西写下来后,不只是 AI 能看,人也能看。后面别人接手这个需求,也能更快理解上下文。

第四,可以让 AI 不要乱改。

我会在项目规则里明确要求 AI:如果需求变化,先更新 Spec;如果代码和 Spec 不一致,先提示我确认;没有确认前,不要直接大面积改代码。

这点很重要。很多 AI 工具执行力很强,但执行力太强也会带来问题。它可能没完全理解你的意思,就已经开始改代码了。Spec 相当于在需求和代码之间加了一道缓冲层。

我是怎么做 Spec 驱动开发的

我的做法大概分成几步。

1、通过 LeanSpec 管理 Spec

我现在主要是通过 LeanSpec 这个工具来落地 Spec 驱动开发。它可以理解为一个围绕 Spec 的命令行工具,帮你在项目里初始化 Spec 目录、创建 Spec、更新 Spec,并给 AI Agent 准备一套围绕 Spec 工作的规则。

当然,Spec 驱动开发的核心不是某个工具,而是「先把需求和方案写清楚,再让 AI 写代码」这个流程。没有 LeanSpec,手动维护 Markdown 文档也可以做。但有工具后,流程会更规范,AI 也更容易知道自己应该把需求写到哪里、应该按什么步骤推进。

我比较看重的是它能把 Spec 变成项目里的固定资产,而不是散落在聊天记录里的临时上下文。这样后面继续开发同一个需求,或者过一段时间再回来修改,AI 都可以重新读取这些 Spec,而不是完全依赖当时那一轮对话。

2、给 AI 准备项目级规则

现在大多数 Agent 编程工具都支持项目级说明文件,比如 AGENTS.mdCLAUDE.md 之类。这个文件可以理解为项目里给 AI 的开发规范。

我会在里面写清楚几类规则:

  • 需求来了以后,先检查是否已有对应 Spec
  • 如果没有 Spec,先创建 Spec
  • 如果已有 Spec,先更新 Spec,再改代码
  • 写代码前要先说明实现方案
  • 改代码后要同步更新测试
  • 修改完成后要执行对应的验证命令

这些规则不一定复杂,但要稳定。因为 AI 每次进项目都会先读这些规则,它就知道这个项目不是随便改代码,而是要按流程来。

3、从 PRD 或需求描述生成技术方案

正常情况下,我们还是会有 PRD、会议纪要、用户故事或者测试同学整理的场景。AI 可以基于这些材料先生成一份技术方案。

这里需要注意一点:PRD 是给人看的,Spec 是给 AI 和开发看的。两者可以有关联,但不一定是同一个东西。

PRD 更关注用户要什么,Spec 更关注系统怎么实现。比如一个需求在 PRD 里可能写的是「用户可以批量分享客户」,但在 Spec 里就要拆得更细:

  • 入口在哪里
  • 权限怎么判断
  • 分享对象怎么筛选
  • 重复分享怎么处理
  • 失败后是否回滚
  • 接口入参和出参是什么
  • 数据库状态怎么变化

这些内容如果不写清楚,AI 就会自己猜。它猜对了是运气,猜错了就是 bug。

4、把大方案拆成接口级或模块级 Spec

一个复杂需求不要只写一份很大的 Spec。我现在更倾向于分两类:

第一类是整体技术方案,主要讲业务流程、数据流、模块关系、整体改造思路。

第二类是接口级或模块级 Spec,讲某个接口、某个任务、某个页面或某个服务具体怎么实现。

这样做的好处是粒度更清楚。整体方案负责方向,接口级 Spec 负责落地。AI 写代码时也更容易根据具体 Spec 去实现,而不是在一大坨文档里找信息。

5、先人工检查 Spec,再让 AI 写代码

AI 参与开发后,不能只等它写完代码再 review。更好的方式是把检查前置,在 Spec 阶段先确认它对需求和方案的理解是否正确。

Spec 阶段要重点看这些问题:

  • AI 是否理解了真实业务意图
  • 有没有漏掉权限、数据状态、异常流程
  • 有没有和现有代码设计冲突
  • 有没有把简单问题复杂化
  • 有没有生成看起来很合理但实际项目里不可行的方案

这一步不能完全交给 AI。AI 可以帮你整理,但最后要不要这么做,还是开发者判断。

什么是自验证闭环

只让 AI 写代码还不够,还要让 AI 验证代码。

我说的自验证闭环,简单说就是:AI 写完代码后,根据预先定义好的验证标准,自己运行测试、检查结果、定位问题、再修改代码,直到验证通过。

这个过程类似这样:

  1. AI 根据 Spec 写代码
  2. AI 根据 Spec 写测试
  3. AI 执行测试或验证命令
  4. 如果失败,AI 读取报错并修复
  5. 再次执行验证
  6. 直到测试通过,再把结果反馈给人

这就是一个闭环。

如果没有这个闭环,AI 写代码就像一个只负责交作业、不负责检查的人。它写完后把结果丢给你,剩下的问题都要你自己排查。这样虽然也能提高一点效率,但还没有真正释放 AI 的价值。

自验证的关键是要有明确标准

「验证」不是让 AI 自己说一句「我检查过了」。验证必须有客观标准。

对后端来说,最直接的标准就是测试用例。尤其是真实启动项目、真实调用接口、真实检查数据库状态的测试,而不是只 Mock 掉所有依赖的测试。

Mock 测试不是没有价值,但如果所有东西都 Mock 掉,AI 很容易写出一种「测试通过但功能没真跑过」的代码。它只是证明了 mock 出来的世界里逻辑成立,不代表真实系统里也成立。

所以我更喜欢让 AI 写接近真实调用链路的测试。例如:

  • 启动 Spring Boot 测试环境
  • 用类似 HTTP 请求的方式调用接口
  • 准备必要的测试数据
  • 校验接口返回值
  • 查询数据库确认状态变化
  • 覆盖正向、反向、边界场景

前端也一样。前端的自验证可以不是单元测试,也可以是浏览器自动化测试、组件测试、页面截图比对、表单交互验证等。重点不是形式,而是 AI 必须能自己执行一套明确的检查。

测试同学其实也可以参与进来。测试同学擅长设计正反例、边界值和业务场景,如果这些用例能在开发阶段就提供给 AI,AI 写出来的测试会更接近真实业务,开发阶段就能提前暴露很多问题。

我是怎么让 AI 做自验证的

我一般会把验证规则也写进项目级规则或 Spec 里。

比如后端项目里,我会告诉 AI:

  • 测试类命名要有固定后缀,方便和原有测试区分
  • 参考项目里已有的测试写法
  • 每个接口按测试组组织用例
  • 测试要检查真实数据库状态
  • 修改业务代码时,要同步修改或新增测试
  • 执行测试时,要使用项目指定的 JDK、Maven 配置和 Spring Profile
  • 测试失败后,不要直接跳过,要先分析失败原因

这些规则看起来比较琐碎,但对 AI 很有用。因为 AI 不知道你本地项目的特殊情况,比如这个项目要用 JDK 8,另一个项目要用 JDK 21;这个项目要指定 settings.xml 才能拉依赖;这个项目要用某个 profile 才能连到正确的测试数据库。

这些东西如果只靠人每次口头提醒,肯定会漏。写到规则里后,AI 每次执行时就能复用。

自验证闭环带来的变化

有了自验证闭环后,我感觉最大的变化是开发者从「手动验收每一处实现」变成「检查验证体系是否可靠」。

以前 AI 写完代码,我要自己启动项目、自己调接口、自己看数据库、自己判断有没有问题。现在很多重复动作可以让 AI 做。它写完代码后自己跑测试,失败了自己改,最后把执行了什么命令、哪些测试通过、还有哪些风险告诉我。

这样并不是说人就不用管了。恰恰相反,人要管更关键的地方:

  • 测试场景是否覆盖了真实业务
  • 测试数据是否可靠
  • 断言是否真的验证了核心逻辑
  • AI 有没有为了让测试通过而改坏业务逻辑
  • 是否还有需要人工验收的场景

也就是说,人从低价值的重复验证里抽出来,更多去做判断和把关。

适用场景和限制

Spec 驱动开发和自验证闭环不是所有场景都必须用。

如果只是一个很小的 bug,十分钟就能改完,也没有复杂业务影响,没必要写一大堆 Spec。直接让 AI 帮你定位和修改,再跑一下对应测试就可以了。

如果是从 0 到 1 做一个新东西,也不一定特别适合一开始就完整走这套流程。因为 0 到 1 阶段需求和方案往往还在不断变化,很多东西还没有定下来,这时候如果一开始就把 Spec 写得很细,后面可能会频繁推翻重写,反而增加额外成本。

它更适合这些场景:

  • 需求比较复杂,需要多轮开发
  • 涉及多个模块或多个接口
  • 后续还会持续迭代
  • 项目有一定历史包袱,容易改出回归问题
  • 团队希望沉淀 AI 可读的项目知识

它也有一些挑战。

第一,团队要愿意维护 Spec。如果只有一个人遵守,其他人都直接改代码,Spec 很快就会失真。

第二,项目规则要持续维护。AI 踩过的坑、项目里的特殊约束、测试命令、代码规范,都需要慢慢沉淀进去。

第三,测试环境要相对稳定。真实测试越接近业务,越依赖测试数据、数据库、配置和外部服务。如果环境不稳定,AI 自验证也会变得不稳定。

第四,PRD 本身也需要更清楚。很多需求文档是给人沟通用的,里面可能省略了大量线下讨论的信息。人能靠经验补全,AI 不一定能。所以未来如果想更好地用 AI 开发,PRD、用户故事、测试用例这些上游材料也要变得更结构化。

总结

我现在对 AI 编程的理解是:不要把 AI 当成一个只会写代码的工具,而是要把它放进一个有规则、有文档、有验证的开发流程里。

Spec 驱动开发解决的是「让 AI 知道要做什么、为什么这么做、怎么做」的问题。

自验证闭环解决的是「让 AI 证明自己做对了」的问题。

前者控制方向,后者控制质量。只有这两件事结合起来,AI 写代码才会从一次性的代码生成,变成相对稳定的工程协作。

当然,这套方式不是银弹,也不是用了以后人就不用写代码了。相反,它对开发者的要求更高了。你要能判断需求是否合理,能看懂方案是否靠谱,能设计出有效的验证标准,也要能识别 AI 为了通过测试而做出的错误修改。

AI 可以帮我们少写很多重复代码,少做很多重复验证,但它不能替我们承担工程判断。未来开发者的价值,可能会越来越多地体现在定义问题、制定规则、检查结果和控制风险上。

代码可以让 AI 多写一点,但方向和标准还是要掌握在开发者手里。

image.png