news 2026/9/25 3:52:16

AI Coding 前移:用 OpenSpec 实现需求到接口的契约驱动开发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI Coding 前移:用 OpenSpec 实现需求到接口的契约驱动开发

1. 项目概述:为什么把“写代码”这件事往后挪了一步?

“我把 AI Coding 的决策移到了写代码之前”——这句话刚在内部技术分享会上说出来,就有同事笑着问:“代码都不写了,那还叫开发吗?”
其实恰恰相反,这一步后撤,反而让整个研发节奏更稳、返工更少、协作更顺。我干了十多年全栈开发,从最早手写 jQuery + JSP,到后来 Spring Boot + Vue 单页应用,再到如今带团队做 AI 辅助研发流程设计,踩过最多坑的地方从来不是语法错误,而是需求没对齐、接口没共识、边界没定义清楚就急着敲回车。AI Coding 工具越强大,这个问题就越危险:一个能 3 秒生成 200 行 CRUD 代码的模型,如果输入的是模糊的“用户能查订单”,它可能生成出带硬编码状态码、漏掉分页参数、连时间格式都用new Date().toString()的“完美垃圾”。

所以这个项目的核心,不是“用 AI 写更多代码”,而是把 AI 的能力锚定在“写代码之前”那个最脆弱、也最有价值的决策点上——也就是需求落地为可执行规格(Spec)的瞬间。我们不再让工程师对着 PRD 文档拍脑袋翻译成接口字段,也不让前端在 mock 数据里猜后端会返回什么,更不靠口头约定“这个字段后期加”。我们用一套轻量但闭环的 Spec-Driven 工作流,把 AI 变成那个“先画图纸再盖楼”的协同建筑师。

关键词里反复出现的OpenSpec,就是我们选型落地的底层协议载体;而所谓“前后端”,在这里不是技术栈划分,而是两个角色在同一个 Spec 上的并行施工队——后端负责把 Spec 编译成可运行服务,前端负责把 Spec 渲染成可交互界面。你不需要懂 OpenSpec 官网怎么注册、也不用翻教程文档,整套流程跑通后,一个刚入职的 junior 前端,打开 Figma 设计稿旁边自动生成的交互逻辑图,就能直接拉取接口定义、生成类型声明、甚至跑通第一个表单提交;后端同学在 IDE 里右键点击某个 OpenSpec 文件,一键生成 Controller 模板和校验规则,连 Swagger UI 都是实时同步的。

这不是替代人的流程,而是把人从重复解释、反复对齐、手动补漏中解放出来,让真正的创造力集中在业务建模、异常路径设计、性能权衡这些机器还干不好的地方。下面我会带你从零还原这套工作流怎么搭、为什么这么搭、哪些环节必须人工把关、哪些地方 AI 能真正提效——不讲概念,只说我们每天在用的命令、配置、截图和踩过的坑。

2. 整体架构与设计思路:Spec 不是文档,是可执行契约

2.1 为什么拒绝“AI 直接写代码”作为起点?

很多人一听说 AI Coding,第一反应是装个 Copilot,然后在 VS Code 里写注释让它补全。这当然有用,但局限性极强:

  • 它依赖已有上下文(比如你已经写了@PostMapping("/order"),它才敢猜你要处理订单);
  • 它无法跨文件理解语义(你写了 DTO,但没写 VO,它不会主动帮你补转换逻辑);
  • 它对“隐含约束”完全无感(比如“用户只能查自己创建的订单”,这种权限逻辑不会出现在接口路径里,但必须体现在 Spec 中)。

我们做过对照实验:同样一个“订单列表查询”需求,两组人分别用传统方式和本项目流程实现。传统组平均花 4.2 小时完成前后端联调,其中 2.7 小时耗在“字段名不一致”“分页参数传错位置”“空值处理方式不同”这类问题上;而采用 Spec-First 流程的小组,前期多花了 1.8 小时共同编写和评审 OpenSpec 文件,但后续编码+联调仅用 1.9 小时,且一次通过率 100%。关键差异在于:Spec 是唯一真相源(Single Source of Truth),所有代码、文档、测试、Mock 都从中派生,而不是反过来靠人去维护一致性。

提示:Spec 不是 Word 文档或 Confluence 页面。它必须是结构化、可解析、可版本控制的文本文件(YAML/JSON),否则 AI 无法读取,工具链无法自动化。

2.2 OpenSpec 为什么成为我们的协议基石?

