从 2025 年下半年开始,我陆续在真实项目里实践 SDD(Spec-Driven Development,规格驱动开发)。其中有两个很有代表性的项目:一个是把历史悠久(Tornado + Jinja2)的某历史平台重构到当前技术栈,另一个是开发面向非研发同学、承载多种业务需求的某新平台。

这两个项目恰好代表了两种完全不同的开发场景:某历史平台是典型的棕地项目,已有业务复杂、历史约束很多,重构时最重要的是不要破坏原有逻辑;某新平台则更接近绿地项目,需要从零开始梳理需求、设计边界,并控制项目在快速演进中的复杂度。

SDD 确实解决了过去使用 AI 开发时遇到的许多问题。但随着实践深入,我也逐渐意识到:SDD 并不是写几份 spec、plan 和 tasks 文档就结束了。它真正要解决的,是如何让 AI 在长时间、跨会话、复杂代码库的开发过程中,始终理解我们的意图、遵守系统约束,并且能够证明自己的工作是正确的。

这篇文章想结合两个项目的实践,聊一聊我对 SDD 的理解、它目前的局限,以及它接下来可能演进的方向。

本文所说的 SDD,是一种以可验证规格作为开发主线的工作方式。规格不只描述需求,还应包含业务约束、非目标、验收条件和验证方式,并持续参与任务拆分、实现、审查与交付。它与 TDD、BDD、ADR/RFC、API-first 等方法并不冲突:这些方法分别提供行为验证、决策记录和接口契约,SDD 则负责把它们组织到同一条从意图到交付的主线上。

SDD 之前:模型上下文窗口与任务拆解

在 Vibe Coding 刚开始流行时,我们通常通过 Cursor 这类编程工具直接描述需求,让 AI 修改代码。对于局部、边界清晰、业务耦合较低的工作,这种方式非常有效。

一个典型案例是某数据管理系统的 ORM 改造。该系统最初没有在代码中定义数据模型,ORM 改造阶段需要将 60 多张存量表转换成 ORM 模型。每个模型都必须严格符合现有表结构,包括字段、索引和类型;某些特殊字段还需要额外引入 SQLAlchemy 扩展模块。

如果完全由人工完成,这项工作大约需要两天,而且非常容易出错。一个 varchar 长度的细微偏差,就可能导致 Alembic 迁移持续告警。借助 AI,我们在半天内完成了模型改造,同时还修复了测试环境与正式环境长期依靠人工同步而产生的表结构差异。

这次实践让我看到了 AI 最擅长的一类任务:边界明确、规格清晰、结果可验证的重复性工作。只要能够明确告诉 AI 输入是什么、输出应该满足什么条件,它就可以显著降低重复劳动和人为误差。

从一次重构开始遇到问题

在 ORM 改造取得不错效果后,我开始尝试让 AI 完成更“重”的工作,例如重构某历史平台。

该平台基于 Tornado 和 Jinja 实现,包含多个仍在使用的人工工具。它的整体架构较老,无论接入新功能还是维护旧功能,都需要投入不少人力。很多历史业务逻辑甚至需要先借助 AI 梳理,开发者才能理解其设计意图。

第一次重构时,我选择了当时上下文窗口很大的 Gemini 2.5。理论上,它足以容纳该项目,但实际生成的代码在编程质量上存在明显问题,许多功能无法正常走通。

随后我改用 Claude 4.5。它生成的项目基本可以运行,但部分特殊业务逻辑被省略或改写了。模型可能在一轮轮上下文压缩中丢失了细节,又把某些看起来不合理、实际上承担兼容职责的逻辑当作 bug,于是进行了自由发挥。

后来我也尝试过 GPT、Kimi 等模型。整体来看,模型能力当然很重要,但真正限制大型重构效果的,并不只是模型够不够聪明,而是它能否在漫长的任务中持续记住业务边界和历史约束。

最终,我把五个功能模块拆开,让 AI 一次只重构一个模块,使每个任务都处于更合适的上下文范围内。同时,用一份 tasks 文档记录目标、边界和完成状态,保证多个 session 之间仍然沿着同一条主线推进。

这就是当时比较流行的 plan-tasks 模式,也可以看作 SDD 的雏形。最终,五个功能模块都被重构成基于 FastAPI 和新技术框架的后端项目,后来整合进某新平台。

SDD 到底解决了什么

在没有 SDD 的情况下,开发团队其实并非没有流程。只是需求、约束和决策通常分散在 issue、聊天记录、PR 评论、代码、测试以及开发者的记忆里。

