1. 什么是“规范驱动开发”?它真不是给大厂准备的PPT术语

Spec-Driven Development(SDD),中文常译作“规范驱动开发”,这个词刚听上去特别像那种挂在CTO办公室白板上的战略口号——高大上、抽象、带着点学术腔。但在我带过的6个早期创业团队里,它从来不是用来写汇报材料的,而是每天早上站会时,工程师盯着产品文档里那几行加粗的接口字段、前端同学反复确认的错误码定义、测试同学提前写好的边界用例表格时,所有人心里默认的那条“不许越界的红线”。它解决的不是“怎么写代码”的问题,而是“怎么让三个人在没有专职项目经理的情况下,还能同步对齐需求、不返工、不扯皮”的生存问题。

核心关键词—— spec(规范) ,在这里不是指ISO标准或RFC文档那种动辄几百页的庞然大物,而是一份轻量、可执行、版本可控、且被所有角色共同维护的“契约文本”。它可以是一份YAML格式的OpenAPI 3.0描述文件,可以是TypeScript接口定义+JSDoc注释组合的 .d.ts 文件,也可以是Confluence页面上用表格列清楚的“输入字段名|类型|是否必填|示例值|业务含义”的需求清单。关键不在形式,而在它是否具备三个硬性特征: 可读性(非技术人员能看懂)、可验证性(能自动生成测试或校验逻辑)、可追溯性(每个字段变更都能查到谁、何时、为什么改) 。我见过太多初创团队把PRD当spec用,结果开发到一半发现“用户头像URL支持HTTPS”这条没写清楚,后端默认只存相对路径,前端拼接出错,上线前两小时全员加班改;也见过用Figma设计稿当唯一spec的团队,设计师改了个按钮颜色,没人通知后端这个状态码要新增一个,结果埋下线上500错误的伏笔。Spec-driven不是增加流程负担,而是把那些本该在编码前就厘清的模糊地带,用最小成本固化下来。它最适合的人群,不是已经建立成熟流程的大厂,恰恰是资源紧张、沟通链路短、但容错率极低的早期创业公司——因为在这里,一次需求理解偏差,可能直接导致MVP延期两周,错过关键融资窗口。

2. 为什么初创公司比大厂更需要Spec-Driven?这不是反直觉,而是反常识

很多人第一反应是:“大厂流程重、人多、系统复杂,才需要spec来对齐吧?” 这是个典型的认知陷阱。真相是: 大厂有冗余人力、有专职BA、有成熟的变更管理流程、甚至有专门的“需求冻结期”,它们能靠组织能力兜底spec的缺失;而初创公司什么都没有,spec就是它唯一的“组织能力替代品”。 我用三个真实场景拆解这种不可替代性:

2.1 场景一:从0到1搭建支付网关,Spec是避免“技术债雪崩”的第一道防火墙

去年帮一家做跨境SaaS的种子轮团队重构支付模块。他们最初用Node.js快速对接了Stripe,但没定义任何规范,所有逻辑都散落在路由处理函数里。当需要接入PayPal时,后端同学直接复制粘贴了Stripe的代码,只改了API地址——结果PayPal返回的 payment_status 字段是 completed / failed ,而Stripe是 succeeded / requires_action ,前端按老逻辑判断,导致用户付款成功却显示“支付失败”。如果当时有一份YAML格式的 payment_result_spec.yaml ,明确约定统一返回字段 status: enum[success, failed, pending] message: string ,那么Stripe和PayPal的适配器只需各自负责字段映射,核心业务逻辑完全复用。这份spec不到50行,却让后续接入Alipay、本地银行网关的工期缩短了70%。大厂可能用一套微服务治理平台自动校验字段,但初创公司买不起、也等不及,一份手写的spec就是最轻量的“契约式编程”。

2.2 场景二:产品、设计、开发三方协作,Spec是消灭“我以为”的翻译器