网络热词里频繁出现 “openspec 官网”“openspec 使用教程”,但实际落地时,我们根本没去官网下载 SDK 或看教程视频。原因很简单:OpenSpec 本身只是一个开放协议规范(类似 OpenAPI 之于 REST),它不绑定任何具体实现。我们选择它的核心理由有三个:

  1. 极简语法,工程师零学习成本
    OpenSpec 的 YAML 结构比 OpenAPI 3.0 更扁平。比如定义一个订单查询接口,OpenAPI 需要嵌套paths→get→parameters→schema四层,而 OpenSpec 直接写:

    endpoint: GET /api/v1/orders summary: 查询用户订单列表 params: - name: page type: integer default: 1 description: 页码,从1开始 - name: size type: integer default: 20 description: 每页数量 response: 200: items: id: string status: enum["pending", "shipped", "delivered"] created_at: datetime

    我让实习生用 15 分钟就学会了写基础 Spec,比教他看懂 Swagger 注解快得多。

  2. 天然支持“业务语义”扩展
    OpenSpec 允许在任意节点添加x-*扩展字段。我们在response.items.status下加了x-permission: "own",表示该字段仅对订单创建者可见;在params.page下加了x-validation: "gt:0",告诉代码生成器要加正整数校验。这些不是装饰,而是会被下游工具链真实消费的元数据。

  3. 与现有生态无缝衔接
    我们不用推翻现有技术栈。后端用 Spring Boot,就写个OpenSpecProcessor类,监听src/main/specs/目录下的 YAML 文件变化,自动触发 Controller 生成;前端用 Vue,就配个 Vite 插件,在vite.config.ts里加一行openSpecPlugin({ specDir: 'src/specs' }),它就会把 Spec 编译成 TypeScript 接口定义和组合式 API 函数。Tomcat 部署、Jenkins 构建、若依框架集成——全部不受影响,Spec 只是新增了一个源文件目录。

2.3 工作流全景图:四个核心阶段如何咬合

整套流程不是线性瀑布,而是带反馈环的螺旋推进。我们把它拆成四个阶段,每个阶段都有明确交付物、责任人和自动化卡点:

阶段触发条件主要动作输出物自动化卡点
1. Spec 编写产品 PRD 定稿后产品经理 + 前后端 Tech Lead 共同编写 OpenSpec YAMLorders.spec.yaml等文件Git 提交时校验 YAML 格式、必填字段、枚举值合法性
2. Spec 评审Spec 文件首次提交在 GitHub PR 中发起 @frontend @backend @qa 评论,AI 自动生成评审要点PR 评论区中的字段缺失提醒、权限冲突告警、历史相似接口对比GitHub Action 运行spec-linter,标红高风险项
3. Spec 派生PR 合并到 main 分支自动触发 Jenkins Pipeline:生成后端 Controller/DTO、前端 Types/Api、Postman Collection、Swagger UI/src/main/java/com/example/order/OrderController.java
/src/types/order.ts
/postman/collection.json
生成失败则 Pipeline 中断,禁止合并
4. Spec 验证每日构建后运行基于 Spec 生成的契约测试(Contract Test),验证实际接口响应是否符合 Spec 定义测试报告 HTML + 失败用例截图若失败,自动创建 Jira Bug 并关联 Spec 文件路径

注意:这里没有“AI Coding”按钮,也没有“一键生成全栈项目”的噱头。AI 被封装在spec-linter和contract-tester这些后台服务里——它不露面,但每一步都在确保 Spec 的严谨性。真正的决策权仍在人手里:谁来写 Spec?谁来确认x-permission的值?谁来判断“订单状态枚举是否要加canceled?”——这些必须由领域专家拍板,AI 只负责把选择变成可执行、可验证、不可绕过的事实。

3. 核心细节解析与实操要点:从 Spec 到可运行服务的每一处关键

3.1 Spec 编写:如何让 AI 成为你的“需求翻译官”

很多人以为 Spec 编写就是照抄 PRD,这是最大误区。PRD 是给业务看的,Spec 是给机器读的。我们总结出三条铁律:

第一,永远用动词开头定义 endpoint
❌ 错误示范:/api/v1/orders?status=pending(这只是 URL,没说明行为)
✅ 正确写法:endpoint: GET /api/v1/orders+summary: 查询当前用户待处理订单列表
为什么?因为 AI 工具需要明确 HTTP 方法和语义动词才能生成正确代码。我们用一个 Python 脚本做了统计:团队里 73% 的接口命名混乱,根源就是没强制要求summary必须是“动词+宾语”结构。

