存量项目规格驱动开发:OpenSpec轻量落地与AI编码实践
1. 规格驱动开发为什么在存量项目里总是水土不服1.1 从一次真实的改造失败说起去年我接手了一个跑了快四年的订单系统代码量不算夸张大概十二万行左右Java 加 Spring Boot 的老组合业务逻辑散落在 Controller、Service 和一堆 Util 类里。当时团队想引入规格驱动开发Specification-Driven Development后面我统一叫 SDD目标很朴素让 AI 编码助手能真正理解业务意图而不是每次生成代码都靠猜。我们第一版选的是 Spec Kit 那套思路先写完整规格文档再让 AI 按规格生成实现。结果两周不到就卡住了。原因特别现实存量项目根本没有“干净的起点”。你没法要求一个已经上线三年的系统停下来先把所有历史逻辑补成规格文档再继续开发。业务方不会等你线上 bug 也不会等你。这就是我后来转向 OpenSpec 的直接原因。OpenSpec 的核心主张很明确——它不要求你从零开始写规格而是允许你在已有代码的基础上用更轻的方式把“规格”这件事嵌进日常开发流程里。它比 Spec Kit 轻轻在哪儿轻在它不追求规格的完整性和形式化而是追求规格的“可用性”和“增量性”。1.2 存量项目的三个死结我把那两周踩的坑总结了一下存量项目做规格驱动开发基本绕不开三个死结。第一个死结是规格与代码的时序错位。Spec Kit 那类工具默认的流程是“先规格、后代码”但存量项目的现实是“代码已经存在规格需要反向补”。你让 AI 先读规格再写代码它写出来的东西跟现有架构对不上因为现有架构里藏着大量没写进文档的隐性约定。第二个死结是规格粒度过细导致维护成本爆炸。我们当时试着给一个核心下单流程写规格写了三千多字涵盖参数校验、库存扣减、优惠计算、风控拦截。写完第二周业务改了一个优惠规则规格文档要同步改代码也要改两边还经常不一致。规格文档变成了第二个需要维护的代码库而且是没有编译器帮你检查的那种。第三个死结是AI 编码助手对规格的“理解偏差”。你写“用户下单后扣减库存”AI 可能理解成同步扣减也可能理解成异步扣减还可能理解成先冻结再扣减。规格文档里没写清楚的部分AI 会用自己的“常识”补全而它的常识往往跟你的系统约定不一样。OpenSpec 对这三个死结的回应方式我觉得是它最值得聊的地方。它不试图一次性解决所有问题而是把规格拆成“变更级别的规格片段”每个片段只描述这次改动涉及的部分而不是整个系统。这个思路上的转变是它比 Spec Kit 更适合存量项目的根本原因。1.3 OpenSpec 的定位规格是变更的副产品不是前置条件我理解 OpenSpec 的核心哲学是规格不是项目的前置文档而是变更过程的副产品。你不需要先有完整规格才能开发你可以在开发一个具体变更的时候顺手把这次变更的规格写出来而这个规格只对这次变更负责不承担描述整个系统的责任。这个定位带来的直接好处是规格的维护成本被摊薄到了每次变更里。你改一个优惠规则就写一个优惠规则的规格片段改完就归档。下次再改再写一个新的片段。历史片段不需要全部保持最新因为它们描述的是“当时那次变更的意图”而不是“系统当前的状态”。这跟 Spec Kit 那种“维护一份始终最新的完整规格”的思路完全不同。后者在存量项目里几乎不可行因为存量系统的状态是流动的你永远追不上它的变化速度。OpenSpec 承认这种流动性并且把规格设计成了同样流动的东西。2. OpenSpec 的核心机制拆解轻在哪里强在哪里2.1 变更规格片段OpenSpec 的基本工作单元OpenSpec 里最核心的概念是“变更规格片段”我习惯叫它 spec delta。一个 spec delta 描述的不是系统全貌而是“这次变更要做什么、影响哪些部分、验收标准是什么”。它的结构通常包含几个部分变更意图、涉及模块、行为描述、边界条件、验收用例。我拿一个真实例子来说明。假设我要给订单系统加一个“超时未支付自动取消”的功能。用 Spec Kit 的思路我得先找到订单模块的完整规格文档在里面补充这个功能的描述然后确保补充后的文档跟其他部分不冲突。用 OpenSpec 的思路我只需要新建一个 spec delta写清楚这次变更给订单增加超时取消能力涉及订单状态机和定时任务模块行为是“订单创建后三十分钟未支付则自动取消并释放库存”边界条件是“已支付订单不触发、已取消订单不重复取消”验收用例是三条具体场景。这个 delta 不需要跟任何现有文档合并它独立存在独立归档。AI 编码助手读这个 delta 的时候只需要理解这次变更的意图不需要理解整个订单系统。这就大幅降低了 AI 的理解负担也降低了规格的维护负担。2.2 与 Spec Kit 的对比形式化程度与适用场景我把两者的差异整理成了一张表方便你直观判断该选哪个。维度Spec KitOpenSpec规格范围系统级完整规格变更级规格片段起始要求需要较完整的规格基础可从任意存量代码起步维护成本高需持续同步低片段归档即可AI 理解负担重需读大量上下文轻只读本次变更适合场景新项目、绿地开发存量项目、增量迭代形式化程度高结构严谨中灵活务实规格与代码时序先规格后代码规格伴随代码这张表里最关键的一行是“规格与代码时序”。Spec Kit 要求你先有规格OpenSpec 允许你边写代码边补规格。对于存量项目后者才是可操作的。但我也得说句公道话OpenSpec 不是 Spec Kit 的替代品而是补充。如果你在做一个全新项目团队又愿意投入时间维护完整规格Spec Kit 那套严谨的流程是有价值的。但如果你面对的是一个已经跑了几年的系统OpenSpec 的轻量路线明显更现实。2.3 规格片段的生命周期从创建到归档OpenSpec 的 spec delta 有一个明确的生命周期我把它分成四个阶段。创建阶段你在开始一个变更之前或之中创建一个 delta 文件。这个文件不需要很完整可以先写意图和涉及模块细节边做边补。我通常会在写第一行代码之前先建这个文件哪怕只写三行字也比不写强。细化阶段随着你对变更的理解加深逐步补充行为描述、边界条件和验收用例。这个阶段最重要的是把“隐性约定”写出来。比如“库存扣减失败时订单状态回滚到待支付”这种约定在代码里可能只是一行 try-catch但不写进规格AI 下次生成代码时可能就忘了。执行阶段AI 编码助手读取 delta生成或修改代码。你根据验收用例验证结果。如果发现 delta 描述有误回头改 delta而不是直接改代码然后忘了改规格。归档阶段变更上线后delta 归档到历史目录。它不再需要保持最新因为它记录的是“那次变更的意图”而不是“系统当前状态”。下次有人问“为什么订单超时取消是三十分钟而不是十五分钟”翻历史 delta 就能找到当时的决策记录。这个生命周期里归档阶段是最容易被忽略但最有价值的。很多团队做规格驱动开发失败就是因为把规格当成了“必须永远最新的文档”而不是“变更的历史记录”。OpenSpec 把归档作为正式环节实际上是在帮你建立变更决策的可追溯性。3. 在存量项目里落地 OpenSpec 的完整实操3.1 环境准备与最小化接入OpenSpec 的接入成本很低这是它相比 Spec Kit 的另一个优势。你不需要改造现有项目结构也不需要引入新的构建流程。我通常的做法是在项目根目录建一个specs文件夹里面分active和archived两个子目录。active放进行中的 deltaarchived放已完成的。目录结构大概长这样project-root/ specs/ active/ 2024-06-order-timeout-cancel.md archived/ 2024-05-coupon-rule-update.md src/ ...每个 delta 文件用 Markdown 写命名带上日期和变更主题方便排序和检索。我不建议用复杂的编号体系日期加主题就够了简单直接。接入 AI 编码助手的时候你只需要在对话开始时把相关的 delta 文件内容贴进去或者如果助手支持读取项目文件直接让它读specs/active下的文件。我实测下来把 delta 控制在五百到一千字之间AI 的理解准确率最高。太短了信息不够太长了 AI 会抓不住重点。提示不要试图一次性把所有历史逻辑都补成 delta。只对你正在改动的部分写 delta历史部分让它保持原样。OpenSpec 的增量哲学核心就是“只规格化你正在碰的东西”。3.2 写一个高质量 spec delta 的五个要素我写了大概三十多个 delta 之后总结出一个高质量 delta 应该包含的五个要素。缺了任何一个AI 生成代码的准确率都会明显下降。第一变更意图。用一两句话说明这次变更要解决什么问题。不要写“优化订单模块”要写“解决订单超时未支付占用库存导致其他用户无法下单的问题”。意图越具体AI 越不容易跑偏。第二涉及模块。列出这次变更会碰到的代码模块或文件。比如“OrderService、OrderStatusEnum、InventoryService、OrderTimeoutJob”。这一步是给 AI 划范围防止它改到不该改的地方。第三行为描述。用“当……时系统应该……”的句式描述预期行为。比如“当订单创建超过三十分钟且状态仍为待支付时系统应将其状态改为已取消并调用库存服务释放占用库存”。这种句式比陈述句更精确因为它明确了触发条件和预期结果。第四边界条件。列出所有例外情况和特殊处理。比如“已支付订单不触发取消”“已取消订单不重复取消”“库存释放失败时记录告警日志但不阻塞取消流程”。边界条件是 AI 最容易忽略的部分也是线上 bug 的高发区。第五验收用例。用具体场景描述怎么验证这次变更。比如“创建一个订单等待三十一分钟检查订单状态为已取消且库存已释放”。验收用例不需要很 formal但必须具体到可以手动执行。我把这五个要素做成了一个检查清单每次写完 delta 都过一遍。刚开始会觉得麻烦写多了就成肌肉记忆了。3.3 让 AI 编码助手正确读取规格的实操技巧OpenSpec 本身不绑定任何特定的 AI 编码助手它只是一套规格组织方式。但不同的助手对规格的读取效果差异很大我踩过不少坑分享几个实用技巧。第一个技巧是把 delta 放在对话的最前面。很多助手对上下文有位置偏好放在前面的内容权重更高。我通常会把 delta 内容作为第一条消息发出去然后再提具体的编码要求。第二个技巧是明确告诉助手哪些部分不要改。比如“只修改 OrderService 和 OrderTimeoutJob不要动 OrderController 和数据库 schema”。存量项目里AI 乱改无关代码是最大的风险之一明确边界能大幅降低这种风险。第三个技巧是要求助手先复述规格再写代码。我会让它先用三句话总结它理解的变更意图和边界条件确认无误后再生成代码。这一步能提前发现理解偏差比生成完再改省事得多。第四个技巧是把验收用例作为验证提示。代码生成后我会把验收用例再贴一遍让助手自己检查生成的代码是否满足这些用例。虽然它不一定能完全自查但至少能发现一些明显遗漏。注意不要指望 AI 完全按规格执行。规格的作用是缩小它的发挥空间不是消除它的判断。你仍然需要 review 生成的代码尤其是边界条件部分。3.4 存量项目特有的适配策略存量项目用 OpenSpec有几个跟新项目不一样的地方我单独拎出来说。策略一从“最痛的点”开始不要全面铺开。我见过一些团队一上来就想给所有模块写 delta结果两周就放弃了。正确的做法是选一个最近正在改、痛点最明显的模块先跑通一个完整流程让团队看到效果再逐步扩展。策略二允许 delta 与代码不一致但记录不一致的原因。存量项目里有时候代码因为历史原因没法完全按 delta 执行。这时候不要强行改代码去对齐 delta而是在 delta 里加一段“实现说明”记录为什么代码跟规格有偏差。这个记录本身就是有价值的决策文档。策略三把 delta 当作沟通工具不只是 AI 输入。我们团队后来发现delta 在 code review 和需求对齐时也很有用。产品经理看 delta 能确认理解一致reviewer 看 delta 能快速理解变更意图。它变成了一个多用途的轻量文档。策略四定期清理 active 目录。active 目录里的 delta 如果超过两周还没归档要么是变更卡住了要么是忘了归档。我每周五会花十分钟过一遍 active 目录该归档的归档该关闭的关闭。保持 active 目录干净AI 读取时才不会混淆。4. 常见问题与排查技巧实录4.1 AI 生成代码偏离规格的四种典型情况用 OpenSpec 的过程中AI 偏离规格是常态关键是能快速识别偏离类型并对症下药。我整理了四种最常见的情况。情况一意图理解正确但实现方式不符。比如规格说“异步释放库存”AI 写成了同步调用。这种偏离通常是因为规格里没写清楚技术约束。解决办法是在 delta 里补充技术约束说明比如“库存释放必须通过消息队列异步执行不得同步调用”。情况二边界条件遗漏。规格里写了三条边界条件AI 只实现了两条。这种偏离最危险因为遗漏的往往是异常路径。解决办法是在验收用例里把边界条件也写成具体场景让 AI 在自查时能覆盖到。情况三改动了规格范围外的代码。AI 顺手“优化”了无关模块。这种偏离在存量项目里风险很高。解决办法是在对话里明确列出允许修改的文件清单并强调“除此之外的文件一律不动”。情况四规格本身有歧义。AI 的理解没错是规格写得不清楚。这种情况最值得高兴因为它暴露了规格的质量问题。解决办法是回头改 delta把歧义消除然后重新生成。我把这四种情况做成了一个速查表贴在团队 wiki 上新人遇到问题时先对照排查。偏离类型典型表现排查方向解决动作实现方式不符同步写成异步、批量写成单条检查技术约束是否写明补充技术约束到 delta边界条件遗漏异常路径未处理检查验收用例是否覆盖补充边界场景到用例范围外改动无关文件被修改检查是否明确文件清单明确允许修改范围规格歧义AI 理解与预期不同检查行为描述是否精确消除歧义后重新生成4.2 规格维护的常见误区我在推广 OpenSpec 的过程中发现团队最容易掉进三个误区。误区一把 delta 写成完整设计文档。有人觉得写得越详细越好结果一个 delta 写了三千字AI 反而抓不住重点。delta 的目标是“够用就好”不是“面面俱到”。我建议控制在五百到一千字超过一千五百字就该考虑拆分了。误区二delta 归档后就不再更新。归档的 delta 确实不需要保持最新但如果发现归档 delta 里有错误信息还是应该修正。归档不等于冻结它只是不再主动维护。发现错误时加个修正说明就行不用重写。误区三用 delta 替代代码注释。delta 描述的是变更意图代码注释描述的是实现细节两者不能互相替代。我见过有人把 delta 写得很细代码里一行注释都没有结果新人读代码还是看不懂。该写注释的地方还是要写。4.3 团队协作中的规格同步问题多人协作时delta 的同步是个现实问题。我们团队试过几种方案最后稳定下来的做法是delta 文件跟代码一起提交到版本库放在specs/active目录下。谁创建谁维护变更合并时 delta 也一起合并。这样做的好处是 delta 和代码的版本天然对齐不会出现“代码已经合并但规格还在另一个分支”的情况。坏处是 delta 文件也会产生合并冲突不过因为 delta 通常不大冲突解决起来比代码冲突简单得多。另一个经验是code review 时把 delta 作为 review 的一部分。reviewer 先看 delta 理解意图再看代码检查实现。我们后来发现带 delta 的 review 比不带 delta 的 review 平均快百分之二十左右因为 reviewer 不用再靠猜来理解变更意图。提示如果团队用 issue 跟踪系统可以在 issue 里链接对应的 delta 文件路径。这样从 issue 到规格到代码的链路是完整的追溯起来很方便。4.4 什么情况下不该用 OpenSpec说了这么多 OpenSpec 的好话也得说说它不适合的场景。如果你在做的是一个全新项目团队又有精力维护完整规格Spec Kit 那套严谨流程可能更适合。OpenSpec 的轻量是以“放弃系统级规格完整性”为代价的新项目没有历史包袱没必要放弃这个完整性。如果你的项目变更频率极低比如一年就改一两次那写 delta 的投入产出比也不高。OpenSpec 的价值在频繁变更的场景下才明显。如果你的团队完全没有写文档的习惯那引入 OpenSpec 之前得先解决这个习惯问题。工具再好没人用也是白搭。我的建议是先从一个小的、痛的变更开始让团队体验到 delta 带来的好处再逐步推广。5. 我个人的实操体会与后续扩展思路5.1 用了半年之后我最大的三个感受第一个感受是规格的价值不在于“写”而在于“读”。写 delta 的时候你可能觉得麻烦但当 AI 编码助手准确理解你的意图、当 reviewer 快速 get 到变更重点、当三个月后你翻归档 delta 回忆起当时的决策你会发现这些“读”的场景才是 delta 真正产生价值的地方。第二个感受是轻量是一种设计选择不是妥协。OpenSpec 比 Spec Kit 轻不是因为它做不到更严谨而是因为它选择了更适合存量项目的严谨程度。在存量项目里过度严谨的规格反而会成为负担轻量才是可持续的。第三个感受是规格驱动开发的瓶颈从来不是工具而是习惯。OpenSpec 的接入成本很低但让团队养成“改代码前先写 delta”的习惯花了我们差不多两个月。工具能降低门槛但跨过门槛还得靠人。5.2 后续可以尝试的扩展方向如果你已经把 OpenSpec 的基本流程跑通了有几个扩展方向可以试试。一是把 delta 跟自动化测试关联起来。delta 里的验收用例可以转化成测试用例的骨架让 AI 生成测试代码时也有规格可依。我们正在试这个方向初步效果不错。二是建立 delta 的检索机制。归档 delta 多了之后怎么快速找到相关的历史决策是个问题。我们试过用简单的全文搜索也试过给 delta 打标签。标签方案效果更好但需要团队约定标签体系。三是把 delta 作为新人 onboarding 的材料。新人读代码之前先读最近几个月的 delta能快速理解系统最近的演进方向。这比让新人直接啃代码效率高得多。四是探索 delta 与 API 文档的联动。如果变更涉及接口改动delta 里的行为描述可以同步到 API 文档里。这个方向我们还没深入但觉得有潜力。5.3 给准备尝试的团队的最后几句实在话如果你看到这里觉得 OpenSpec 这套思路值得一试我给你几句实在话。别一上来就追求完美。第一个 delta 写得烂没关系写出来就比不写强。我第一个 delta 现在回头看简直没法看但它让我开始了。别指望 AI 完全按规格执行。规格是缩小 AI 发挥空间的工具不是控制 AI 的遥控器。你仍然需要 review仍然需要判断仍然需要对结果负责。别把 OpenSpec 当成银弹。它解决的是“存量项目里规格驱动开发太重”这个问题不解决“团队不写文档”“AI 不靠谱”“需求老变”这些更根本的问题。但它确实能让规格这件事在存量项目里变得可行而可行比完美重要得多。