我们曾为一款教育App设计课程购买流程。产品经理在飞书文档里写:“用户点击购买按钮后,弹出支付弹窗,显示价格、优惠券、最终实付金额。” 设计师据此做了高保真原型,其中优惠券区域标注了“最多可选3张”。但开发同学实现时,后端API只返回了一个 discount_amount 数字,没传可用优惠券列表,也没传限制数量。结果前端只能硬编码“最多3张”,后来运营想临时改成“不限张数”,全链路都要改。如果当时有一份 checkout_ui_spec.json ,用JSON Schema明确定义:

{
  "type": "object",
  "properties": {
    "available_coupons": {
      "type": "array",
      "items": { "$ref": "#/definitions/coupon" },
      "maxItems": 3
    }
  }
}

那么后端就知道必须返回数组且控制长度,前端渲染逻辑自然解耦,运营策略调整只需改配置,无需发版。这里spec的价值,不是约束技术,而是把产品脑中的“我以为用户会这样理解”,翻译成机器可读的、各方无歧义的指令。

2.3 场景三:技术选型摇摆期,Spec是保持架构稳定的锚点

团队A在做实时协作编辑器,初期用Firebase Realtime DB,后来因成本考虑想切到自建WebSocket+Redis。如果没有spec,迁移就是一场灾难:Firebase的 onChildAdded 事件语义和自建服务的 document_update 消息格式完全不同,前端要重写所有监听逻辑。但我们提前定义了 collab_event_spec.avsc (Apache Avro Schema),规定所有协作事件必须包含 event_id: string , timestamp: long , payload: bytes ,且 payload 内嵌结构化数据。迁移时,后端只需保证新服务发出的消息符合Avro Schema,前端完全不用动——因为消费方只认schema,不认底层传输协议。大厂可能用Kafka Schema Registry强制校验,初创公司用一个 .avsc 文件+CI里的 avro-tools compile 命令,效果等价。Spec在这里,成了技术演进的“隔离层”,让团队敢于试错,而不怕推倒重来。

提示:Spec不是越多越好。初创公司的spec必须遵循“最小可行契约”原则——只定义当前迭代必需的、跨角色协作的关键契约点。一份覆盖全部100个API的OpenAPI文档,不如一份精准定义“用户注册”“登录态刷新”“支付回调”这三个核心流程的spec有用。我建议团队每周站会花10分钟评审spec变更,确保它始终是“活的文档”,而不是尘封在Git历史里的古董。

3. Spec-Driven落地四步法:从写第一行YAML到形成团队肌肉记忆

Spec-Driven不是买个工具就能跑起来的流程,它本质是一套协作习惯的养成。我在三个不同技术栈的初创团队(React+Node、Flutter+Go、Vue+Python)验证过这套四步法,平均4-6周就能让团队形成条件反射。关键不在于工具多炫酷,而在于每一步都解决一个具体痛点,让成员立刻感受到“这玩意儿真省事”。

3.1 第一步:选定“契约载体”,拒绝完美主义,先跑通再优化

