OpenSpec 实战详解:可控的AI编程

2 阅读7分钟

前面的文章介绍了 OpenSpec 的基本使用和核心流程,这篇文章换个方式,直接用实战案例把 OpenSpec 的工作流跑一遍。

例子是一个 Java 电商项目 my-mall-app,分两个场景:从零加一个新功能,以及对已有功能做变更,来深入了解OpenSpec工作流和各阶段产物。

OpenSpec 对 AI 编程的意义

直接让 AI 写代码,最常见的问题是:你说的和 AI 理解的不一样,AI 改的比你预期的多(或少),改完之后也没有留下任何记录说明"为什么这样改"。

OpenSpec 通过三个阶段的产物来解决这些问题:

  • Proposal(提案):说清楚"为什么要做这个变更,要解决什么问题"。
  • Design(设计):说清楚"打算怎么做,涉及哪些模块,技术方案是什么"。
  • Task(任务):把设计拆成具体的任务清单,每一步可以单独执行和验证。

这三层是逐级约束的:AI 写代码时按 Task 列表执行,Task 的范围受 Design 限制,Design 的方向受 Proposal 限制。写代码之前先在文档层面对齐认知,同时留下变更记录。

用一张图来表示这个约束关系:

flowchart LR
    P["Proposal<br/>明确意图"] --> D["Design<br/>明确范围"]
    D --> T["Task<br/>明确步骤"]
    T --> C["Code<br/>受控编码"]

    classDef primary fill:#1A9090,stroke:#147272,color:#fff
    classDef secondary fill:#0d4f4f,stroke:#1A9090,color:#e0f0f0

    class P,D,T primary
    class C secondary

换个角度看,这三个阶段对应了团队里的三个角色:Proposal 相当于产品经理写需求,Design 相当于架构师出方案,Task 相当于开发拆排期。OpenSpec 把这三步写成文件,AI 每一步都照着文件来。

实战说明

下面拿 Java 电商项目 my-mall-app 跑一遍完整流程。

初始化项目

先进入项目目录,执行初始化:

cd my-mall-app
openspec init

初始化过程会做两件事:

  1. 让你选择当前使用的 AI 工具(Claude Code、Cursor 等),OpenSpec 会据此注入对应的 Slash Command。
  2. 创建 openspec/ 目录结构:
openspec/
  ├── specs/              # 真实来源(系统的行为规范)
  │   └── <domain>/
  │       └── spec.md
  ├── changes/            # 变更提案(每个变更一个文件夹)
  │   └── <change-name>/
  │       ├── proposal.md
  │       ├── design.md                                                                                      
  │       ├── tasks.md
  │       └── specs/      # 增量规范(记录变更内容)                                                         
  │           └── <domain>/                                       
  │               └── spec.md                                                                                
  └── config.yaml         # 项目配置(可选)

新功能:订单查询功能

现在需要给项目加一个订单查询接口。按照 OpenSpec 的工作流,分四步完成。

第一步:创建变更提案

在 AI 对话中输入:

/opsx:new add-order-query

告诉 AI 你想要什么:

添加订单查询功能,支持按订单号、用户ID、时间范围查询,支持分页,返回订单基本信息和商品明细。

第二步:快速生成全部文档

/opsx:ff

ff 是 Fast Forward 的缩写,一步生成 Proposal、Design 和 Task 三份文件。执行后,openspec/changes/add-order-query/ 目录下会出现:

add-order-query/
├── proposal.md     # 为什么做:描述变更动机和预期效果
├── design.md       # 怎么做:技术方案、涉及的模块、接口定义
├── tasks.md        # 做什么:逐条的任务清单
└── specs/          # 规格变更的增量描述

来看看各文件的内容(简化示意):

proposal.md — 变更意图

## 变更目标
为 my-mall-app 添加订单查询功能,让用户和管理员能够
按多种条件检索订单信息。

## 解决的问题
当前系统没有订单查询接口,运营和客服只能直接查数据库。

## 影响范围
- 新增 OrderQueryController
- 新增 OrderQueryService
- 新增 OrderQueryRepository 查询方法
- 不涉及订单创建和支付流程的修改

design.md — 技术方案

## 接口设计
GET /api/orders/query

请求参数:
- orderId (可选)
- userId (可选)
- startTime / endTime (可选)
- page / size (分页)

响应:Result<PageResult<OrderDTO>>

## 分层设计
- Controller:参数校验 + 调用 Service
- Service:组装查询条件,调用 Repository
- Repository:JPA Specification 动态查询

tasks.md — 任务清单

- [ ] 1. 创建 OrderQueryDTO 和 OrderItemDTO
- [ ] 2. 在 OrderRepository 中添加 Specification 查询方法
- [ ] 3. 实现 OrderQueryService
- [ ] 4. 实现 OrderQueryController
- [ ] 5. 编写单元测试
- [ ] 6. 编写接口集成测试

这个阶段还没有写一行业务代码。你可以逐个检查这些文件,看看 AI 的理解对不对。发现问题直接改文件就行,改的是文档不是代码,成本很低。

第三步:执行变更

确认文档没有问题后,执行:

/opsx:apply