这种方式在小需求里问题不大,但当 AI 开始承担更长、更复杂的任务时,隐式信息会迅速成为风险。AI 可能写出语法正确、测试通过的代码,却并不知道某段历史逻辑为什么不能删除,也不知道某个接口还被另一个系统依赖。

SDD 的核心,是把这些隐式信息转化成可以持续使用的工程工件,让规格不再只是实现前的说明,而是直接参与并约束实现过程。

一个完整但不必僵化的 SDD 流程,通常包括几类内容:

  • 原则与边界:项目必须遵守什么,哪些事情不能做。
  • 需求规格:要解决什么问题,为什么要解决,验收结果是什么。
  • 澄清记录:有哪些模糊点,最终做出了什么选择。
  • 技术方案:系统如何实现,涉及哪些模块和风险。
  • 任务拆分:如何把大目标拆成可执行、可检查的小步骤。
  • 实现与验证:执行任务,并通过测试、检查和运行结果验证实现。

这里最容易产生的误解,是把 SDD 当成“先写很多文档,再让 AI 编码”的瀑布流程。实际上,好的 SDD 必须是循环的:规格会在实现过程中暴露问题,技术方案会反过来修正需求边界,测试和线上反馈也会继续更新规格。

所以,SDD 的价值并不在于文档数量,而在于它是否让意图、约束、实现和验证形成了闭环。一个最小闭环可以表示为:

意图 → 规格 → 技术决策 → 任务 → 实现 → 验证证据 → 规格回写

在这个闭环中,“任务完成”不应只是 tasks 文件中的一个勾选状态,还应关联能够被复查的验证证据,例如测试结果、接口响应、页面截图、日志、指标或 trace。每个任务至少需要说明目标与非目标、影响范围、不可破坏的约束、验收条件、验证方式,以及出现什么情况时应该停止执行并升级给开发者处理。

棕地与绿地,需要不同的入口

两个项目的实践让我很明确地感受到,SDD 不应该只有一种模板。

对于某新平台这类绿地项目,适合从需求开始:先明确用户是谁、要解决什么问题、功能边界在哪里,再逐步形成技术方案和任务列表。它的重点是避免模糊需求在快速生成代码的过程中持续扩散。

对于某历史平台这类棕地项目,第一步通常不是重新描述“我们想做什么”,而是理解“现有系统有哪些东西必须保持不变”。因此,棕地项目更适合从技术设计、影响面和回归风险开始:先梳理调用链、兼容约束、历史逻辑和回滚方案,再决定如何改造。

简单来说:

  • 对绿地项目,先强化意图与节奏
  • 对棕地项目,先强化约束与验证

如果强行让所有项目都套用同一种 requirements-first 流程,结构化很容易变成额外的仪式

SDD 目前面临的困境

SDD 让复杂任务变得更有秩序,但它并没有自动解决 AI 开发中的所有问题。实践中,我认为当前最明显的困境主要有四类。

1. AI 难以可靠地证明影响面已经完整覆盖

即使有了清晰的 spec 和 tasks,AI 在大型、持续演进的仓库里依然可能存在上下文盲区。问题不只是模型能否读取完整个代码库:即使代码全部进入上下文,也不能证明所有静态依赖、运行时耦合和外部调用方都已经被识别。

对于某历史平台这类棕地项目,一个看起来只是 service 层抽取的改动,可能同时影响控制器、后台任务、配置、数据库访问、消息队列、埋点、监控、边界测试和外部系统。如果只依赖文本搜索和局部阅读,AI 很容易把“能改动的代码”误认为“可以安全改动的系统”。

规格能够告诉 AI 应该做什么,却不一定能告诉它代码库里所有会受影响的地方。要提高影响面分析的可信度,除了语义导航,还需要结合构建与依赖图、数据库和消息依赖、基础设施配置、模块所有权,以及 trace 等运行时信息。

2. 文档会膨胀,也会漂移

SDD 很容易走向另一个极端:为了让 AI 获得更多上下文,不断增加文档、规则和模板。最终,开发者和 AI 都需要在大量 Markdown 中寻找真正重要的信息。

更麻烦的是,规格与代码会一起变化。如果实现已经更新,但 spec 没有回写,下一次 AI 读取到的就是过期上下文。文档越多,维护一致性的成本越高,也越容易制造虚假的安全感。