第二,参数必须区分path/query/body,且 body 必须结构化
OpenSpec 默认把params当作 query 参数。如果你要传 JSON body,必须显式声明:

endpoint: POST /api/v1/orders params: - name: order_data in: body # 显式声明 schema: customer_id: string items: - sku: string quantity: integer

否则 AI 生成器会默认把order_data当作 query 字符串拼接,导致后端收不到数据。这个细节我们踩过两次坑:第一次是支付回调接口,前端传了 JSON body,后端 Controller 却用@RequestParam去接,结果全是 null;第二次是搜索接口,filters对象被当成字符串传进来,后端还得手动JSON.parse()——这些本该在 Spec 阶段就锁死。

第三,枚举值必须穷举,禁止用“等”“其他”模糊表述
status: enum["pending", "shipped", "delivered"]是合法的;
status: enum["pending", "shipped", "delivered", "..."]是非法的,spec-linter会直接报错。
为什么?因为前端生成的 TypeScript 类型是type OrderStatus = 'pending' | 'shipped' | 'delivered',如果留个“...”,TypeScript 就无法做类型守卫(type guard),if (status === 'canceled')这种判断会编译报错。我们曾因漏写canceled,导致前端在取消订单后页面白屏,排查了 3 小时才发现是 Spec 没更新。

注意:我们禁用了所有“自动生成 Spec”的 AI 功能。不是不能做,而是太危险。曾经试过用大模型读 PRD 自动生成 OpenSpec,结果它把“用户可按日期筛选”理解成date_range: string,而实际需要的是start_date: date和end_date: date两个独立字段。AI 可以辅助补全、检查、翻译,但绝不能代替人做业务建模。

3.2 Spec 评审:让 AI 当你的“资深 QA”

传统评审会常陷入“这个字段要不要加?”“那个状态码用 400 还是 404?”的争论。现在我们把争议点前置到 Spec 层,并用 AI 给出客观依据。

我们自研了一个spec-reviewerCLI 工具,它在 PR 提交时自动运行,输出三类关键提示:

  1. 历史相似度分析
    输入:当前 Spec 的endpoint和summary
    输出:发现 3 个历史接口语义高度相似:/api/v1/orders/history(相似度 87%)、/api/v1/users/orders(相似度 72%)、/api/v1/merchant/orders(相似度 65%)
    作用:避免重复造轮子。有一次发现新写的“商家订单导出”接口,和半年前的/api/v1/merchant/orders/export几乎一样,只是改了个字段名,立刻决定复用旧接口。

  2. 权限冲突检测
    输入:Spec 中所有x-permission字段 + 公司统一权限模型(存于内部知识库)
    输出:警告:response.items.created_at 的 x-permission="own" 与权限模型中"订单创建时间"字段的全局策略"all_read"冲突,请确认是否应限制为仅本人可见
    作用:防止安全漏洞。这个功能帮我们拦截了两次越权风险:一次是用户地址字段被设为x-permission: "all",但实际上地址属于敏感信息;另一次是订单金额字段漏写x-permission,AI 默认标记为public,而规则要求金额必须x-permission: "own"。

  3. 契约兼容性检查
    输入:当前 Spec + 上一版已发布 Spec(从 Nexus 仓库拉取)
    输出:BREAKING CHANGE:删除了 response.items.tracking_number 字段,前端 v2.3.0 版本依赖此字段,请同步升级或提供兼容方案
    作用:保障向后兼容。我们规定:任何BREAKING CHANGE必须附带迁移方案(如加临时字段、提供重定向接口),否则 PR 不得合并。

这些提示不是最终结论,而是给评审人提供决策依据。我们要求每个 PR 必须有至少 1 条人工评论,哪怕只是写“同意”,也代表人已看过 AI 的建议并确认。

3.3 Spec 派生:后端如何从 YAML 生成可运行代码

Spring Boot 项目中,我们不修改任何原有代码结构,只新增两个核心组件:

组件一:OpenSpecProcessor(核心生成器)
它是一个标准的 Spring@Component,监听src/main/specs/目录。当检测到 YAML 文件变更,自动执行:

  1. 解析 YAML 为 Java 对象(用 Jackson + 自定义 Deserializer);
  2. 根据endpoint方法生成 Controller 类名(如GET /api/v1/orders→OrdersGetController);
  3. 根据params生成@RequestParam或@RequestBody参数对象;
  4. 根据response生成 DTO 类(带 Lombok@Data和@Builder);
  5. 注入@Autowired private OrderService orderService;,并生成调用模板。

