1. 先别急着关页面:SDD 到底卡在哪儿了
如果你点开这篇文章,大概率是因为搜过“SDD 是什么”“SDD 和 TDD 区别”“SDD 程序员怎么上手”这类问题,然后被一堆术语砸得有点懵。我先说结论:SDD(Specification-Driven Development,规格驱动开发)不是又一个新瓶装旧酒的缩写词,它真正想解决的问题,是“代码写完了才发现做错了东西”这个老毛病。你不需要先成为架构师才能理解它,你只需要承认一件事——大多数返工,不是因为程序员不会写代码,而是因为写代码之前没人把“要做什么”说清楚。
我见过太多团队,需求文档是一句话,设计文档是几张图,然后直接开干。等到提测那天,产品说“这不是我要的”,开发说“你当初就是这么说的”,测试说“我按文档测的没问题”。三方都没撒谎,问题出在中间那层“规格”从来没被认真对待过。SDD 就是把这一层拎出来,当成一等公民来对待:先写规格,再写代码,规格是唯一的事实来源。
这篇文章适合谁看?如果你是刚工作一两年的程序员,被各种开发方法论绕晕了;如果你是带小团队的技术负责人,想找一套能落地的流程而不是 PPT 上的口号;如果你只是好奇“AI 编程时代为什么又冒出来一个 SDD”,那这篇就是写给你的。我会用大白话把 SDD 拆开,讲清楚它的核心逻辑、和 TDD/DDD 的区别、实际怎么落地、以及我在真实项目里踩过的坑。不堆概念,不画大饼,只讲能用的东西。
2. 把 SDD 翻译成人话:规格不是文档,是契约
2.1 为什么“写文档”这三个字劝退了那么多人
一提到“先写规格”,很多程序员第一反应是抗拒的。原因很实在:过去十几年里,“写文档”这件事在大多数团队里就是走形式。需求文档写完没人看,设计文档写完就过期,接口文档靠 Swagger 自动生成但字段含义全靠猜。久而久之,大家形成了一个条件反射——文档等于浪费时间,代码才是真相。
这个反射不能怪程序员。它是在无数次“写了也没人维护”的教训里练出来的。所以当你听到 SDD 说“规格优先”的时候,本能地会觉得这又是哪个咨询公司编出来卖课的概念。
但 SDD 里的“规格”和你理解的“文档”有一个本质区别:文档是给人看的参考材料,规格是给人和机器共同执行的契约。参考材料可以模糊、可以过期、可以没人看;契约不行,契约一旦定下来,代码必须符合它,测试必须验证它,变更必须走流程。这个区别听起来只是措辞上的,但它决定了整套方法能不能落地。
打个比方。你装修房子,设计师给你画了一张效果图,这是文档——好看就行,施工队不一定完全照着做。但如果设计师给你出的是一份带尺寸、带材料标号、带水电点位的施工图,那就是规格——工人照着做,监理照着验,改一个插座位置都要重新出图。SDD 要的就是后面这种。
2.2 规格的最小可用形态:三件事说清楚就够了
很多人以为写规格就是要写几十页 Word,其实不是。一个能用的规格,最少只需要说清楚三件事:
- 输入是什么:这个功能接收什么数据、什么触发条件、什么前置状态。
- 输出是什么:成功时返回什么、失败时返回什么、边界情况怎么处理。
- 约束是什么:性能要求、安全要求、兼容性要求、不能碰的红线。
就这三件事。你把它写成表格也好,写成结构化文本也好,写成带注释的接口定义也好,形式不重要,重要的是这三件事必须明确到“没有第二种解释”的程度。
我举个真实的例子。之前有个需求叫“用户下单后发送通知”。这句话作为需求是合格的,作为规格是灾难。因为“通知”可以是短信、邮件、站内信、推送;“下单后”可以是支付成功后、订单创建后、库存扣减后;“发送”可以是同步、异步、重试三次。你不把这些说清楚,五个程序员能写出五种东西。
改成规格之后是这样的:
| 项目 | 内容 |
|---|---|
| 触发条件 | 订单状态变更为“已支付” |
| 通知渠道 | 站内信(主)+ 邮件(备) |
| 发送时机 | 异步,订单状态变更后 5 秒内 |
| 失败处理 | 重试 3 次,间隔 10 秒,仍失败则记录告警日志 |
| 不发送场景 | 用户已关闭通知开关、订单金额为 0 |
你看,还是那一页纸,但歧义消失了。这就是 SDD 说的“规格”。
2.3 规格和代码谁先谁后:一个容易被绕晕的问题
有人会问:那是不是必须先写完所有规格才能写代码?不是。SDD 不要求你一次性把整个系统的规格写完,它要求的是在你动手写某一块代码之前,先把这一块的规格定下来。粒度可以很细,细到一个函数、一个接口、一个状态机。
这就像盖楼,你不需要把整栋楼的施工图全部画完才开始打地基,但你要打地基的那块地方,图纸必须先有。SDD 反对的是“边写边想”,支持的是“想清楚一块写一块”。
这里有个实操上的判断标准:如果你发现自己写代码的时候在纠结“这里到底该返回什么”,说明规格没写够。纠结的那一刻,就是规格缺失的信号。停下来,把规格补上,再继续写。这个习惯养成了,返工率会肉眼可见地下降。
3. SDD、TDD、DDD 到底是不是一家人
3.1 一张表看清三个缩写的分工
这三个词经常被放在一起比较,但它们其实不在一个层面上。我用一张表说清楚:
| 维度 | TDD | DDD | SDD |
|---|---|---|---|
| 全称 | 测试驱动开发 | 领域驱动开发 | 规格驱动开发 |
| 关注点 | 代码正确性 | 业务建模 | 需求到实现的契约 |
| 核心动作 | 先写测试再写代码 | 先划分领域再写代码 | 先写规格再写代码 |
| 产出物 | 测试用例 | 领域模型 | 规格说明 |
| 解决的问题 | 代码有没有写对 | 业务有没有建对 | 东西有没有做对 |
| 适用场景 | 逻辑复杂的模块 | 业务复杂的系统 | 协作复杂的需求 |
看明白了吗?TDD 管的是“实现层”,DDD 管的是“建模层”,SDD 管的是“需求层”。它们不是互相替代的关系,而是可以叠加的。一个成熟的团队完全可以先用 SDD 把需求变成规格,再用 DDD 把规格变成领域模型,最后用 TDD 把模型变成有测试保障的代码。
3.2 为什么 SDD 在 AI 编程时代突然火了
这两年 AI 编程工具爆发,Cursor、Windsurf、Copilot、各种 Agent 满天飞。很多人发现一个尴尬的事实:AI 写代码很快,但 AI 不知道你要什么。你给它一句模糊的需求,它给你一段能跑但不对的代码;你给它一段详细的规格,它给你的代码质量立刻上一个台阶。
这就是 SDD 重新被提起的直接原因。AI 编程的瓶颈从来不是“写代码的速度”,而是“表达需求的精度”。SDD 恰好就是训练你表达需求精度的工具。你写规格的能力越强,AI 帮你写代码的效率越高。反过来,你规格写得含糊,AI 只会把你的含糊放大成 bug。
我自己的体会是:用 AI 编程工具之前,先花十分钟把规格写清楚,比事后花一小时 debug 划算得多。这十分钟不是浪费,是投资。而且规格写多了之后,你会发现自己对需求的理解速度也变快了,因为你的大脑已经习惯了“输入-输出-约束”这个思考框架。
3.3 别把 SDD 当成宗教:它只是一个工具
我特别怕看到有人把 SDD 当成信仰,逢人就说“你不写规格就是不合格的程序员”。这就走极端了。SDD 是一个工具,工具有适用场景,也有不适用场景。
适合用 SDD 的场景:多人协作、需求频繁变更、对接方多、AI 辅助编程、长期维护的项目。
不太需要 SDD 的场景:一次性脚本、个人小工具、探索性原型、需求极其稳定的老系统维护。
判断标准很简单:如果“做错了”的代价大于“写规格”的成本,就值得写规格。反过来,如果改一行代码就能试错,那直接试比写规格快。工具是拿来用的,不是拿来供的。
4. 落地 SDD 的完整路径:从一句话需求到可执行规格
4.1 第一步:把需求拆成“可验证的句子”
需求文档里最常见的一句话是“系统要支持用户管理”。这句话没法验证,因为“支持”是个模糊动词。SDD 的第一步,就是把它拆成可验证的句子。
拆法是这样的:把每个模糊动词换成具体动作,把每个名词换成具体对象,把每个形容词换成具体数值。
“系统要支持用户管理”拆完变成:
- 管理员可以创建用户,必填字段为用户名、邮箱、初始角色。
- 管理员可以禁用用户,禁用后该用户无法登录。
- 管理员可以重置用户密码,重置后系统生成临时密码并通过邮件发送。
- 普通用户只能查看和修改自己的信息,不能查看他人信息。
每一句都能写测试,每一句都能判断对错。这就是“可验证”。
提示:拆需求的时候,遇到“等等”“类似”“其他”这类词要特别警惕。这些词背后往往藏着没想清楚的需求,不拆出来就是定时炸弹。
4.2 第二步:给每条规格定义验收标准
规格写完了,怎么知道代码有没有符合规格?靠验收标准。验收标准就是“满足什么条件算通过”。
继续用上面的例子。“管理员可以创建用户”这条规格,验收标准可以写成:
- 提交合法数据时,用户创建成功,返回用户 ID。
- 用户名重复时,返回错误码 409,提示“用户名已存在”。
- 邮箱格式非法时,返回错误码 400,提示“邮箱格式不正确”。
- 缺少必填字段时,返回错误码 400,并列出缺失字段。
- 创建成功后,用户状态默认为“启用”。
这五条写出来,开发知道怎么写,测试知道怎么测,AI 知道怎么生成代码。三方对齐,返工概率大幅下降。
这里有个经验:验收标准要写成“给定-当-则”的结构。给定什么前置条件,当发生什么操作,则得到什么结果。这个结构强迫你把边界情况想清楚,而不是只写 happy path。
4.3 第三步:把规格变成代码骨架
规格和验收标准都有了,接下来才是写代码。但 SDD 建议你不要一上来就写业务逻辑,而是先把骨架搭出来。
骨架是什么?就是函数签名、接口定义、数据结构、状态枚举。这些东西直接对应规格里的输入输出和约束。
比如“创建用户”这条规格,骨架可以是:
class CreateUserRequest: username: str email: str role: str class CreateUserResponse: user_id: str status: str def create_user(request: CreateUserRequest) -> CreateUserResponse: # 规格:校验用户名唯一性 # 规格:校验邮箱格式 # 规格:校验必填字段 # 规格:创建用户,状态默认启用 pass你看,函数体还是空的,但规格已经以注释的形式嵌进去了。接下来无论你自己写实现,还是让 AI 补全,都有明确的靶子。这就是 SDD 说的“规格驱动”——规格不是写完就扔的文档,而是贯穿到代码里的骨架。
4.4 第四步:用规格反推测试用例
骨架搭完,测试用例其实已经呼之欲出了。因为验收标准本身就是测试用例的雏形。
“用户名重复时返回 409”这条验收标准,直接翻译成测试就是:
def test_create_user_duplicate_username(): # 给定:已存在用户名为 "alice" 的用户 create_user(CreateUserRequest("alice", "a@example.com", "admin")) # 当:再次创建用户名为 "alice" 的用户 result = create_user(CreateUserRequest("alice", "b@example.com", "user")) # 则:返回 409 错误 assert result.error_code == 409这就是 SDD 和 TDD 的衔接点。SDD 负责把需求变成规格,TDD 负责把规格变成测试。两者配合,代码质量有双重保障。
4.5 第五步:规格变更管理
规格不是刻在石头上的。需求变了,规格就要变。但 SDD 要求规格变更必须显式、可追溯。
什么叫显式?就是不能偷偷改代码不改规格。代码和规格不一致的时候,以规格为准,要么改代码,要么改规格,不能两边各说各话。
什么叫可追溯?就是每次规格变更都要记录:谁改的、为什么改、影响哪些代码、影响哪些测试。这个记录不需要多正式,一个变更日志文件就够了。
我见过最离谱的情况是:规格文档还是三个月前的版本,代码已经改了八轮,测试用例还是按老规格写的。这种状态下,规格不但没起到契约作用,反而成了误导。所以规格变更管理不是可选项,是必选项。
5. 真实项目里的 SDD:一个订单模块的完整拆解
5.1 项目背景和初始需求
假设你接到一个需求:给电商系统加一个“订单取消”功能。原始需求描述只有一句话:“用户可以取消未发货的订单。”
这句话如果直接开干,你会遇到一堆问题:什么叫“未发货”?已支付未发货能取消吗?取消后钱怎么退?部分商品已发货怎么办?取消次数有限制吗?取消后库存怎么处理?
用 SDD 的方式,第一步就是把这些问号变成规格。
5.2 规格拆解过程
我先列出所有需要明确的点,然后逐个定义:
状态定义:订单状态分为待支付、已支付待发货、已发货、已完成、已取消。只有“已支付待发货”状态可以取消。
取消条件:订单未发货、订单未完成、订单未取消、用户是订单所有者。
退款规则:取消成功后,支付金额原路退回,退款到账时间以支付渠道为准。
库存处理:取消成功后,订单内商品库存加回。
取消次数:同一订单只能取消一次。
并发处理:同一订单同时发起多次取消请求,只有第一次成功,其余返回“订单已取消”。
把这些写成规格表:
| 规格项 | 定义 |
|---|---|
| 可取消状态 | 已支付待发货 |
| 取消发起人 | 订单所有者 |
| 退款方式 | 原路退回 |
| 库存处理 | 加回可用库存 |
| 幂等性 | 同一订单取消操作幂等 |
| 并发控制 | 乐观锁,版本号校验 |
5.3 从规格到代码的映射
规格定完,代码结构基本就出来了。你需要:
- 一个状态机,管理订单状态流转。
- 一个取消服务,处理取消逻辑。
- 一个退款服务,处理退款。
- 一个库存服务,处理库存加回。
- 一个幂等控制,防止重复取消。
每个服务对应一条或几条规格。写代码的时候,你不需要再想“这里该做什么”,因为规格已经告诉你了。你只需要想“怎么做得更好”。
这就是 SDD 带来的心智负担转移:把“想做什么”和“怎么做”分开。想做什么在规格阶段解决,怎么做在编码阶段解决。两个阶段混在一起,就是 bug 的温床。
5.4 踩坑记录:规格写太细和写太粗都出事
我在实际项目里踩过两个方向的坑。
第一个坑是规格写太细。有一次我把一个接口的规格写到了字段级别,连每个字段的长度限制、正则表达式都写进去了。结果需求一变,规格要改,代码要改,测试要改,三处同步改,累得半死。后来我学乖了:规格写到“行为”层面就够了,不要写到“实现”层面。字段长度这种细节,放在代码里定义,规格里只说“用户名长度符合系统约束”。
第二个坑是规格写太粗。另一个项目里,规格只写了“取消订单要退款”,没写退款失败怎么办。结果上线后遇到支付渠道超时,退款失败,订单状态卡在中间,用户既没收到退款也不能重新取消。后来补了规格:退款失败时订单状态置为“取消失败”,允许用户重试,并记录告警。
这两个坑的教训是一样的:规格的粒度要刚好覆盖“会出错的边界”,不多不少。怎么判断?问自己一句:如果这里出错了,代码知道怎么处理吗?不知道,就是规格没写够。
6. 让 AI 编程工具真正听懂你:SDD 提示词实战
6.1 为什么直接让 AI 写代码效果差
很多人用 AI 编程工具的方式是:打开对话框,输入“帮我写一个订单取消功能”,然后期待 AI 给出完美代码。结果往往是一段能跑但不符合业务逻辑的代码,然后你开始一轮一轮地改,改到最后发现还不如自己写。
问题不在 AI,在输入。AI 不是读心术,它只能根据你给的信息推理。你给的信息越模糊,它的推理空间越大,跑偏的概率越高。SDD 提供的规格,恰好就是 AI 最需要的输入。
6.2 把规格改写成 AI 能吃的提示词
规格是给人看的,提示词是给 AI 看的。两者内容一样,但表达方式要调整。给 AI 的提示词要更结构化、更明确、更少歧义。
一个基于规格的提示词模板:
角色:你是一名后端工程师,使用 Python 和 FastAPI。 任务:实现订单取消接口。 规格: 1. 接口路径:POST /orders/{order_id}/cancel 2. 前置条件:订单状态为“已支付待发货”,发起人为订单所有者 3. 成功行为:订单状态改为“已取消”,触发退款,库存加回 4. 失败行为: - 订单不存在:返回 404 - 订单状态不允许取消:返回 409,提示“当前状态不可取消” - 非订单所有者:返回 403 5. 幂等性:同一订单重复取消,第二次返回成功但不重复退款 6. 并发控制:使用乐观锁,版本号不匹配时返回 409 约束: - 退款和库存操作必须异步执行 - 所有状态变更必须记录日志 - 不允许在接口层直接操作数据库 请先输出接口定义和数据结构,等我确认后再输出实现。这个提示词和直接说“帮我写订单取消”的区别,就像施工图和口头描述的区别。AI 拿到这个提示词,输出的代码质量会稳定得多。
6.3 用规格约束 AI 的“自由发挥”
AI 有个特点:你不管的地方,它会自己补。补得好是惊喜,补得不好是惊吓。SDD 的作用就是划定边界,告诉 AI 哪些地方可以发挥,哪些地方必须按规格来。
我的做法是:在提示词里明确写“不允许”清单。比如“不允许引入新的第三方库”“不允许修改现有接口签名”“不允许在业务逻辑里写 SQL”。这些约束写进去,AI 就不会乱来。
还有一个技巧:让 AI 先输出规格理解,再输出代码。你可以这样问:“请先用你自己的话复述一遍你对规格的理解,确认无误后再写代码。”这一步能提前发现 AI 的理解偏差,避免写完一大段代码才发现方向错了。
6.4 实测对比:有规格和没规格的差距
我在同一个需求上做过对比测试。需求是“实现一个带重试的 HTTP 请求封装”。
没规格的提示词:“帮我写一个带重试的 HTTP 请求封装。”AI 给了一个基础版本,重试三次,固定间隔,没有区分错误类型,没有超时控制。
有规格的提示词:写清楚了重试次数、重试间隔策略(指数退避)、哪些错误码重试、哪些不重试、超时时间、最大重试总时长、日志要求。AI 给的版本直接可用,只需要微调。
两次对比,有规格的版本节省了大约 70% 的调试时间。这个投入产出比,值得每个用 AI 编程的人认真对待。
7. 团队推行 SDD 时最容易翻车的几个地方
7.1 把规格写成没人看的八股文
最常见的翻车方式,是把 SDD 执行成了“写文档运动”。规格写得又长又正式,格式漂亮,但没人看,没人维护,写完就归档。这种规格还不如不写,因为它给了团队一种“我们在做规范”的错觉,实际上什么都没改变。
避免这个坑的关键是:规格要短、要活、要跟代码在一起。短,是指一页纸能说完;活,是指随需求更新;跟代码在一起,是指放在代码仓库里,而不是放在某个文档系统里吃灰。
我推荐的做法是把规格写成 Markdown 文件,和代码放在同一个仓库,用 Pull Request 管理变更。这样规格变更和代码变更在同一个流程里,不会脱节。
7.2 规格和代码不一致时没人管
第二个坑是规格和代码各走各的。代码改了,规格没改;规格改了,代码没改。时间一长,没人知道哪个是对的。
解决办法是加一道检查:在代码审查清单里加一条“规格是否同步更新”。如果代码变更涉及行为变化,规格必须同步改。这条规则执行几次之后,就会变成肌肉记忆。
更严格的做法是用工具做一致性检查。比如接口规格可以用 OpenAPI 描述,代码实现必须通过 OpenAPI 校验。这样规格和代码不一致的时候,CI 直接报错,想偷懒都偷不了。
7.3 规格粒度失控:有人写太细有人写太粗
团队里每个人对“规格”的理解不一样,有人写得像需求文档,有人写得像设计文档,有人写得像测试用例。粒度不统一,协作就乱。
我的建议是给团队定一个规格模板,明确哪些必须写、哪些不用写。模板不用复杂,包含这几块就够了:
- 功能描述(一句话)
- 输入输出(表格)
- 边界条件(列表)
- 验收标准(给定-当-则)
- 变更记录(日期+变更内容)
模板定下来之后,新写的规格都按这个来,慢慢就统一了。
7.4 把 SDD 当成考核指标
最怕的是管理层把“规格覆盖率”当成 KPI。一旦变成考核,大家就会为了写规格而写规格,质量反而下降。
SDD 的价值在于减少返工,不在于产出多少文档。衡量 SDD 是否有效的指标应该是:返工率有没有下降、需求理解偏差有没有减少、AI 生成代码的可用率有没有提升。这些才是真正有意义的指标。
8. 我个人的 SDD 实践心得
8.1 规格写不出来,往往是需求没想清楚
这是我最大的体会。很多时候我坐下来准备写规格,写着写着发现写不下去,不是因为不会写,是因为需求本身有矛盾或者有空白。规格就像一面镜子,照出需求的漏洞。
所以我现在把写规格当成需求评审的一部分。规格写不顺的地方,就是需要找产品对齐的地方。与其等到代码写完才发现问题,不如在规格阶段就把问题暴露出来。
8.2 规格是给未来的自己写的
很多人觉得写规格是给别人看的,其实最大的受益者是三个月后的自己。三个月后你回头看代码,如果没有规格,你得重新读一遍代码才能想起当初为什么这么写。如果有规格,五分钟就能恢复上下文。
我现在维护的项目,每个模块目录下都有一个SPEC.md。新同事入职,先读规格再读代码,上手速度快很多。这个习惯坚持下来,收益是复利的。
8.3 从最小的模块开始试
如果你之前没接触过 SDD,不要一上来就在核心项目上全面推行。找一个边缘模块,或者一个新启动的小功能,先试一个月。试的过程中记录哪些地方有用、哪些地方多余,然后调整。
SDD 不是一套必须完整执行的仪式,它是一组可以按需取用的实践。你可以只用它的“验收标准”部分,也可以只用它的“规格提示词”部分。怎么用取决于你的场景,没有标准答案。
8.4 一个实用的小技巧:规格先行五分钟
最后分享一个我每天都在用的小技巧。每次开始写一个新功能之前,先花五分钟写一段规格,不用很正式,就用大白话写清楚“这个功能接收什么、返回什么、出错怎么办”。五分钟写不完,说明这个功能比你想的复杂,需要更多时间想清楚。
这五分钟的投入,通常能省下半小时以上的调试时间。而且写多了之后,你会发现自己对需求的敏感度提高了,很多以前会忽略的边界情况,现在一眼就能看出来。
这个技巧不需要任何工具,不需要团队配合,今天就能开始用。如果你觉得 SDD 这个概念太大不好落地,那就从这个五分钟开始。