因此,项目需要的不是一份巨大而完整的说明书,而是一个层次清楚、能够持续更新的知识结构。入口文档应该短小,只负责告诉 AI 去哪里寻找事实;具体设计、约束和运行手册则分层维护,并尽可能通过工具自动检查。

3. 验证经常落后于生成

AI 生成代码的速度已经很快,但验证能力通常没有同步跟上。

只运行单元测试并不足以证明一项功能真的完成。对于前后端应用,还需要验证页面交互、接口联调、日志、指标、异常路径和真实运行状态。对于棕地重构,还要确认旧功能没有被悄悄改变。

如果 AI 只能根据自己写的代码和自己补的测试宣布“任务完成”,它很容易在错误前提上自证正确。验证系统必须尽量独立,并且覆盖从静态检查、单元测试、集成测试到真实运行观测的不同层级。

对于棕地重构,可以先通过 characterization test 固化现有行为,再使用 differential test 对新旧实现输入相同请求并比较结果;对于关键输出,可以使用 golden 或 snapshot test。条件允许时,还可以结合流量回放、影子运行和灰度发布,观察真实环境中的差异。对于绿地项目,则应优先从需求和接口契约派生验收测试,避免测试只是在重复实现本身的假设。

4. 流程容易变得僵硬

不同项目、不同阶段需要的 SDD 深度并不相同。一个简单 bugfix 不需要完整经历需求、设计、任务拆分和多轮评审;一个高风险重构则不能只靠一句 prompt 和几条测试。

随着模型能力和开发工具不断进步,一些过去必须显式要求的动作,现在 AI 已经能够主动完成。例如,当项目拥有清晰的测试目录和运行方式时,AI 往往会自行补充并执行测试。

因此,SDD 不应该追求固定流程,而应该根据任务风险动态决定需要哪些工件、多少澄清以及多强的验证。

SDD(Or Vibe Coding)未来会如何强化

我越来越倾向于把可靠的 AI 开发看成一个完整系统,而不是“模型加 prompt”。这个系统至少需要五个相互配合的部分:

  • 意图层:通过规格、约束、验收标准和决策记录说明要做什么,以及哪些事情不能做。
  • 上下文层:通过文档入口、语义导航、依赖关系和运行时信息,让 AI 理解代码与系统边界。
  • 执行层:通过隔离环境、权限边界、任务状态和可恢复执行,让 AI 能够逐步完成工作。
  • 验证层:通过测试、浏览器、接口、日志、指标和验证证据检查结果。
  • 交付层:通过 PR 审查、灰度、部署、回滚与复盘,把结果反馈回规格和系统。

这五层并不是另一套需要完整照搬的流程,而是一张能力地图。不同风险的任务可以启用不同深度的能力,但任何任务都应能够回答三个问题:AI 依据什么做出改动、如何证明改动正确、出现问题时如何停止或恢复。

接下来真正值得投入的方向,不是把 spec 写得更厚,而是补齐 AI 的执行环境、代码库理解和验证能力。

Harness engineering:给 AI 一个可靠的工作环境

Harness engineering 可以理解为:不只优化 prompt,而是为 AI 建立一套能够可靠做事、能够恢复、能够验证、也能够回写结果的工程环境。

对于某历史平台这类棕地项目,harness 的首要价值是控制风险。每次改动应该在隔离环境中进行,有明确的任务范围、回归清单和停止条件;完成后,不仅要运行测试,还要检查关键接口、页面流程、日志和指标。出现异常时,系统应该能够快速定位变更并回滚。

对于某新平台这类绿地项目,harness 更像是控制开发节奏的工具。它帮助 AI 按小任务推进,持续记录完成状态,让每个功能都带着验收条件进入实现,避免一次生成过多代码后再集中修正。

一个实用的最小 harness 不需要从多代理系统开始。它可以只是:

  • 一份简短的项目入口说明,告诉 AI 如何寻找文档和运行项目。
  • 一套稳定的开发、测试、检查和构建命令。
  • 隔离的工作区与小批量提交方式。
  • 清晰的任务状态、验收清单和停止条件。
  • 能够读取页面、日志、指标或 trace 的验证工具。
  • 最小权限、密钥隔离、网络访问控制和操作审计。
  • 完成任务后更新规格和文档的回写机制。

先让单个 AI 在正确的环境和安全边界中稳定工作,通常比立即增加多个 agent 更有价值。

代码库强化:让 AI 真正理解仓库