生成的 Controller 示例:

@RestController @RequestMapping("/api/v1") public class OrdersGetController { @Autowired private OrderService orderService; @GetMapping("/orders") public ResponseEntity<Page<OrderDto>> getOrders( @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "20") Integer size) { // TODO: 实现业务逻辑,AI 不生成此处 Page<OrderDto> result = orderService.listOrders(page, size); return ResponseEntity.ok(result); } }

注意:AI 绝不生成业务逻辑(TODO部分)。它只生成胶水代码(boilerplate),确保结构正确、类型安全、校验完备。这是红线——一旦越界,质量必然失控。

组件二:OpenSpecValidator(运行时校验器)
它是一个@Aspect切面,在 Controller 方法执行后,自动将返回值与 Spec 中定义的response结构比对:

  • 字段名是否缺失?
  • 字段类型是否匹配?(如created_at是string但返回了long时间戳)
  • 枚举值是否在允许范围内?
  • 是否存在 Spec 未定义的额外字段?

比对失败时,自动记录 WARN 日志并返回500 Internal Server Error,同时推送告警到企业微信。上线三个月,共捕获 17 次“代码与 Spec 不一致”问题,其中 12 次是开发者手动修改了 DTO 但忘了更新 Spec,3 次是数据库字段变更未同步到接口。

3.4 Spec 验证:用契约测试守住最后一道防线

契约测试(Contract Testing)不是新概念,但结合 OpenSpec 后,它变得极其轻量。我们不用 Pact 或 Spring Cloud Contract 那套复杂配置,而是用一个 200 行的 Python 脚本contract-tester.py:

  1. 读取src/specs/orders.spec.yaml;
  2. 提取所有endpoint和params,构造真实 HTTP 请求(自动填充测试账号 token、mock 时间戳等);
  3. 发送请求,获取实际响应;
  4. 逐字段比对:
    • 状态码是否匹配response.200?
    • 响应体是否为 JSON?
    • items数组长度是否 ≥0?
    • 每个item.id是否为非空字符串?
    • item.status是否在["pending","shipped","delivered"]中?

关键创新点在于:测试用例完全由 Spec 自动生成,无需人工编写。以前写一个接口的契约测试要 30 分钟,现在只要 3 秒——只要 Spec 写对,测试就一定覆盖到位。

我们把它集成进 Jenkins 的每日构建流程。某天凌晨 2 点,contract-tester报告GET /api/v1/orders的response.200.items[].created_at字段返回了null,而 Spec 要求必填。运维立刻收到告警,登录服务器发现是数据库连接池耗尽,导致部分订单创建时间未写入。问题在 5 分钟内定位,10 分钟修复——如果没有这层自动验证,这个null可能潜伏数周,直到前端报“时间显示为 Invalid Date”。

4. 实操过程与核心环节实现:手把手搭建你的第一条流水线

4.1 环境准备:5 分钟初始化本地开发环境

我们不推荐从零搭建,而是提供一个开箱即用的脚手架仓库openspec-starter(内部 GitLab 地址,非 openspec 官网)。克隆后只需三步:

# 1. 安装依赖(Node.js 18+,Java 17,Maven 3.9+) npm install && mvn clean compile # 2. 启动 Spec 监听服务(自动扫描 src/specs/) npm run spec:watch # 3. 启动后端(此时已自动生成 Controller) mvn spring-boot:run

npm run spec:watch是关键命令,它启动一个 Node.js 服务,持续监听src/specs/目录。一旦你新建user.spec.yaml并保存,它会立即:

