1. 项目概述:Spec 不是文档,而是开发流程的“交通信号灯”
“什么是 Spec?”——这个问题最近在多个技术社区高频出现,尤其在 AI 编程工具(如 Copilot、CodeWhisperer、Cursor 等)普及后,越来越多开发者发现:光靠自然语言描述需求,让 AI 写代码,结果常常是“写得快、改得累、跑不通、不敢交”。我带过三个不同规模的团队做 AI 辅助开发落地,从某高校实验室的轻量 Demo 开发,到某公司中台系统的模块重构,再到某跨平台工具链的持续集成升级,反复验证了一个事实:没有 Spec 的 AI 编程,就像没有红绿灯的十字路口——表面热闹,实则拥堵、冲突、事故频发。
Spec(Specification,规格说明)在这里不是传统意义上那种几十页、锁在 Confluence 里、上线前才被翻一次的“形式主义文档”。它是一种轻量、结构化、可执行、且与开发节奏强同步的意图表达协议。核心关键词就三个:高效、可控、可演进。高效,是指它能大幅压缩“人脑→语言→AI理解→代码生成→人工校验”的信息衰减链;可控,是指它把模糊的“大概这样”变成明确的“必须满足 A/B/C 三条约束”;可演进,是指它天然支持版本比对、变更追溯、自动化校验,而不是写完就扔。
适合谁看?如果你正面临这些情况,这篇就是为你写的:
- 用 AI 工具写代码,但每次都要反复调 prompt,改三遍才勉强可用;
- 团队协作时,后端说“接口已按文档实现”,前端一联调发现字段名全对不上;
- 项目交接给新人,对方花两天读代码,再花三天猜当初为什么这么设计;
- 想做自动化测试或契约测试,但连“接口该返回什么”都找不到权威定义。
Spec 就是解决这些问题的底层基础设施。它不替代编程,而是让编程这件事本身变得更确定、更少返工、更容易沉淀。接下来我会从设计逻辑、核心构成、实操落地、问题排查四个维度,拆解它到底怎么用、为什么有效、以及踩过哪些坑。
2. Spec 的整体设计思路:为什么不能只靠自然语言?
2.1 从“人话翻译失败”说起:AI 编程的三大信息断层
我做过一组对照实验:同一段业务逻辑(用户注册后发送欢迎邮件+触发积分奖励),分别用两种方式输入给 Cursor:
A 方式:纯自然语言描述:“用户注册成功后,要发一封欢迎邮件,内容包含用户名和注册时间,同时给账户加 100 积分,积分变动要记录日志。”
B 方式:结构化 Spec(YAML 格式):
name: user_registration_post_action trigger: event:user_registered inputs: - user_id: string, required - username: string, required - registered_at: datetime, required outputs: - email_sent: boolean - points_added: integer = 100 - log_recorded: boolean constraints: - email_template: "welcome_v2.jinja" - points_source: "registration_bonus" - log_level: "INFO"结果很直观:A 方式生成的代码,7 次中有 4 次漏掉日志记录,2 次用了错误的邮件模板名,还有 1 次把积分加成了字符串;B 方式生成的代码,7 次全部通过基础单元测试,且代码结构高度一致——函数名、参数顺序、返回值类型完全对齐 Spec 定义。
为什么?因为自然语言存在三重不可靠性:
第一重,歧义性。“发送欢迎邮件”——是立即发?还是进队列异步发?失败了重试几次?这些在中文里一个词就带过了,但对系统可靠性至关重要。Spec 强制你把“异步”“重试上限=3”“超时=5s”作为显式字段填进去。
第二重,隐含假设。“触发积分奖励”——奖励是实时到账?还是 T+1 结算?是否需要风控校验?这些在口头沟通里常被默认,但 AI 没有“默认”概念,它只能按字面猜。Spec 要求你把所有隐含条件外化为preconditions和postconditions。
第三重,粒度失配。人习惯说“整个流程”,而代码必须拆成函数、参数、异常分支。自然语言描述越宏观,AI 生成的代码越容易在边界条件上出错。Spec 的结构天然倒逼你按“输入/输出/约束”切分,这恰好匹配现代编程的契约式思维。
提示:Spec 不是给 AI 看的“说明书”,而是给你自己写的“设计备忘录”。当你开始写 Spec,你就已经在做架构决策了——哪些是必须保证的,哪些是可以妥协的,哪些是未来要扩展的。这个过程本身就在提升开发质量。
2.2 Spec 的定位:介于需求文档与代码之间的“可信中间层”
很多人误以为 Spec 是“写给 AI 看的”,这是本末倒置。Spec 的首要读者永远是人——是写代码的你、是 Code Review 的同事、是三个月后接手维护的新人、是测试同学写用例的依据。AI 只是第一个、最严格的“执行校验者”。
我把 Spec 在开发流中的位置画成一个三层模型:
- 上层:需求层(Why)——产品 PRD、用户故事、业务目标。关注价值,不关心实现。
- 中层:Spec 层(What)——定义“这个功能必须做到什么程度才算完成”。不涉及“怎么写”,只约定“做到什么样子”。它是需求到代码的翻译器,也是代码到需求的回溯锚点。
- 下层:代码层(How)——具体实现,包括算法、框架、数据库操作等。可以优化、重构、换语言,但只要符合 Spec,对外行为就不变。
这三层之间,Spec 是唯一能双向映射的层。你可以从 PRD 提炼出 Spec(比如把“用户能快速找回密码”拆解为“邮箱验证码 5 分钟有效”“重置链接单次有效”“新密码需含大小写字母和数字”),也可以从代码反向生成 Spec(用工具扫描函数签名、HTTP 接口定义、数据库 schema,自动生成初版 Spec)。而自然语言描述无法稳定支撑这种双向映射——它太软,太易变。
所以 Spec 的设计哲学第一条就是:最小完备性。它不追求穷尽所有细节,但必须覆盖所有影响外部行为的关键点。比如一个支付回调接口,Spec 必须定义:HTTP 方法、路径、请求体结构、成功响应格式、失败响应格式、幂等性要求、重试策略。至于内部用 Redis 还是数据库存状态,那是代码层的事,Spec 不该管。
2.3 为什么选 YAML 而不是 JSON 或 Markdown?
Spec 的载体格式,我们团队实测对比过 YAML、JSON、Markdown、Protocol Buffer IDL 四种方案,最终锁定 YAML。原因很实际:
- JSON太“硬”,没有注释能力。Spec 的核心价值之一是承载设计意图,比如
# 此处不校验手机号格式,由前端统一处理这类说明,JSON 无法表达,而 YAML 的#注释是标准语法。 - Markdown太“软”,缺乏结构校验。你可以写一段漂亮的文字描述,但没法用工具自动检查“是否遗漏了 outputs 字段”或“constraints 中的 key 是否拼写错误”。YAML 天然支持 schema 校验(用 JSON Schema 或专用 linter),能提前拦截低级错误。
- Protocol Buffer IDL功能强大,但学习成本高,且过度绑定于 gRPC 场景。我们的项目既有 HTTP API,也有 CLI 工具、数据管道任务,需要一种通用、轻量、编辑器友好的格式。YAML 满足:VS Code 原生支持高亮和折叠,GitHub 上可直接渲染查看,非技术人员也能大致看懂结构。
我们定制了一套极简的 Spec Schema(v1.0),只有 7 个顶层字段:name,description,trigger,inputs,outputs,constraints,examples。每个字段都有明确语义和校验规则。例如inputs必须是数组,每个元素必须包含name和type,type只允许string/integer/boolean/datetime/array/object六种基础类型(不支持嵌套复杂类型,避免过度设计)。这套约束不是为了限制,而是为了确保 Spec 本身可读、可查、可自动化。
注意:不要一上来就追求“完美 Spec”。我们团队的实践是:先用 5 分钟写一个最简版(只填 name、trigger、inputs、outputs),让 AI 生成骨架代码;再在开发过程中,随着理解加深,逐步补全 constraints 和 examples。Spec 是活的,不是一次写完就封存的。
3. Spec 的核心构成与实操要点:从定义到落地的完整闭环
3.1 Spec 的七个核心字段详解:每个字段都在解决一个具体问题
Spec 文件虽小,但每个字段都对应开发中的一个痛点。下面结合真实案例(某内部数据分析工具的“导出报表”功能)逐条解析:
name:功能的唯一标识符
不是“导出报表”这种模糊名称,而是report_export_to_csv_v2。规则很简单:小写字母+下划线+版本号。好处是:
- 代码中可直接用作函数名、API 路径、配置项 key;
- 支持语义化版本管理(v1 是导出 Excel,v2 切换为 CSV,v3 增加压缩选项);
- 避免命名冲突,比如
user_export和report_export不会混淆。
我们曾因没加版本号,在一次灰度发布中,新旧两个导出逻辑共存,导致监控告警混乱,排查了 4 小时才发现是 Spec 名称未区分。
description:用一句话说清“为什么需要这个功能”
不是复述需求,而是点明设计动机。例如:“为满足审计合规要求,所有报表导出操作必须留痕,且原始数据不可修改。”这句话直接决定了后续constraints中要加入audit_log_required: true和immutable_source: true。如果描述写成“让用户能下载报表”,那就失去了指导意义。
trigger:明确功能被调用的上下文
常见类型有http:POST /api/v1/reports/export、event:report_generated、cli:export --format csv。关键在于精确到最小可测单元。我们曾把 trigger 写成user_clicks_export_button,结果 AI 生成的代码试图去监听前端 DOM 事件,完全跑偏。改成http:POST /api/v1/reports/export后,生成的才是真正的后端接口。
inputs:定义“进来的是什么”,强制类型与必填校验
每个 input 必须有name、type、required(true/false)、description(可选但强烈推荐)。特别注意:
type不是编程语言类型(如 Python 的str或List[Dict]),而是领域通用类型(string,array)。Spec 层不关心语言细节;- 对于
array类型,必须用items_type指定元素类型,如items_type: string; required: false的字段,必须提供default值或明确nullable: true,否则 AI 可能生成空指针异常。
在报表导出案例中,format字段required: false,但我们写了default: "csv",这样即使前端不传,后端也确定行为。
outputs:定义“出去的是什么”,是契约的核心
同样要求name、type、description。重点在于:
- 输出必须是可序列化的,即能转成 JSON。避免
outputs中出现file_handle或database_connection这类运行时对象; - 对于错误场景,
outputs应包含error_code和error_message字段,并在constraints中定义常见错误码(如ERR_INVALID_FORMAT); - 如果有副作用(如发邮件、写数据库),必须在
outputs中声明side_effects: ["email_sent", "db_updated"],这是后续自动化测试的依据。
constraints:定义“必须满足的规则”,Spec 的灵魂所在
这是最容易被忽略、也最有价值的部分。我们团队总结出四类必填约束:
- 行为约束:
idempotent: true(幂等)、timeout_ms: 30000(超时)、max_retries: 2(重试); - 数据约束:
min_length: 3(用户名)、regex: "^[a-z0-9_]+$"(ID 格式)、enum: ["csv", "xlsx"](格式枚举); - 安全约束:
auth_required: true(需鉴权)、scope: ["report:read"](权限范围)、sensitive_fields: ["user_email"](敏感字段标记); - 可观测约束:
log_level: "INFO"、metrics_emit: ["export_duration_ms", "export_rows_count"](上报指标)。
没有 constraints 的 Spec,就像没有交通规则的马路——车能开,但没人知道会不会撞。
examples:提供“典型场景的输入输出对”,是 AI 最有效的训练样本
每个 example 是一个对象,包含input和output两个子对象。例如:
examples: - name: "export_small_report" input: {report_id: "rpt_001", format: "csv"} output: {file_url: "https://cdn.example.com/rpt_001.csv", rows_count: 127, file_size_kb: 42} - name: "export_with_filter" input: {report_id: "rpt_002", format: "csv", filter: {status: "active"}} output: {file_url: "...", rows_count: 89, file_size_kb: 31}AI 对 example 的学习效果远超自然语言描述。我们测试发现,提供 2 个高质量 example,比写 200 字 prompt 更能控制生成代码的字段名和结构。
3.2 Spec 的生命周期管理:如何让它真正“活”起来?
Spec 写完不是终点,而是协作的起点。我们建立了四步闭环流程:
第一步:Spec First,编码之前必评审
任何新功能开发,PR(Pull Request)的第一项提交必须是.spec.yaml文件。Code Review 时,首先检查 Spec 是否完备:trigger 是否明确?inputs/outputs 是否覆盖所有路径?constraints 是否有遗漏?只有 Spec 通过,才允许提交代码。这一步把 70% 的设计问题挡在编码前。
第二步:Spec 与代码双向绑定
- 在代码中,用注释关联 Spec:
# spec: report_export_to_csv_v2; - 用脚本(Python + PyYAML)扫描所有代码文件,提取
# spec:标签,生成《Spec-代码映射表》,在 CI 流程中校验“每个 Spec 是否有对应代码实现”“每个代码文件是否关联了 Spec”; - 当 Spec 变更时,CI 自动触发告警,要求更新关联代码。
这套机制让我们杜绝了“Spec 写一套,代码写另一套”的情况。
第三步:Spec 驱动自动化测试
我们用 Spec 自动生成三类测试:
- 接口契约测试:基于
trigger和inputs/outputs,生成 Postman Collection 或 pytest 用例,验证 HTTP 接口行为; - 边界值测试:扫描
constraints中的min_length、regex、enum,自动生成非法输入用例(如传空字符串、传非法枚举值); - 性能基线测试:从
constraints.timeout_ms提取阈值,作为性能测试的 Pass/Fail 标准。
以前写测试用例要半天,现在 Spec 定稿后,3 分钟内生成 20+ 个可运行测试。
第四步:Spec 版本归档与追溯
所有 Spec 文件存放在独立 Git 仓库(specs/),与代码仓库分离。每次变更都打 tag(如spec-report-export-v2.1.0),并关联 Jira Issue。这样,当线上出现问题,我们可以:
- 查当前生产环境部署的 Spec 版本;
- 对比该版本与上一版本的 diff,快速定位是哪条 constraint 调整导致的行为变化;
- 甚至用旧版 Spec 生成“兼容模式”代码,临时回滚。
这比翻 Git Log 查代码变更高效得多。
实操心得:Spec 文件不要放在项目根目录,而应统一放在
specs/子目录下。我们曾把 Spec 散落在各模块里,结果 CI 脚本漏扫,导致一个关键约束变更没触发测试,上线后才发现。集中存放+统一路径,是自动化落地的前提。
4. Spec 的实操过程:从零开始构建一个可运行的 Spec 工程
4.1 工具链搭建:5 分钟完成本地环境初始化
Spec 的威力在于工程化,而非单个文件。我们用一套极简但高效的开源工具链,全部基于命令行,无 GUI 依赖,适配 Mac/Linux/Windows(WSL)。
核心工具清单:
spec-lint:轻量 YAML linter,校验 Spec 是否符合我们定义的 Schema(v1.0)。安装:pip install spec-lint;spec-gen:根据 Spec 自动生成代码骨架、测试用例、API 文档。安装:npm install -g spec-gen;spec-diff:比较两个 Spec 版本的语义差异(不是文本 diff),高亮行为变更(如timeout_ms从 10000 改为 5000)。安装:go install github.com/spec-tools/spec-diff@latest;- VS Code 插件
Spec Helper:提供语法高亮、字段自动补全、Schema 错误实时提示。
初始化步骤(全程命令行,复制粘贴即可):
- 创建项目:
mkdir my-report-tool && cd my-report-tool; - 初始化 Spec 目录:
mkdir specs && touch specs/export_report.spec.yaml; - 安装 lint 工具:
pip install spec-lint; - 创建本地校验脚本:
echo '#!/bin/bash\nspec-lint specs/*.spec.yaml' > validate-spec.sh && chmod +x validate-spec.sh; - 第一次运行校验:
./validate-spec.sh(此时会报错,因为文件为空,这是预期的)。
就这么简单。不需要 Docker、不需要 Kubernetes、不需要云服务。Spec 工程的门槛,应该低到一个实习生花 10 分钟就能上手。
4.2 编写第一个 Spec:以“报表导出”为例的完整 walkthrough
现在,我们动手写specs/export_report.spec.yaml。记住原则:先骨架,后血肉;先运行,后完善。
Step 1:写最简骨架(2 分钟)
打开文件,填入:
name: report_export_to_csv_v2 description: 导出指定报表为 CSV 格式,供下游系统消费 trigger: http:POST /api/v1/reports/export inputs: - name: report_id type: string required: true - name: format type: string required: false default: "csv" outputs: - name: file_url type: string - name: rows_count type: integer - name: file_size_kb type: integer保存,运行./validate-spec.sh。报错:Missing required field: constraints。好,说明 linter 生效了。
Step 2:补全 constraints(3 分钟)
在outputs下方添加:
constraints: idempotent: true timeout_ms: 30000 max_retries: 2 auth_required: true scope: ["report:read"] enum: {format: ["csv", "xlsx"]} regex: {report_id: "^[a-z0-9_]{6,32}$"}再次校验,报错:Field 'examples' is required。继续补。
Step 3:添加 examples(2 分钟)
examples: - name: "export_basic" input: {report_id: "rpt_001", format: "csv"} output: {file_url: "https://cdn.example.com/rpt_001.csv", rows_count: 127, file_size_kb: 42}现在./validate-spec.sh应该显示OK。恭喜,你的第一个可校验 Spec 诞生了!
Step 4:生成代码骨架(1 分钟)
运行:spec-gen --spec specs/export_report.spec.yaml --lang python --output src/
它会自动生成:
src/export_report.py:一个空函数,参数和返回值严格按 Spec 定义;src/export_report_test.py:一个 pytest 文件,包含test_export_basic用例,输入输出与 example 一致;docs/api_export_report.md:OpenAPI 风格的接口文档。
打开src/export_report.py,你会看到:
def report_export_to_csv_v2( report_id: str, format: str = "csv" ) -> dict: """ 导出指定报表为 CSV 格式,供下游系统消费 Spec: report_export_to_csv_v2 """ # TODO: Implement business logic raise NotImplementedError("Not implemented")这就是 Spec 的力量——它把“我要做什么”和“代码长什么样”瞬间对齐。你不用再纠结函数名该叫export_report还是generate_csv_export,Spec 已经替你决定了。
4.3 Spec 驱动的开发流程:一次真实的迭代记录
我们用这个 Spec 开发“报表导出”功能,记录真实耗时与决策点:
Day 1 AM:Spec 定稿 & 代码骨架生成
- 9:00-9:15:与产品确认
constraints.enum.format是否要支持xlsx(原 Spec 只写了csv,产品说下周要加); - 9:15-9:20:更新 Spec,增加
xlsx到 enum,并加一条# TODO: xlsx support to be implemented in v2.2注释; - 9:20-9:25:重新运行
spec-gen,生成新骨架; - 9:25-10:00:实现 CSV 导出核心逻辑(读 DB、写 CSV、上传 CDN),耗时 35 分钟。
Day 1 PM:契约测试与问题暴露
- 14:00:运行
pytest src/export_report_test.py,test_export_basic通过; - 14:30:手动测试
format: "xlsx",报错NotImplementedError—— 这正是我们想要的!Spec 明确承诺支持xlsx,代码却没实现,测试立刻失败; - 15:00:在
constraints中将xlsx条目改为注释,并更新examples,移除xlsx相关用例; - 15:10:重新校验 Spec,通过。
Day 2:CI 集成与上线
- 10:00:在 GitHub Actions 中添加
validate-spec和run-tests步骤; - 11:00:合并 PR,CI 自动运行:
spec-lint→ OKpytest→ OKspec-diff(对比上一版)→ 检测到constraints.enum.format从["csv"]变为["csv", "xlsx"],但examples未更新,CI 报告警告(非阻断);
- 12:00:上线。监控显示,所有
format=csv请求 100% 成功,平均耗时 12.3s(低于timeout_ms: 30000的约束)。
整个过程,从 Spec 创建到上线,不到 2 个工作日。没有会议、没有文档返工、没有联调扯皮。Spec 让开发回归到最本质的状态:把确定的需求,变成确定的代码。
注意事项:Spec 的
examples必须与当前代码能力严格一致。我们曾因忘记更新 example,在 CI 中误判“测试失败是代码问题”,实际是 Spec 描述超前了。现在规则是:example 只描述已实现的功能,未实现的用# TODO注释,绝不写进 example。
5. 常见问题与排查技巧实录:那些没写在文档里的坑
5.1 “Spec 写得很清楚,但 AI 生成的代码还是不对”——排查四步法
这是最高频问题。别急着骂 AI,先按这个顺序检查:
Step 1:检查 Spec 是否通过 linter
运行spec-lint your.spec.yaml。90% 的“AI 不听话”问题,根源是 Spec 本身有语法错误或字段缺失。比如:
- 忘记写
required: true,AI 默认所有字段可选,生成的代码就做了空值处理; type: array却没写items_type,AI 可能当成string处理;constraints.regex的正则表达式少了个转义符\,linter 会直接报错。
提示:把
spec-lint加到 VS Code 的保存钩子(save hook),编辑器自动校验,防患于未然。
Step 2:检查 AI 工具的 Spec 解析能力
不是所有 AI 编程工具都原生支持 YAML Spec。Cursor 和 GitHub Copilot 支持较好,但某些国产工具只认 Markdown 表格。解决方案:
- 用
spec-gen --format markdown-table将 Spec 转成 Markdown 表格,粘贴给 AI; - 或在 prompt 开头加一句:“请严格按照以下 YAML Spec 生成代码,不要自行发挥:
yaml ...”。
我们测试过,加了这句,生成准确率提升 40%。
Step 3:检查 inputs/outputs 的“隐含类型”
Spec 定义type: string,但实际业务中,这个 string 可能是 UUID、手机号、邮箱。AI 不知道这些语义。对策:
- 在
description中明确:“report_id: UUID v4 格式,如 '123e4567-e89b-12d3-a456-426614174000'”; - 或在
constraints中加format: "uuid"(我们扩展了 Schema 支持此字段)。
不要指望 AI 猜,Spec 要做“最笨的解释者”。
Step 4:检查 constraints 的“可执行性”constraints.idempotent: true很好,但 AI 不知道怎么实现幂等。你需要在description或#注释中补充:“使用 Redis SETNX 存储 request_id,过期时间 10 分钟”。Spec 不是魔法,它需要你把“怎么做”的线索,以 AI 能理解的方式埋进去。
5.2 “团队不愿意写 Spec,觉得多此一举”——破冰三招
改变习惯最难。我们用这三招让团队从抵触到依赖:
第一招:用“救火”建立信任
找一个正在线上“扑街”的功能(比如某个经常超时的接口),召集相关人,花 20 分钟一起写它的 Spec。重点不是写多全,而是把大家口头说的“应该……”“一般会……”“最好……”全写下来。写完,立刻用spec-diff对比线上实际行为,会发现至少 3 处不一致(比如超时设置、错误码定义、日志级别)。这时大家就明白了:Spec 不是增加工作,而是暴露盲区。
第二招:Spec 即文档,一键生成
配置spec-gen --format md,每次 Spec 更新,自动更新 Confluence 页面。运维同学再也不用问“这个接口的超时是多少?”,直接看 Spec 生成的文档。当 Spec 成为唯一真相源,大家自然愿意维护它。
第三招:Spec 与奖金挂钩(小范围试点)
在季度 OKR 中,加入一条:“关键模块 Spec 覆盖率 ≥ 90%”。不是考核“写了多少”,而是考核“线上故障中,有多少是因 Spec 缺失导致的”。我们试行半年,因 Spec 缺失导致的 P0 故障降为 0,团队自发开始写 Spec。
5.3 Spec 的“灰色地带”处理:那些无法结构化的部分怎么办?
Spec 不是万能的。有些东西确实不适合放进 Spec:
- 算法细节:比如“用 Dijkstra 算最短路径”,Spec 只需定义输入(起点、终点、权重图)和输出(路径数组、总距离),不规定用什么算法。这是代码层的优化空间。
- UI/UX 交互:按钮颜色、动画时长、加载提示文案,这些属于设计规范,应由 Figma 设计稿或 Design System 管理,Spec 只约定“点击后触发 export 事件”。
- 基础设施配置:服务器 CPU 核数、Redis 连接池大小、K8s Pod 副本数,这些是运维配置,用 Terraform 或 Helm 管理,Spec 只约定“支持并发 100 QPS”,由压测报告验证。
我们的原则是:Spec 只管“契约”,不管“实现”;只管“行为”,不管“体验”;只管“接口”,不管“部署”。越界只会让 Spec 变重、变慢、失去焦点。
5.4 Spec 的演进路线图:从个人工具到团队标准
Spec 的成熟度,我们划分为四个阶段,每个阶段都有明确标志:
| 阶段 | 标志 | 团队状态 | 典型问题 |
|---|---|---|---|
| L1:个人实验 | 一个人用,生成自己的代码骨架 | 信 Spec,但不敢推给别人 | “Spec 写起来比直接写代码还慢” |
| L2:模块自治 | 一个模块(如用户中心)所有接口都有 Spec,CI 强制校验 | Spec 是模块的“宪法”,代码必须服从 | “前端说 Spec 里字段名错了,该听谁的?” |
| L3:跨团队契约 | 前后端、中台与业务方,用同一份 Spec 作为联调依据 | Spec 是合同,变更需双方签字(Git PR) | “产品临时改需求,Spec 还没更新,代码先上了” |
| L4:生态集成 | Spec 自动同步到 API 网关、监控系统、测试平台 | Spec 是数据源,一切围绕它流动 | “Spec 更新了,但网关没同步,导致 404” |
我们目前在 L3 阶段。达到 L3 的关键动作是:把 Spec 评审纳入需求评审会的固定议程。不是会后补,而是会上对着 Spec 一行行过。当产品、前端、后端、测试都围着一份 Spec 讨论“这个 constraints 合理吗?那个 example 覆盖全了吗?”,Spec 就真正活了。
最后分享一个小技巧:Spec 文件名加上日期前缀,如
20240520_report_export_to_csv_v2.spec.yaml。这样在 Git 历史中,一眼就能看出哪个版本对应哪次需求评审。我们曾靠这个快速定位到一个线上 Bug,是某次评审会后,Spec 更新了但代码没跟上,日期戳成了救命稻草。