规格描述的是人的意图,代码库表达的是系统的现实。要让两者真正连接起来,需要强化代码库提供上下文的方式。

文档应该成为系统记录,而不是说明附件

项目文档最重要的作用,不是重复描述所有代码,而是记录无法从代码可靠推导的信息:为什么这样设计、哪些业务约束不能突破、哪些模块由谁负责、如何运行和验证,以及出现问题时如何恢复。接口、类型、测试和构建配置等可执行事实,应尽量由机器可检查的工件表达,而不是只写在说明文档中。

入口文档应该保持简短,把更具体的知识分散到架构说明、模块文档、运行手册和决策记录中。这样 AI 每次只需要读取与当前任务相关的内容,减少无关上下文对判断的干扰。

LSP:用语义导航替代高噪声搜索

文本搜索很有用,但它并不理解代码语义。相同的名称可能属于不同类型,动态引用可能无法通过简单搜索发现,复杂项目中的重命名也很容易漏改。

LSP 能够提供定义、引用、类型、诊断、调用层级和重命名等语义能力。对 AI 来说,这意味着它可以更准确地回答“这个符号在哪里定义”“谁在调用它”“改名会影响哪些地方”,而不是依赖高噪声的 grep 结果进行猜测。

LSP 的落地可以分三步:先让 AI 能够读取定义、引用和诊断;再补充 CI 侧的离线索引,让平台拥有更完整的仓库视角;最后才开放重命名等修改能力,并增加相应守卫。

这里的关键不是装上语言服务器就结束了。构建元数据、项目配置、索引刷新和响应速度都会直接影响语义结果是否可信,LSP 自身也需要被监控和验证。尤其在大量使用动态调用、反射或运行时配置的项目中,LSP 只能提供静态视角,仍然需要通过依赖分析和运行时观测补充验证。

一些来自实践的技巧

最后补充几个我在项目中体会比较明显的做法。

让 AI 能够访问版本匹配的接口契约

前后端不一定需要存放在同一个仓库,但在执行跨端开发任务时,AI 应当能够在同一任务上下文中访问版本匹配的接口契约。对于个人维护或协作边界简单的项目,将前后端放在同一个 workspace 并共享一套 spec,确实能够减少接口对接中的信息差。

对于多团队或多仓库项目,更可靠的方式是使用 OpenAPI、Protobuf、生成客户端或消费者驱动契约测试,把接口约束变成机器可检查的工件。模型能力可以降低对接错误率,但不能替代契约验证。

用 workspace 保持边界清晰

将不同任务放在独立 workspace 或 worktree 中,可以避免多个 AI 任务互相污染,也方便审查、比较和回滚。尤其在重构场景下,隔离环境能够让每一次尝试都保持清晰边界。

小任务不是把大任务机械切碎

好的任务拆分需要让每一步都能独立验证,并且能够为下一步提供稳定基础。如果一个任务完成后无法判断对错,它仍然只是一个缩小版的大任务。

任务应该尽量包含明确的输入、改动范围、验收条件和停止条件。这样即使跨越多个 session,AI 也不容易偏离主线。

事后问题排查

在大部分代码都是由 LLM 生成的情况下,我们当然希望出现问题之后,也能够让 LLM 自己去处理。但是线上报错与运行环境通常是本地编码工具无法触及和感知的,所以需要通过 MCP 工具作为桥梁。

这里推荐 Grafana MCP 与 Sentry MCP。具体实践可以参考:

结语

经过棕地与绿地项目的实践,我对 SDD 的判断是:它非常适合作为 AI 时代软件研发的意图与约束层,但它本身还不足以成为复杂代码库里的可靠生产方式。

SDD 能帮助我们把模糊需求变成可执行任务,把跨会话工作变得连续,也能明显改善大型任务中“越做越乱”的问题。但它的效果上限,最终取决于团队是否拥有清晰的版本控制纪律、小批量交付方式、可访问的仓库知识以及真实有效的验证闭环。

对棕地项目,先强化约束与验证;对绿地项目,先强化意图与节奏;对团队整体,先强化语义与上下文,再强化自动执行与平台能力。

如果只能先做一件事,不必急着把 spec 模板写得更长。先选择一条最容易出事故的改动链路,把澄清、任务拆分、语义导航、回归验证、验证证据和结果回写连接起来。真正重要的不是规格描述得多么完整,而是系统能否证明实现遵守了规格。只有这样,SDD 才会从“看起来更有方法”,真正变成“实际上更能交付”。