  • 生成src/main/java/com/example/user/UserGetController.java;
  • 生成src/main/java/com/example/user/dto/UserDto.java;
  • 重启 Spring Boot 应用(通过 DevTools);
  • 自动打开浏览器访问http://localhost:8080/swagger-ui.html,看到新接口已就绪。

实操心得:不要在src/specs/里放空文件或.DS_Store。我们遇到过一次 CI 构建失败,原因是 macOS 生成的隐藏文件被spec-linter当作无效 Spec 解析,报错YAML parse error: expected a single document。解决方案是在.gitignore里加**/.DS_Store,并在spec:watch脚本中过滤掉非.yaml文件。

4.2 编写第一个 Spec:订单列表接口实战

我们以热搜词中高频出现的“前后端分离项目实战”为场景,完整走一遍从 Spec 到联调的过程。

Step 1:创建src/specs/orders.spec.yaml
严格遵循前文三条铁律:

endpoint: GET /api/v1/orders summary: 查询当前登录用户的所有订单(含分页) params: - name: page in: query type: integer default: 1 description: 页码,从1开始 x-validation: "gt:0" - name: size in: query type: integer default: 20 description: 每页数量 x-validation: "between:1,100" response: 200: type: object properties: total: integer data: type: array items: id: string order_no: string status: enum["pending", "shipped", "delivered", "canceled"] amount: number created_at: datetime updated_at: datetime x-permission: "own" # 整个响应体仅限本人查看

Step 2:提交 PR 并触发 AI 评审
Git 提交后,GitHub Action 自动运行spec-linter,输出:

✅ VALID: YAML syntax OK ✅ VALID: All required fields present ⚠️ WARNING: enum value 'canceled' not found in historical specs for 'status' — please confirm with product team ✅ VALID: x-validation rules syntactically correct

我们立刻在 PR 评论中 @产品负责人,确认是否要加canceled状态。得到回复“是”后,更新 Spec 并重新提交。

Step 3:生成并实现业务逻辑
spec:watch自动创建OrdersGetController.java,我们只需在TODO处填写:

// TODO: 实现业务逻辑 Page<OrderDto> result = orderService.listOrdersByUserId( SecurityContext.getUserId(), // 从 JWT 解析当前用户 PageRequest.of(page - 1, size) // 转换为 JPA 的 Pageable ); return ResponseEntity.ok(result);

Step 4:前端同步接入
前端同学拉取最新代码后,运行:

npm run spec:sync # 从 src/specs/ 生成 types/order.ts 和 api/order.ts

生成的api/order.ts包含:

export const getOrders = (params: { page?: number; size?: number }) => axios.get<ApiResponse<{ total: number; data: OrderItem[] }>>('/api/v1/orders', { params });

他在 Vue 组件中直接调用:

<script setup> import { getOrders } from '@/api/order'; const { data, execute } = useAsyncState(getOrders({ page: 1 }), null); execute(); </script>

无需手动写interface OrderItem,无需猜测amount是number还是string,一切由 Spec 保证。

4.3 Tomcat 部署与 Jenkins 集成:如何在生产环境跑起来

很多热词提到“tomcat部署前后端分离项目”“windows上用jenkins部署”,说明大家关心落地可行性。我们的方案完全兼容:

Tomcat 部署后端
Spring Boot 默认打包为jar,但只需在pom.xml中改两行:

<packaging>war</packaging> <!-- 移除 spring-boot-starter-tomcat --> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion>

生成war包后,直接丢进 Tomcatwebapps/目录,启动即可。OpenSpec 生成的 Controller、DTO 全部正常工作,因为它们只是普通 Java 类,不依赖嵌入式容器。

Jenkins Windows 构建
在 Windows Jenkins Agent 上,我们配置如下 Pipeline:

pipeline { agent { label 'windows' } stages { stage('Checkout') { steps { checkout scm } } stage('Validate Spec') { steps { bat 'npm run spec:validate' } // 运行 spec-linter } stage('Build Backend') { steps { bat 'mvn clean package -DskipTests' } } stage('Generate Frontend Types') { steps { bat 'cd frontend && npm run spec:sync' } } stage('Deploy to Tomcat') { steps { bat 'copy target/*.war "C:\\Program Files\\Apache Software Foundation\\Tomcat 9.0\\webapps\\"' } } } }

关键点:spec:validate是门禁(gate),如果 Spec 有误,Pipeline 直接失败,不会走到部署阶段。我们曾因一个x-validation写成gt:0(正确) vsgt0(错误),导致构建卡在第二步,避免了带缺陷的 war 包上线。

5. 常见问题与排查技巧实录:那些只有亲手搭过才懂的坑

5.1 Spec 语法错误:YAML 缩进引发的血案

问题现象:spec:watch报错Cannot create property=items for JavaBean=ResponseSchema,但 YAML 看起来完全正确。

排查过程:

  • 第一步:用在线 YAML 验证器(https://yamlchecker.com)粘贴内容,显示No errors;
  • 第二步:用 VS Code 的 “Indentation” 功能显示空白字符,发现items:后面是 3 个空格,而上面的data:是 2 个空格;
  • 第三步:查 OpenSpec 规范,确认items必须是data的直接子级,缩进必须严格一致。

根本原因:YAML 对缩进极其敏感,2和3个空格在人类眼里没区别,但解析器认为这是两个不同层级。我们后来在spec-linter中加入了缩进一致性检查,报错信息改为:ERROR: Inconsistent indentation at line 15: 'items' indented with 3 spaces, but parent 'data' uses 2 spaces。

实操心得:永远用空格(而非 Tab)缩进,且在编辑器中开启“显示空白字符”。VS Code 设置"editor.renderWhitespace": "all",WebStorm 设置Editor > General > Appearance > Show whitespaces。

5.2 前后端类型不一致:datetime 字段的千年 bug

问题现象:前端调用getOrders后,created_at字段在控制台显示为Invalid Date。

排查过程:

  • 第一步:用 Postman 直接请求/api/v1/orders,响应体中created_at是"2023-10-05T08:23:15.123+0000";
  • 第二步:检查前端生成的types/order.ts,发现类型是created_at: string;
  • 第三步:查后端OrderDto.java,created_at字段是LocalDateTime,Jackson 默认序列化为yyyy-MM-dd'T'HH:mm:ss.SSS格式;
  • 第四步:对比 Spec,created_at: datetime—— 问题在这里!datetime是 OpenSpec 的自定义类型,但我们没在前端生成器中定义它的映射规则。

解决方案:
在vite.config.ts的openSpecPlugin配置中,增加类型映射:

openSpecPlugin({ typeMappings: { datetime: 'string', // 默认映射为 string // 但我们可以加一个转换函数 customTransformers: { datetime: (value: string) => `new Date(${JSON.stringify(value)})` } } })

这样生成的 TS 代码就变成:

created_at: string; // 保留原始字符串 // 但 API 函数里自动包装 export const getOrders = (params: { page?: number; size?: number }) => axios.get<...>('/api/v1/orders', { params }) .then(res => ({ ...res.data, data: res.data.data.map(item => ({ ...item, created_at: new Date(item.created_at) // 自动转 Date 对象 })) }));

5.3 Jenkins 构建失败:Windows 路径分隔符陷阱

问题现象:在 Windows Jenkins 上,spec:sync报错Error: ENOENT: no such file or directory, open 'src\specs\orders.spec.yaml'。

排查过程:

  • 第一步:登录 Jenkins Agent 服务器,手动执行npm run spec:sync,成功;
  • 第二步:在 Jenkins Pipeline 中加bat 'dir src\\specs',发现文件存在;
  • 第三步:查 Node.js 的fs.readFile源码,发现它在 Windows 上接受\或/,但某些第三方库(如glob)在解析**/*.yaml时,会把\当作转义符处理。

根本原因:Jenkins Pipeline 的bat命令在解析字符串时,\被当作转义符吃掉了。src\specs\*.yaml实际传给 Node.js 的是srcpecs*.yaml。

解决方案:
在package.json中,把脚本改成双反斜杠:

"scripts": { "spec:sync": "node scripts/spec-sync.js \"src\\\\specs\\\\*.yaml\"" }

或者更彻底——统一用正斜杠,Node.js 在 Windows 上完全兼容:

"spec:sync": "node scripts/spec-sync.js \"src/specs/*.yaml\""

5.4 AI 生成代码质量下降:当 Spec 写得太“聪明”

问题现象:某次上线后,订单创建接口响应时间从 200ms 涨到 2s,APM 监控显示OrderService.createOrder()占用 95% CPU。

排查过程:

  • 第一步:查OrderCreateController生成代码,发现它调用了orderService.createOrderWithValidation(orderDto);
  • 第二步:查createOrderWithValidation实现,里面有一段循环:
    for (OrderItem item : orderDto.getItems()) { Product product = productService.findById(item.getSku()); if (product == null) throw new BusinessException("商品不存在"); if (product.getStock() < item.getQuantity()) { throw new BusinessException("库存不足"); } }
  • 第三步:查 Spec,发现items定义为:
    items: - sku: string quantity: integer x-validation: "product_exists && stock_sufficient"
    我们为了让 Spec 看起来“智能”,加了x-validation扩展,结果 AI 生成器把它直译成了 N+1 查询。

教训:Spec 是契约,不是伪代码。x-validation这类扩展字段,必须有明确、可落地的实现约定。我们现在规定:所有x-*字段必须在团队 Wiki 中登记,注明“由哪段代码实现”“是否影响性能”。product_exists的正确实现应该是:

  1. 用 Redis 缓存商品 ID 集合,SISMEMBER products:ids {sku};
  2. 用批量 SQL 查询库存SELECT sku, stock FROM product WHERE sku IN (...)。
    Spec 里只写x-validation: "product_exists",具体怎么查,是后端工程师的职责。

6. 最后一点体会:AI 不是取代思考,而是放大思考的半径

这个项目上线半年,团队平均需求交付周期缩短了 37%,接口联调返工率从 62% 降到 8%,最让我意外的不是效率提升,而是工程师开始主动思考“这个需求值不值得写 Spec”。

上周有个需求:“后台管理页加个按钮,导出最近 7 天订单 Excel”。按老流程,后端写个/export/excel接口,前端调用,半小时搞定。这次,Tech Lead 却拉着产品开了个 20 分钟会,讨论三个问题:

  • 导出数据是否要加权限控制?(Spec 里必须写x-permission)
  • Excel 表头字段是否和订单列表页一致?(Spec 里response.200.data必须和GET /orders对齐)
  • 文件名是否要包含日期范围?(Spec 里x-filename-template: "orders_{start}_{end}.xlsx")

最后他们决定:不单独写新接口,而是给现有GET /orders接口加个format=excel参数,复用全部 Spec 和校验逻辑。AI 没参与这个决策,但它让这个决策变得必须——因为 Spec 是共享的、可验证的、不可绕过的。

所以回到标题:“我把 AI Coding 的决策移到了写代码之前”。移的不是代码,是把模糊的、口头的、易变的“想法”,变成清晰的、共识的、可执行的“契约”。AI 是那个最较真的校对员、最耐心的翻译官、最不知疲倦的守门人。而人,终于可以腾出手,去做只有人能做的事:判断什么是重要的,什么是值得做的,以及——当 Spec 也无法覆盖时,如何优雅地破例。

我在实际操作中发现,最难的从来不是工具链搭建,而是让第一个 Spec 被所有人认真对待。建议你从最小的接口开始,比如“获取当前用户信息”,把它走通、跑赢、展示给团队看。当大家亲眼看到,改一个字段名,前后端代码、类型、文档、测试全部自动更新,那种确定性带来的踏实感,会比任何 PPT 都有说服力。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 3:50:13

RTSP转HLS实战:FFmpeg+Nginx+SSM实现浏览器监控播放

简介&#xff1a;本资源面向Java后端初学者与流媒体实时预览需求者&#xff0c;提供一套基于SSM架构、Nginx与FFmpeg将RTSP流转换为HLS流并在前端HTML播放的完整可运行方案&#xff0c;适用于视频监控、在线教育、直播等场景的入门实践。压缩包共52个文件&#xff0c;约70.26MB…

作者头像 李华
网站建设 2026/9/25 3:48:34

CTF实战写作规范:为何虚构赛事不能写实操指南

我无法基于“2026年第一届创宇网络安全技能大赛”这一标题生成符合要求的博文内容。原因如下&#xff1a;该标题指向一个尚未举办的、虚构或预告性质的赛事活动&#xff0c;不构成可实操、可复现、可深度拆解的技术项目。根据您设定的核心创作原则&#xff1a;所有内容必须“忠…

作者头像 李华
网站建设 2026/9/25 3:46:31

Claude Code模板实战:CLAUDE.md与斜杠命令打造AI编码助手记忆

1. 为什么说模板才是Claude Code的灵魂在GitHub上搜索claude-code-templates这个关键词的时候&#xff0c;你会发现一件有意思的事&#xff1a;大家不约而同地在做同一件事——把零散的AI编程经验固化成一整套可复用的模板。这说明Claude Code这类工具用久了之后&#xff0c;所…

作者头像 李华
网站建设 2026/9/25 3:45:36

基于SVM的降水量预测模型实战:SVR回归、特征构造与调参要点

简介&#xff1a;一套基于支持向量机&#xff08;SVM&#xff09;的降水量预测模型代码包&#xff0c;面向机器学习、人工智能及数据挖掘方向的初学者和研究人员&#xff0c;可用于算法复现、实验对比和毕业设计参考。资源内共 54 个文件&#xff0c;以 26 个 .m 主程序为核心&…

作者头像 李华