AI 会按 tasks.md 的清单逐个执行。每完成一个任务,在对应条目上打勾。AI 的行为被 design.mdspecs/ 限定,它不会跑去改支付模块,也不会自己换数据库框架。因为完成了前面设计和任务的确认,实现环节不需要投入太多的注意力,可以并行去实现其他任务。

第四步:归档

功能开发完成、测试通过后:

/opsx:archive

这一步做两件事:把 changes/add-order-query/specs/ 中的规格增量合并到主 specs/ 目录,然后清理变更工作区。归档之后,specs/ 目录就反映了系统的最新状态,包括刚加上的订单查询功能。

整个新功能的开发流程可以用下图表示:

flowchart TD
    A["/opsx:new<br/>创建变更提案"] --> B["/opsx:ff<br/>生成 Proposal + Design + Task"]
    B --> C{"检查文档<br/>是否准确?"}
    C -- 有问题 --> D["手动修改文档"]
    D --> C
    C -- 确认无误 --> E["/opsx:apply<br/>AI 按任务清单编码"]
    E --> F{"测试是否<br/>通过?"}
    F -- 不通过 --> G["修复问题"]
    G --> F
    F -- 通过 --> H["/opsx:archive<br/>归档,合并到 specs/"]

    classDef action fill:#1A9090,stroke:#147272,color:#fff
    classDef decision fill:#0d4f4f,stroke:#1A9090,color:#e0f0f0
    classDef fix fill:#2a6b6b,stroke:#1A9090,color:#e0f0f0

    class A,B,E,H action
    class C,F decision
    class D,G fix

功能变更:给已有功能加缓存

新功能的流程比较直接,但实际工作中更常见的情况是对已有功能做变更。比如订单查询接口上线后发现查询慢,需要加 Redis 缓存。

对已有功能做变更,比从零开发多了一个前置步骤:先搞清楚当前实现是什么样的。

第一步:探索现有实现

/opsx:explore

这个命令让 AI 读当前代码,把指定功能的现有实现梳理出来。AI 会遍历订单查询相关的 Controller、Service、Repository,输出一份分析报告,包括:

  • 现有的接口定义和调用链路
  • 数据模型和表结构
  • 发现的潜在问题(比如 N+1 查询、缺少索引)
  • 建议的改动点

比如 AI 的分析报告可能指出:

OrderQueryService.queryOrders() 方法中存在 N+1 查询问题,每个订单会单独查询一次商品明细。建议使用 JOIN FETCH 或者引入缓存层。

第二步:交互确认变更方向

看完分析报告,你跟 AI 确认要做的事情:

给订单查询加 Redis 缓存。缓存策略:按查询条件 hash 作为 key,过期时间 5 分钟。订单状态变更时清除相关缓存。

第三步:推进变更

确认方向后,有两种方式往下走:

  • /opsx:continue:从当前状态一步步向前推进,适合变更比较复杂、需要分阶段讨论的情况。AI 会根据 explore 阶段的分析和你的确认,依次生成 Proposal、Design、Task,每一步都可以停下来调整。

  • /opsx:new:直接创建一个新的变更提案,然后走完整的 ff → apply → archive 流程。适合变更内容已经想清楚、不需要再反复讨论的情况。

选哪种看实际情况。如果你对要做的事很确定,直接 /opsx:new add-order-cache 然后一路往前走。如果还需要和 AI 反复讨论方案细节,就用 /opsx:continue

什么时候用 OpenSpec,什么时候不用

OpenSpec 管的是变更过程。但不是所有场景都需要走这套流程。

适合用 OpenSpec 的场景:

  • 涉及多个文件、多个模块的功能开发或变更
  • 需要修改已有业务逻辑,特别是不熟悉的模块
  • 团队协作场景,需要留下变更记录供他人 review
  • 复杂重构,需要先理清现状再动手

不需要 OpenSpec 的场景:

  • 修一个简单 bug,比如空指针、拼写错误
  • 纯样式调整或配置修改
  • 写一个独立的工具脚本
  • 快速验证一个想法的原型代码

判断标准很简单:如果你能用一句话说清楚要改什么、怎么改,直接让 AI 干就行。如果你需要先想一想"这个改动会影响哪些地方",那就该用 OpenSpec。

经验总结

用了一段时间 OpenSpec 之后,几点体会。

  • 先写文档再写代码,做起来比想的快。很多人一听"写 Proposal、写 Design"就觉得麻烦,但 /opsx:ff 一条命令就自动生成了,花几分钟看一遍确认没问题就行。这几分钟省下的是后面反复改代码的时间。
  • Explore 对老项目特别好用。接手别人的代码时,先用 /opsx:explore 让 AI 把相关模块梳理一遍,比自己翻代码快得多。AI 整理出来的报告,本身就是一份不错的模块文档。
  • 归档容易被忽略,但值得坚持做。功能做完后执行 archive,变更说明会进入 specs/ 目录。过几个月回头看某个功能为什么这样设计,打开 spec 文件就有答案,不用翻 git log 猜提交意图。
  • 小改动不用走 OpenSpec。修一个空指针、改一行配置,直接让 AI 干就行。判断哪些变更需要走流程、哪些不需要,这本身也是工程经验的一部分。

Niko-白色版.png