载体选择的核心标准只有一条: 所有角色(产品、前端、后端、测试)能否在5分钟内看懂、修改、并验证修改是否生效? 基于这个标准,我淘汰了Swagger Editor(学习成本高)、Postman Collections(无法定义类型约束)、甚至GraphQL Schema(对非技术角色太抽象)。最终锁定三个轻量方案:

  • API契约 :OpenAPI 3.0 YAML文件( .openapi.yaml )。优势是生态成熟,VS Code装 Redocly 插件,保存即生成交互式文档; openapi-generator 能一键生成TypeScript客户端、Spring Boot服务端骨架;CI中用 spectral 做规则校验(如“所有POST接口必须有requestBody”)。我们团队用它定义所有外部HTTP接口,文件放在 /specs/api/ 目录下,与代码同库管理。

  • 前端组件契约 :TypeScript接口 + JSDoc注释( .d.ts 文件)。例如 ButtonProps.d.ts

    /**
     * @description 按钮组件属性契约
     * @see https://confluence.example.com/button-spec
     * @example <Button size="large" variant="primary" onClick={handleClick} />
     */
    export interface ButtonProps {
      /** 按钮尺寸,影响padding和font-size */
      size: 'small' | 'medium' | 'large';
      /** 按钮样式变体 */
      variant: 'primary' | 'secondary' | 'ghost';
      /** 点击事件,必须提供可追踪的event_id */
      onClick: (event: { event_id: string; target: 'button' }) => void;
    }
    

    产品在Confluence写需求时,直接引用这个文件路径;设计师检查UI时,对照 size 枚举值确认是否支持“large”;前端开发时,IDE自动提示类型错误。契约就在代码里,零额外维护成本。

  • 数据模型契约 :JSON Schema( .schema.json )。用于定义数据库表结构、消息队列事件、配置中心参数。例如用户表契约 user.schema.json

    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "required": ["id", "email", "created_at"],
      "properties": {
        "id": { "type": "string", "pattern": "^usr_[a-z0-9]{8}$" },
        "email": { "type": "string", "format": "email" },
        "status": { 
          "type": "string", 
          "enum": ["active", "inactive", "pending_verification"] 
        }
      }
    }
    

    后端用 ajv 库在入库前校验;前端表单提交前用 @json-schema-form/core 做实时校验;DBA用它生成建表SQL。一份schema,三方受益。

注意:不要试图一步到位。建议从最痛的一个点切入——比如后端总抱怨前端传参格式错,那就先用OpenAPI定义所有入参;如果UI走查总发现状态遗漏,那就先用TS接口定义组件props。让团队第一次尝到“改spec就能自动修复bug”的甜头,比开10场培训管用。

3.2 第二步:建立“契约即代码”工作流,让Spec成为CI/CD流水线的一环

Spec一旦脱离代码仓库,就会迅速失效。我们强制所有spec文件与对应服务代码同库、同分支、同PR。关键是在CI中加入三道自动门:

  1. 语法门 yamllint .openapi.yaml 检查YAML格式, jsonlint user.schema.json 验证JSON语法。失败则PR无法合并,杜绝“手抖多打个逗号”导致整个文档失效。

  2. 一致性门 :用 openapi-diff 对比PR分支与主干的OpenAPI变更,自动生成差异报告。如果新增了 /v1/users/{id}/deactivate 接口,但没在 changelog.md 里记录,CI会失败并提示:“检测到新增接口,请更新变更日志”。这强迫团队养成“改spec必写日志”的习惯。

  3. 契约门 :最关键的一步—— 运行基于spec生成的测试用例 。我们用 openapi-backend 库,将 .openapi.yaml 自动转换为Express中间件,模拟所有API行为。CI中启动这个mock服务,然后运行前端E2E测试(Cypress)和后端集成测试(Jest)。如果spec里定义了 GET /users 返回 { "data": [{ "id": "string" }] } ,但后端实际返回 { "items": [...] } ,测试立即失败。这意味着: spec不是文档,而是可执行的契约;违反spec=代码错误,必须修复。

这套CI流水线,我们花了3天搭建(用GitHub Actions),但它让团队对spec的信任度飙升。以前大家觉得“改个spec又不编译,随便写”,现在看到CI红了,第一反应是“我是不是改错了spec?”,而不是“测试环境挂了?”。契约真正长出了牙齿。

3.3 第三步:定义“契约变更协议”,让每一次修改都有迹可循、有据可依

