1. “Vibe Coding”不是风格,是失控的信号灯
最近在好几个技术协作群里,看到新人提交的 PR 里夹着一段“很 vibe”的代码:函数名叫doTheThing(),注释写的是// this is magic, don’t touch,三处重复逻辑被复制粘贴后只改了变量名,但类型校验全靠any和// @ts-ignore硬扛。有人还发截图炫耀:“AI 一气呵成,连测试都没跑,直接上线!”——结果第二天凌晨三点告警炸了,数据库连接池耗尽,日志里全是Cannot read property 'data' of undefined。这不是酷,这是把生产环境当沙盒玩。
“Vibe Coding”这个词,表面看是种轻松随意的开发氛围,实则暴露了一个被长期忽视的系统性缺口:当 AI 编程工具从“辅助打字机”升级为“逻辑生成器”,开发者却没同步建立与之匹配的约束机制。它不是指用 VS Code 写代码时放点爵士乐,而是指一种缺乏显式契约、无验证闭环、靠直觉和运气推进的开发状态。我去年带的一个 IoT 设备固件项目,团队初期沉迷“vibe 感”——用 Copilot 自动生成 Modbus 协议解析器,结果生成的 CRC 校验逻辑在 0x8000 以上地址段会溢出,设备批量离线三天才定位到问题。不是 AI 不行,是我们没给它划清边界。
规范驱动开发(SDD)恰恰是对这种失控的反制。它不反对 AI,而是把 AI 放进一个可审计、可验证、可回溯的轨道里。SDD 的核心不是写更多文档,而是让所有关键决策——接口定义、状态流转、错误码范围、数据格式约束——都以机器可读的形式前置固化,并成为后续所有生成、校验、测试环节的唯一权威源。它不是给程序员加枷锁,而是给 AI 装上导航仪。你不会让自动驾驶汽车在没有高精地图的情况下上高速,为什么敢让 AI 在没有明确规范的前提下生成核心业务逻辑?
这个转变背后,是开发范式的代际迁移:从“人脑即规范”(靠经验、靠口头约定、靠代码注释暗示),转向“规范即中枢”(所有参与者——人、AI、CI、测试框架——都对同一份结构化契约达成共识)。关键词里的 “MonkeyCode” 并非某个具体产品,而是 SDD 实践中一个关键隐喻:代码不再是规范的载体,而是规范的衍生物;真正的“源代码”是那份被版本控制、被自动化校验、被团队共同演进的规范定义文件。当你开始用 OpenAPI 3.1 描述接口、用 JSON Schema 定义数据流、用 State Machine DSL 声明状态跃迁时,你写的.ts或.py文件,本质上只是规范的“编译产物”。
提示:SDD 不是要求你先写 200 页 Word 文档再动手。它的最小可行单元,可能只是一个带
required字段和enum枚举值的 YAML 片段,但这个片段必须被 CI 流水线自动拉取、解析、并用于生成类型定义和 mock 数据——否则,它就只是另一个没人看的 Wiki 页面。
2. SDD 的真实落地路径:从“规范即文档”到“规范即引擎”
很多人第一次接触 SDD,会下意识把它等同于“更严格的 API 文档”。这就像把 Git 当成高级 U 盘——只用了它最表层的功能。真正的 SDD 实践,是让规范文件(我们暂且叫它spec.yaml)成为整个开发流水线的“心脏起搏器”,每一次跳动都驱动下游环节自动响应。下面是我过去三年在三个不同规模项目中验证过的、可立即复用的落地四步法,每一步都对应一个具体的技术锚点,而非抽象原则。
2.1 第一步:用 OpenAPI 3.1 锁死接口契约,拒绝“口头协议”
传统做法是后端写完接口,再补 Swagger 注释;或者前端凭感觉写调用逻辑,联调时才发现字段名大小写不一致。SDD 的起点,是让spec.yaml成为接口的唯一真相源。我们不用手写全部,而是用OpenAPI Generator 的openapi-generator-cli工具链,配合一个极简的初始模板:
# spec.yaml openapi: 3.1.0 info: title: Device Management API version: 1.0.0 paths: /devices/{id}: get: operationId: getDeviceById parameters: - name: id in: path required: true schema: type: string pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' # 强制 UUID 格式 responses: '200': description: Device details content: application/json: schema: $ref: '#/components/schemas/Device' components: schemas: Device: type: object required: [id, name, status] properties: id: type: string format: uuid name: type: string minLength: 1 maxLength: 64 status: type: string enum: [online, offline, maintenance] # 枚举值强制约束关键不是这个 YAML 多漂亮,而是它如何被激活:
- CI 阶段:用
openapi-diff工具比对新旧spec.yaml,自动检测破坏性变更(如删除必填字段、修改枚举值),失败则阻断合并。 - 开发阶段:运行
openapi-generator generate -i spec.yaml -g typescript-axios -o ./src/generated,自动生成强类型 API Client 和 DTO 接口。前端工程师从此不再手动写interface Device { id: string; name: string; },所有类型都来自spec.yaml。 - 测试阶段:用
prismmock启动基于spec.yaml的 Mock Server,前端在无后端依赖时即可完成完整联调。
我见过最典型的反例:某电商项目,后端同学在spec.yaml里定义price为number,但实际返回的是字符串"199.00"。因为没人强制校验,前端用parseInt()处理,结果遇到"199.99"就变成199。SDD 的解法很简单——在 CI 中加入spectral规则检查:if (response.body.price && typeof response.body.price !== 'number') fail()。规则写一次,所有接口自动受检。
2.2 第二步:用 JSON Schema 管控数据流,终结“野数据”
API 接口只是入口,真正让系统崩溃的,往往是那些在服务间流转的“野生数据”。比如一个订单创建请求,前端传来的shipping_address对象,后端没做深度校验,直接存入数据库,结果某天用户输入了 5000 字的“详细地址”,触发 MySQLTEXT字段截断,后续物流系统解析失败。SDD 要求对每一个跨边界的数据结构,都用 JSON Schema 显式声明其形状与约束。
我们不把 Schema 写在代码里(那又成了“人脑即规范”),而是放在独立的schemas/目录下,例如order-create-request.json:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "required": ["user_id", "items"], "properties": { "user_id": { "type": "string", "pattern": "^U[0-9]{6}$" }, "items": { "type": "array", "minItems": 1, "maxItems": 100, "items": { "type": "object", "required": ["sku", "quantity"], "properties": { "sku": { "type": "string", "minLength": 5, "maxLength": 20 }, "quantity": { "type": "integer", "minimum": 1, "maximum": 999 } } } }, "shipping_address": { "$ref": "./address.json" } } }这个文件的价值,在于它能被多个工具消费:
- 运行时校验:在 Express/Koa 中间件里,用
ajv加载此 Schema,对每个请求体做实时校验。失败直接返回400 Bad Request及具体错误路径(如$.items[0].quantity: must be >= 1),而不是让错误穿透到业务逻辑层。 - 数据生成:用
json-schema-faker基于此 Schema 生成海量符合约束的测试数据,用于压力测试和异常场景覆盖。 - 文档联动:Swagger UI 自动将此 Schema 渲染为交互式请求示例,前端工程师点几下就能生成合法 payload。
注意:JSON Schema 的
pattern和enum是强约束,但description字段只是给人看的。SDD 要求所有pattern必须有对应正则表达式测试用例,所有enum必须在 CI 中用脚本扫描,确保后端代码里switch语句覆盖了全部枚举值——否则,enum就只是个装饰。
2.3 第三步:用 State Machine DSL 声明业务状态,消灭“幽灵状态”
订单有“待支付”、“已发货”、“已完成”等状态,但很多系统里,这些状态的流转逻辑散落在几十个if-else和switch里,甚至混在数据库更新 SQL 中。SDD 要求用领域专用语言(DSL)将状态机显式建模。我们选用轻量级的XState的machine定义语法,保存为machines/order-state.ts:
import { createMachine } from 'xstate'; export const orderMachine = createMachine({ id: 'order', initial: 'created', states: { created: { on: { PAY: 'paid', CANCEL: 'cancelled', } }, paid: { on: { SHIP: 'shipped', REFUND: 'refunded', } }, shipped: { on: { DELIVER: 'delivered', RETURN: 'returned', } }, delivered: { on: { REVIEW: 'reviewed', } }, // ... 其他状态 } });这个文件的作用远超“定义状态”:
- 可视化追踪:用
@xstate/viz工具,将此文件一键生成状态流转图,嵌入 Confluence,产品经理、测试、开发都能看到同一份状态图。 - 代码生成:用自研脚本解析此文件,自动生成 TypeScript 类型定义(
type OrderStatus = 'created' | 'paid' | ...)、状态转换校验函数(canTransition('paid', 'ship') === true)、以及单元测试骨架(覆盖所有on事件)。 - 运行时防护:在业务代码中,所有状态变更必须通过
orderMachine.transition(currentState, event)执行,该函数会严格校验event是否在当前currentState下被允许。试图从created状态直接DELIVER,会立刻抛出错误。
去年一个金融项目,风控规则要求“贷款申请在pending_review状态下,必须在 72 小时内进入approved或rejected”。我们就在pending_review状态的entryaction 中启动定时器,并在exitaction 中清除。这个逻辑不再藏在某个 Service 方法里,而是作为状态机的一部分,被所有人看见、被所有测试覆盖。
2.4 第四步:用 CI/CD 流水线固化规范执行,让“自动”成为默认
以上三步,如果只靠人工执行,迟早失效。SDD 的终极保障,是把所有校验、生成、测试环节,全部注入 CI/CD 流水线。我们使用 GitHub Actions,构建一个名为sdd-validate-and-generate的工作流:
name: SDD Pipeline on: pull_request: paths: - 'spec.yaml' - 'schemas/**/*.json' - 'machines/**/*.ts' jobs: validate-spec: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Validate OpenAPI spec run: npx openapi-validator spec.yaml - name: Check for breaking changes run: npx openapi-diff old-spec.yaml spec.yaml --fail-on=breaking generate-code: needs: validate-spec runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Generate API client run: npx openapi-generator-cli generate -i spec.yaml -g typescript-axios -o ./src/generated/api - name: Generate JSON Schema types run: npx quicktype --src-lang schema --lang typescript --out ./src/generated/schemas schemas/*.json test-state-machine: needs: generate-code runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run state machine tests run: npm test -- --testPathPattern=machines/这个流水线的意义在于:任何对规范文件的修改,都会触发全自动的契约校验、代码生成、和状态机测试。开发者无需记住“要跑什么命令”,因为“不跑就过不了 CI”。我们曾故意在spec.yaml中删掉一个required字段,推送后 CI 立刻失败,并给出清晰提示:“/devices/{id}/get响应缺少status字段,违反契约”。修复只需一行 YAML,而不是去翻查十几个文件找漏掉的字段。
3. 为什么 SDD 不是“反 AI”,而是给 AI 装上安全带
常有人质疑:“SDD 这么重,是不是在否定 AI 编程的效率?” 这是个根本性误解。SDD 和 AI 编程不是对立关系,而是互补的共生关系。把 AI 比作一辆高性能跑车,Vibe Coding 是蒙着眼睛踩油门,而 SDD 是给这辆车装上 GPS 导航、ABS 防抱死、和自动紧急制动系统。它不降低速度,而是确保速度用在正确方向上。
3.1 AI 的“幻觉”需要规范来锚定
AI 编程模型(如 Codex、Claude、DeepSeek-Coder)的核心能力是模式匹配与概率生成。它能写出语法正确的代码,但无法保证逻辑符合你的业务意图。例如,让它“实现一个用户登录接口”,它可能生成一个接受username和password的 POST 端点,但完全忽略你公司强制要求的双因素认证(2FA)流程、或密码强度策略(必须含大小写字母+数字+特殊字符)。SDD 的spec.yaml就是这份“业务意图”的数字化表达。当 AI 工具(如 Cursor 或 Windsurf)接入 SDD 工作流时,它不再凭空生成,而是基于spec.yaml中定义的securitySchemes和requestBody约束,生成符合 2FA 和密码策略的完整实现。
我们做过对比实验:同样需求“生成订单取消接口”,Vibe Coding 模式下,Copilot 生成的代码只处理了数据库更新,漏掉了向用户发送取消通知、释放库存、更新积分等关键步骤;而在 SDD 模式下,我们先在spec.yaml中为/orders/{id}/cancel添加x-business-rules扩展字段,明确列出必须触发的下游事件,再让 AI 生成。结果,AI 生成的代码不仅包含数据库操作,还自动引入了NotificationService和InventoryService的调用,并附上了对应的单元测试桩。
3.2 SDD 让 AI 的输出可验证、可追溯
Vibe Coding 下,AI 生成的代码像黑箱:你不知道它依据什么逻辑,也不知道它是否遗漏了边缘情况。SDD 则为 AI 输出建立了完整的验证链条:
- 输入可追溯:AI 生成的代码,其 prompt 中必须包含
spec.yaml的特定 commit hash 或版本号(如Based on spec v1.2.3 at commit abc123),确保生成依据可审计。 - 输出可校验:生成的代码必须通过
tsc --noEmit类型检查(类型来自spec.yaml生成),并通过eslint(规则集包含@typescript-eslint/no-explicit-any等 SDD 强制项)。 - 行为可验证:所有 AI 生成的业务逻辑,必须配套生成基于
spec.yaml的契约测试(Contract Test),验证其是否满足接口定义的所有responses和examples。
这意味着,当某天发现一个线上 Bug,你可以快速定位:是spec.yaml定义有歧义?是 AI 生成时理解错误?还是测试用例覆盖不足?而不是在几百行 AI 生成的代码里大海捞针。
3.3 SDD 降低 AI 的“认知负荷”,释放人类创造力
Vibe Coding 把开发者变成了“AI 指令调优师”:花大量时间调试 prompt,尝试不同温度值(temperature),反复让 AI 重写同一段逻辑。SDD 则把这部分认知负担卸载了。开发者只需专注两件事:
- 定义规范:用清晰、无歧义的语言,描述“系统应该做什么”。这本身就是一项高价值的设计活动。
- 审查与集成:审视 AI 生成的代码是否忠实实现了规范,是否符合团队工程标准(如日志规范、错误处理模式)。
我们团队有个真实案例:一个资深后端工程师,过去每天花 2 小时在 Copilot 的 prompt 上反复调试,只为生成一个符合内部 RPC 框架序列化要求的 DTO 类。实施 SDD 后,他把 RPC 框架的序列化约束(如@RpcField(order=1)注解规则、byte[]字段的 base64 编码要求)写入rpc-contract.jsonSchema,然后配置 AI 工具自动读取此 Schema。现在,他只需说“生成 UserDTO”,AI 就能输出完全合规的代码,他只需花 5 分钟做最终确认。省下的时间,他用来设计新的缓存淘汰策略,这才是他不可替代的价值。
提示:不要指望 AI 一次性生成完美代码。SDD 的最佳实践是“小步快跑”:先让 AI 基于
spec.yaml生成骨架(接口、DTO、空 Service 方法),再由开发者填充核心业务逻辑。AI 负责“结构”,人负责“灵魂”。
4. SDD 的实战陷阱与避坑指南:那些没人告诉你的细节
SDD 理念清晰,但落地过程布满深坑。我在三个团队推行时,踩过不少坑,也看到别人重复踩同样的坑。以下是最致命、也最容易被忽略的五个实战陷阱,附带可立即执行的解决方案。
4.1 陷阱一:规范文件沦为“静态快照”,与代码脱节
最常见的情况是:spec.yaml初期很规范,但随着项目迭代,后端同学悄悄改了数据库字段,却忘了更新spec.yaml;前端同学为了快速上线,绕过生成的 API Client,直接用fetch写硬编码 URL。久而久之,spec.yaml变成一份“历史文档”,没人信它,也没人维护它。
破局方案:双向绑定 + 自动化哨兵
- 双向绑定:在后端代码中,用
swagger-jsdoc或fastify-swagger等工具,从 JSDoc 或 Decorator 中提取接口信息,自动生成spec.yaml的增量 diff。CI 流水线强制要求:所有接口变更,必须通过openapi-diff检查,且spec.yaml的更新必须与代码变更在同一 commit 中。 - 自动化哨兵:在生产环境部署一个轻量级中间件,实时抓取所有 API 请求和响应,与
spec.yaml中定义的requestBody和responses进行比对。发现不匹配(如响应多返回了一个debug_info字段),立即记录告警,并关联到具体spec.yaml版本。我们用express-openapi-validator的validateResponses: true选项实现此功能,它会在响应返回前做校验。
4.2 陷阱二:JSON Schema 过度设计,陷入“完美主义瘫痪”
有人试图用 JSON Schema 描述一切:从email字段的 RFC 5322 全部规则,到phone_number的全球区号映射表。结果 Schema 文件长达 2000 行,没人敢改,AI 生成器也因过于复杂而失效。
破局方案:分层 Schema + “最小可行约束”原则
- 分层 Schema:将 Schema 分为三层:
core.json:基础类型约束(如email只需format: email,phone只需pattern: '^\\+?[1-9]\\d{1,14}$')。domain.json:领域特定约束(如电商的sku必须匹配^SKU-[A-Z]{2}-\\d{6}$)。integration.json:第三方系统对接约束(如支付网关要求的amount_cents字段必须为整数)。
- 最小可行约束:每个 Schema 只定义当前阶段必须强制执行的约束。
email字段的 RFC 5322 验证交给前端库(如validator.js)在 UI 层做,后端只做format: email的基础校验。记住:90% 的数据问题,靠required、type、minLength就能拦截,不必追求 100% 的理论完备。
4.3 陷阱三:状态机定义脱离业务语义,变成技术玩具
有些团队用 XState 定义了一堆状态,但状态名是state1,state2,转移事件是EVENT_A,EVENT_B,完全看不出业务含义。这违背了 SDD “规范即沟通媒介”的初衷。
破局方案:业务术语驱动 + 事件溯源验证
- 业务术语驱动:状态名和事件名必须来自领域专家(Product Manager、BA)使用的词汇。例如,订单状态必须是
pending_payment,fulfilled,disputed,而不是s1,s2;事件必须是customer_paid,warehouse_shipped,customer_requested_refund。 - 事件溯源验证:在数据库中,为每个业务实体增加
event_log表,记录每次状态变更的event_type、from_state、to_state、timestamp。定期运行脚本,扫描event_log,验证所有实际发生的event_type是否都在order-machine.ts的on字段中定义。未定义的事件,说明业务流程已超出状态机覆盖范围,必须更新规范。
4.4 陷阱四:CI 流水线只做“形式校验”,不碰真实数据
有些团队的 CI 会检查spec.yaml语法是否正确,但从未验证它是否能生成可用的代码,或生成的代码能否通过编译。openapi-generator可能因模板错误生成一堆语法错误的 TS 代码,CI 却只报“YAML 格式 OK”。
破局方案:端到端流水线 + “生成即运行”
- 端到端流水线:CI 步骤必须包含:
openapi-generator生成代码。tsc --noEmit编译生成的代码。jest运行基于生成代码的单元测试(如测试 API Client 的getDeviceById方法是否返回 Promise)。
- 生成即运行:在本地开发时,配置一个
precommithook,运行npm run sdd:generate && npm run build。只有生成代码能成功编译,才能提交。我们用husky+lint-staged实现,避免“CI 过了,本地跑不通”的尴尬。
4.5 陷阱五:团队协作中“规范所有权”模糊,导致推诿
当spec.yaml出现歧义时,后端说“这是前端定义的”,前端说“这是后端提供的”,测试说“文档没写清楚”。规范成了甩锅对象,而不是协作枢纽。
破局方案:设立“规范守护者”角色 + 每周契约对齐会
- 规范守护者(Spec Guardian):不是新增职位,而是由团队中一名资深工程师(轮值,每季度换人)担任。职责包括:
- 主持
spec.yaml的 CR(Code Review),确保所有变更都有业务方签字(Slack 截图或 Confluence 评论)。 - 维护
spec-change-log.md,记录每次变更的背景、影响范围、负责人。 - 每月发布《规范健康度报告》,统计
spec.yaml与实际代码的偏差率、Schema 校验失败次数。
- 主持
- 每周契约对齐会:15 分钟站会,只做一件事:打开
spec.yaml,逐条确认本周上线功能是否 100% 符合规范。谁负责的模块,谁来汇报。没有长篇大论,只有“是”或“否”,以及一个具体的 issue 链接。
5. SDD 的未来:从“规范驱动”到“契约智能体”
SDD 不是一个终点,而是一个正在加速演进的范式。随着 AI 编程工具的成熟,SDD 的形态也在进化。我们观察到三个清晰的趋势,它们正在重塑“规范”的定义和作用方式。
5.1 趋势一:规范从“静态文件”走向“动态契约服务”
当前的spec.yaml是一个 Git 仓库里的文本文件。未来的趋势是,它将成为一个可查询、可订阅、可执行的微服务。我们已在内部试点Contract Registry服务:
- 它提供 REST API,供 CI 工具查询
spec.yaml的最新稳定版本(GET /contracts/device-api/v1)。 - 它支持 Webhook,当
device-api规范更新时,自动通知所有订阅的服务(如前端构建流水线、Mock Server、测试平台)。 - 它内置 DSL 解释器,能直接执行
spec.yaml中的x-validation-rules,返回 JSON Schema 校验结果,无需客户端再加载 AJV。
这意味着,规范不再需要被“下载”和“解析”,而是作为一个活的契约服务,被整个生态按需调用。AI 编程工具可以直接向Contract Registry发送请求,获取当前上下文所需的精确约束,而不是依赖本地文件。
5.2 趋势二:AI 成为规范的“主动协作者”,而非被动执行者
现在的 AI 是“你给我规范,我生成代码”。下一代 AI 将是“我帮你发现规范中的漏洞”。我们训练了一个轻量级 LLM 微调模型,专门阅读spec.yaml和对应的changelog,它能主动提出:
- “
/devices/{id}/get的200响应中,status字段的enum值maintenance在device-status-machine.ts中未被on事件覆盖,可能导致状态机死锁。” - “
schemas/order-create-request.json中items[].sku的maxLength: 20与数据库products.sku字段的VARCHAR(50)不一致,建议统一为 50。”
这个模型不生成代码,只做规范审计。它把 SDD 的“预防性”能力,从人工 Review 提升到了毫秒级自动扫描。
5.3 趋势三:SDD 与 AI Agent 深度融合,形成“契约智能体”(Contract Agent)
最终形态,是出现一种新型的 AI Agent,我们称之为Contract Agent。它不是通用聊天机器人,而是被严格限定在spec.yaml定义的契约边界内行动的智能体。例如:
- 当产品经理在 Slack 中说:“给订单增加一个‘部分退款’功能”,Contract Agent 会:
- 解析
spec.yaml,找到/orders/{id}/refund接口。 - 检查当前
requestBody是否支持partial: true字段。 - 若不支持,自动生成
spec.yaml的 diff 补丁(添加partial字段及对应responses),并发起 PR。 - PR 通过后,自动触发 CI,生成新 Client、更新状态机、运行契约测试。
- 解析
- 整个过程,Agent 的所有操作,都严格遵循
spec.yaml的约束,它不能“发明”新字段,只能在契约允许的范围内组合与扩展。
这不再是“人指挥 AI”,而是“契约指挥 AI”。开发者从“AI 操作员”转变为“契约架构师”,专注于定义系统应该做什么,而把“如何做”的执行权,安全地交给被契约驯化的 AI。
我在实际使用中发现,SDD 最大的收益,不是减少 Bug,而是大幅降低了团队的认知摩擦。当新成员加入,他不再需要花一周时间读代码猜逻辑,而是打开spec.yaml,5 分钟内就能理解整个系统的数据流向和状态规则。当跨团队协作,大家争论的不再是“你那边怎么实现的”,而是“spec.yaml第 42 行的定义是否准确”。规范,终于从一份文档,变成了团队共享的、活的、可执行的“共同语言”。