Spec不是静态文档,它必须随业务演进。但初创公司最怕“随意改”,所以必须立下三条铁律:

  • 黄金法则一:禁止直接修改生产环境spec 。所有变更必须通过PR发起,且PR标题强制格式: [SPEC] [BREAKING|MINOR] api/v1/users.yaml: add email_verified field BREAKING 表示破坏性变更(如删除字段、改类型), MINOR 表示向后兼容(如加字段、扩枚举)。CI会扫描标题,如果是 BREAKING ,则强制要求关联Jira任务ID和回滚方案。

  • 黄金法则二:每个字段变更必须附带“业务上下文”注释 。在OpenAPI的 description 字段里,不写“用户邮箱”,而写:“用户注册时填写的邮箱,用于发送验证码(见PR#123)和密码重置(见PR#456);若为空,则用户未完成邮箱验证流程”。这样,半年后新人接手,一眼明白这个字段为什么存在、影响哪些功能。

  • 黄金法则三:建立“契约快照”机制 。每次发布正式版本(如v1.2.0),用脚本自动将 /specs/ 目录打包为 specs-v1.2.0.tar.gz ,上传至内部MinIO存储,并生成SHA256校验码。当线上出现诡异问题,运维同学一句 curl -s https://specs.example.com/specs-v1.2.0.tar.gz | sha256sum ,就能确认当前线上服务是否真的运行着标称的v1.2.0契约——这比翻Git历史快10倍。

我们曾用这套协议处理过一次危机:某次紧急热修复,后端同学绕过PR直接改了数据库字段名,导致前端调用失败。但因为spec快照存在,我们5分钟内定位到“线上实际运行的是v1.1.9契约,而非标称的v1.2.0”,立刻回滚数据库变更,避免了更大范围故障。契约快照,就是初创公司的“时间机器”。

3.4 第四步:培养“契约思维”,让Spec成为团队日常对话的语言

工具和流程只是骨架,真正的灵魂是人的习惯。我们通过三个动作,把Spec-Driven刻进团队DNA:

  • 每日站会新增30秒“契约对齐” :每人只说一句:“今天我的工作是否会影响任何spec?如果有,我已更新PR并@相关人”。比如前端说:“今天改登录页,会新增 login_method: 'email' | 'phone' 字段,PR#789已提”。后端听到,就知道要去实现这个字段。没有长篇大论,只有契约变更的即时广播。

  • Code Review强制检查项 :在GitHub模板中加入一条:“✅ 已检查本次变更是否需更新对应spec文件(路径:/specs/xxx)”。Reviewer不点勾,PR不能合并。刚开始大家嫌麻烦,但两周后,90%的PR都主动附带了spec更新,因为不更新,代码就合不了。

  • 新人入职第一课:读懂spec,再碰代码 。我们给新人一个任务:用Postman调通 GET /health 接口,但不给任何代码链接,只给 /specs/api/health.yaml 。他必须自己看OpenAPI定义,构造请求头、理解响应结构。完成这个任务,才算通过“契约入门考试”。这比讲1小时“我们为什么用Spec”有效得多。

实操心得:别指望一次培训就改变习惯。我们坚持了8周,每周五下午设为“契约健康日”:团队一起用 openapi-diff 扫描所有spec,找出过期字段、缺失描述、不一致枚举值,当场修复。8周后,团队自发开始用 $ref 复用公共schema,用 x-example 补充真实业务示例——这才是Spec-Driven真正扎根的标志。

4. 规范驱动开发的实战细节:从OpenAPI字段设计到前端类型安全落地

Spec-Driven的价值,最终要落到每一行代码、每一个API调用、每一个UI渲染上。这里不讲虚的,直接拆解我们在真实项目中打磨出的、可直接抄作业的细节方案。这些细节,往往决定了Spec是沦为摆设,还是真正成为生产力引擎。

4.1 OpenAPI设计:如何写出既严谨又易用的API规范?

很多团队的OpenAPI文档写得像教科书,字段堆砌、示例缺失、约束模糊。我们总结出“三要三不要”原则,并附上真实案例:

  • 要定义精确的枚举值,不要用 string 糊弄
    错误示范:

    status:
      type: string
      description: 用户状态
    

    正确做法:

    status:
      type: string
      description: 用户账户状态。active=已激活,inactive=已停用,pending_verification=待验证邮箱
      enum: [active, inactive, pending_verification]
      example: active
    

    为什么? 枚举让前端能直接生成下拉选项、做状态映射;后端用 enum 可自动生成校验逻辑;测试同学能一键生成所有状态的测试用例。 example 字段更是神器——我们用 openapi-sampler 工具,基于 example 自动生成Mock数据,前端联调时再也不用手动构造 {"status": "active"}

  • 要使用 allOf 复用公共结构,不要重复造轮子
    错误示范:在 /users /orders 两个接口的响应里,都写一遍分页字段 page , per_page , total
    正确做法:定义 components/schemas/Pagination.yaml

    Pagination:
      type: object
      properties:
        page:
          type: integer
          example: 1
        per_page:
          type: integer
          example: 20
        total:
          type: integer
          example: 156
    

    然后在各接口中复用:

    components:
      schemas:
        UserListResponse:
          allOf:
            - $ref: '#/components/schemas/Pagination'
            - type: object
              properties:
                data:
                  type: array
                  items:
                    $ref: '#/components/schemas/User'
    

    为什么? 一处修改,全局生效。比如运营要求分页 per_page 最大值从100改为200,改 Pagination.yaml 一个文件,所有接口自动继承。

  • 要为关键字段添加业务规则注释,不要只写技术类型
    错误示范:

    email:
      type: string
      format: email
    

    正确做法:

    email:
      type: string
      format: email
      description: |
        用户邮箱地址。规则:
        - 必须为有效邮箱格式(RFC 5322)
        - 注册时强制验证(发送验证码邮件)
        - 修改时需二次验证(原邮箱收确认码)
        - 作为密码重置唯一凭证
      example: user@example.com
    

    为什么? 技术类型( string )告诉机器怎么处理,业务规则告诉人为什么这么处理。测试同学看到“修改时需二次验证”,立刻知道要写两条用例:修改邮箱未验证、修改邮箱已验证。

4.2 前端类型安全:如何让TypeScript成为Spec的天然延伸?

Spec写得再好,如果前端还是 any 乱飞,就前功尽弃。我们的方案是: 让TypeScript类型完全由OpenAPI生成,且保证100%可追溯

  • 生成策略 :用 openapi-typescript (非 openapi-generator )生成TS类型。原因: openapi-typescript 生成的类型是纯类型声明( .d.ts ),不带运行时代码,体积小、IDE友好;且它保留了OpenAPI的 description example ,生成的类型注释就是spec原文。
    命令:

    npx openapi-typescript https://localhost:3000/openapi.yaml --output src/types/api.ts --use-options --default-export
    

    生成的 api.ts 里,字段注释直接来自spec:

    /** 
     * 用户邮箱地址。规则:
     * - 必须为有效邮箱格式(RFC 5322)
     * - 注册时强制验证(发送验证码邮件)
     * ...
     */
    email?: string;
    
  • 类型使用规范 :禁止手动写接口类型。所有API调用必须用生成的类型:

    // ✅ 正确:使用生成的User类型
    const user = await apiClient.get<User>('/users/{id}', { params: { id } });
    
    // ❌ 错误:手动定义interface
    interface MyUser { name: string; }
    

    为什么? 手动类型必然滞后于spec。我们用ESLint规则 @typescript-eslint/no-explicit-any 配合自定义规则,扫描代码中所有 interface / type 定义,如果文件名不包含 generated ,则报错。强制让开发者“拥抱生成,远离手写”。

  • 错误处理契约化 :Spec不仅要定义成功响应,更要定义所有可能的错误。我们在OpenAPI中为每个接口定义 400 , 401 , 404 , 422 , 500 响应:

    responses:
      '422':
        description: 请求参数校验失败
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ValidationError'
    components:
      schemas:
        ValidationError:
          type: object
          properties:
            code:
              type: string
              enum: [invalid_email, password_too_short, duplicate_username]
            message:
              type: string
            field:
              type: string
              example: email
    

    前端调用时,错误处理逻辑就变成:

    try {
      await apiClient.post('/users', userData);
    } catch (error) {
      if (isValidationError(error)) { // 类型守卫,由生成的类型提供
        showFieldError(error.field, error.message); // 精准定位到出错字段
      }
    }
    

    效果 :用户注册时邮箱格式错,前端直接在邮箱输入框下方显示“邮箱格式不正确”,而不是弹个笼统的“请求失败”。这就是Spec驱动的用户体验。

4.3 后端契约执行:如何让Spec不只是文档,而是运行时的守护者?

后端是Spec的最终执行者,也是最容易“阳奉阴违”的环节。我们的方案是: 用中间件把spec变成一道不可逾越的墙

  • 请求校验 :用 express-openapi-validator (Node)或 fastapi-openapi-validator (Python)中间件,在路由入口处自动校验:

    • 路径参数、查询参数、请求体是否符合OpenAPI定义的类型、格式、枚举、必填项。
    • 如果spec定义 id string 且匹配正则 ^usr_[a-z0-9]{8}$ ,而用户传了 123 ,中间件直接返回 400 Bad Request ,附带详细错误信息:“id must match pattern ^usr_[a-z0-9]{8}$”。
      价值 :前端再也不用写“if (id.length < 8) return error”,后端也不用在每个控制器里写校验逻辑。校验逻辑集中、统一、可配置。
  • 响应校验(可选但强烈推荐) :开启 validateResponses: true ,中间件会在响应发送前,校验返回数据结构是否符合spec定义的 200 响应schema。虽然会带来微小性能损耗(<1ms),但它能100%杜绝“后端改了代码,忘了改spec”的低级错误。我们只在开发和测试环境开启,生产环境关闭。

  • 动态契约生成 :对于高度动态的接口(如搜索API,支持任意字段组合),我们用 json-schema-faker 根据 search_request.schema.json 自动生成海量测试数据,喂给混沌测试工具(如Chaos Mesh),模拟各种边界条件。这比人工写测试用例高效10倍。

常见问题速查表:

问题现象 排查思路 解决方案
前端调用API报 400 ,但后端日志没记录 检查 express-openapi-validator 中间件是否在日志中间件之前注册 调整中间件顺序,确保校验日志先于业务日志
TypeScript类型生成后,IDE提示 Cannot find module openapi-typescript 生成的 api.ts 路径未被 tsconfig.json include 包含 tsconfig.json 中添加 "include": ["src/**/*", "src/types/api.ts"]
OpenAPI中 example 字段没生效,Mock数据全是 null openapi-sampler 默认不使用 example ,需加参数 --use-examples npx openapi-sampler --use-examples --output mock-data.json spec.yaml
团队抱怨“写spec比写代码还累” 检查是否在定义过度细节(如所有字段的 maxLength 遵循“最小可行契约”,只定义影响协作的关键约束;用 x-nullable: true 代替 nullable: true (OpenAPI 3.0 bug workaround)

5. 初创公司Spec-Driven避坑指南:那些没人告诉你的血泪教训

Spec-Driven听起来很美,但我在多个项目踩过的坑,远比教科书里写的多。这些教训,都是真金白银换来的,有些甚至导致过线上事故。分享出来,不是为了吓退你,而是帮你绕开那些本可避免的深坑。

5.1 坑一:把Spec当“需求文档”写,结果写成了天书

这是最普遍的误区。我见过一个团队,花两周写了3000行OpenAPI文档,覆盖了所有未来可能用到的API,连“用户注销设备”这种还没排期的功能都定义好了。结果呢?文档没人看,因为太厚;开发时发现spec和实际业务逻辑冲突,但改spec要走流程,干脆绕过spec写代码;最后文档成了“考古资料”,没人敢动。
我的解法 :Spec必须遵循“Just-in-Time”(及时制)原则——只定义 下一个迭代周期内必须交付 的契约。我们用Jira的“Sprint Backlog”视图,把每个用户故事(Story)拖到“Ready for Spec”列,产品同学必须在此列完成对应的spec片段(通常1-3个接口),才能拖到“In Progress”。这样,spec永远是“小而精”的,永远和代码进度严格对齐。一个迭代结束,spec文档增长不超过200行,团队毫无压力。

5.2 坑二:Spec和代码不同步,版本混乱成灾难

某次发布后,前端突然报错 Cannot read property 'name' of undefined 。排查发现,后端在 /users 接口的响应里,把 user.name 字段改成了 user.full_name ,但没更新OpenAPI文档。前端用旧spec生成的类型,还在访问 name 字段。更糟的是,Git历史里有5个不同分支的 openapi.yaml ,没人知道哪个是“权威版本”。
我的解法 :建立“单一事实源”(Single Source of Truth)。我们规定:

  • 唯一权威spec :只有 main 分支根目录下的 openapi.yaml 是权威版本。
  • 所有环境指向同一spec :本地开发、测试、预发、生产环境,都通过环境变量 OPENAPI_URL=https://api.example.com/openapi.yaml 加载spec(后端服务启动时下载并缓存)。
  • 代码生成强制绑定 :CI中, openapi-typescript 命令的输入URL必须是 $OPENAPI_URL ,而不是本地文件路径。这样,如果生产环境的spec变了,前端构建就会失败,因为生成的类型和线上API不匹配。
    一句话: 让环境决定spec,而不是让分支决定spec 。从此再没出现过“本地能跑,线上报错”的诡异问题。

5.3 坑三:过度追求自动化,忽略了人的因素

有团队迷信工具,搞了一套全自动流程:产品在Notion写需求 → Notion webhook触发脚本 → 自动生成OpenAPI → 自动提交PR → 自动合并。结果呢?产品同学为了“让脚本能解析”,把需求写成冰冷的JSON格式,失去了业务语境;生成的spec缺少 description ,全是 field1 , field2 ;PR合并后,没人Review,错误spec直接上线。
我的解法 :自动化只做“体力活”,不做“脑力活”。我们只自动化:

  • openapi-diff 生成变更报告(体力:比对文本)
  • openapi-typescript 生成类型(体力:字符串转换)
  • CI中运行基于spec的测试(体力:执行命令)
    所有需要判断、决策、沟通的环节,必须由人完成
  • 产品写spec描述,必须用自然语言,解释业务意图(脑力)
  • PR合并前,必须有人(通常是Tech Lead)Review,确认变更合理(脑力)
  • 每周五的“契约健康日”,团队一起讨论spec是否还反映真实业务(脑力)
    工具是杠杆,人是支点。没有支点的杠杆,一推就倒。

5.4 坑四:Spec成了甩锅工具,破坏团队信任

最危险的苗头是:后端说“你spec没写清楚,所以我的实现有问题”,前端说“你代码没按spec来,所以我的调用失败”。Spec本应是协作的桥梁,结果变成了互相指责的武器。
我的解法 :推行“契约共担”文化。我们在团队公约里写明:

  • Spec是集体创作,不是产品单方面输出 。每个新接口的spec初稿,必须由产品、前端、后端三人一起在白板上画完流程、字段、状态机,再由产品整理成文。
  • Bug归因,先问“spec是否覆盖此场景” 。如果没覆盖,责任在spec;如果覆盖了但实现不符,责任在实现者;如果覆盖了且实现正确但业务逻辑错,责任在需求本身。
  • 每月复盘“契约失效案例” 。例如某次支付失败,根本原因是spec没定义网络超时后的重试策略。复盘后,我们立刻在 payment_spec.yaml 里新增 x-retry-policy 扩展字段,并写入团队知识库。
    效果 :当Spec从“考核标准”变成“改进工具”,团队才真正拥抱它。

最后分享一个小技巧:我们给每个spec文件加了一个 x-maintainer 字段,例如:

components:
  schemas:
    User:
      x-maintainer: "@backend-team"
      # ... 其他定义

这样,当有人想改 User 结构时,GitHub PR会自动@ @backend-team ,提醒他们评估影响。一个小小的扩展字段,让责任归属变得无比清晰。Spec-Driven的终极目标,从来不是写出完美的文档,而是让一群聪明人,在资源有限的情况下,用最简单的方式,达成最可靠的协作。它不性感,不炫技,但它像空气一样,当你拥有时习以为常,一旦失去,立刻窒息。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