
最近好几个朋友来找我聊vibe coding聊到最后都会冒出同一个问题AI写代码确实快但为什么项目越写越乱甚至到了自己都不敢改代码的地步如果你也有这种感受那这篇内容大概率对你有用。我过去半年一直在折腾vibe coding的各种玩法把AI当作结对编程搭档也踩了不少坑后来逐渐把重心转向spec-driven的方案也就是“先写规格再让AI照着实现”。这篇文章会把vibe coding的典型痛点、spec-driven的核心思路、完整实操流程以及我记录下的问题排查经验一次性讲清楚适合正在用AI辅助开发、又苦于代码失控的开发者参考。1. 先搞清楚vibe coding到底哪里让人又爱又恨1.1 vibe coding的真实体验爽是爽翻车也是真的快所谓的vibe coding简单说就是让AI进入高比例自主生成代码的状态开发者更多负责描述想法、审查结果、修正方向而不是逐行手写逻辑。你给一句“帮我写个读取温湿度传感器的驱动”AI能立刻给你吐出一套完整实现包括寄存器配置、数据解析、错误重试机制乍一看还挺像模像样。我最初用的时候确实是爽尤其是做嵌入式原型验证原来写一个外设驱动至少要半天现在十分钟就能跑起来乐高一样的拼接体验让人上瘾。但问题也随之而来AI生成代码的“舒适区”是模式化逻辑一旦项目涉及复杂状态机、并发控制、硬件时序约束它就开始出现各种想当然的假设而这些假设往往是事后才暴露的因为在写代码的那一刻你根本不会去逐行校验它到底为什么这么写。我统计过自己一个月的实际工作流用vibe coding写的代码里大概有三分之一的时间花在了“修AI生成代码产生的边界问题”上而不是在推进功能本身。“快”变成了错觉因为你在辨别哪些代码是能用的、哪些是幻觉出来的反而消耗了大量精力。1.2 痛点的本质代码有“感觉”产品没“共识”再往深一层看vibe coding最大的问题其实是缺少一个稳定的约束锚点。普通开发流程里需求文档、接口定义、验收标准这些产物天然就是约束你按着文档写代码再怎么跑偏也能拉回来。但vibe coding里对话记录和AI的记忆就是全部的上下文而它们都是会漂移的。你今天下午和AI说“这个字段表示毫秒”隔两天再开话题它可能就默认是秒了这种上下文丢失几乎每隔几次迭代就要出现一次。更麻烦的是多人协作的时候“感觉”是无法同步的。你用vibe coding写了一个模块同事拿到代码根本不知道原始需求是什么AI当初为什么选这个方案、什么边界条件是特意处理的全都藏在对话历史里他要完整考古一遍才能动手改。所以我把vibe coding从小项目延展到稍大一点的项目之后发现自己真正缺的不是写代码的速度而是一个能把“我要什么”讲清楚、并且能被机器和人都确认的中间层。1.3 不是要放弃vibe coding而是要给它戴上“笼头”先说结论vibe coding本身没有错错的是裸奔式使用。AI需要被约束而spec-driven正是目前我用下来最靠谱的约束方式。它不要求你回到传统开发那种“万事写文档”的重流程而是要求你在让AI动手生成代码之前先把行为契约、输入输出、异常情况、验收标准这些关键要素写清楚。我现在的开发习惯已经固定成两步走第一步用一份spec把需求和边界定义清楚第二步再把spec喂给vibe coding工具让它严格按照规格实现。这么做之后AI的自由发挥被限制在了一个合理的范围里而我和同事之间的沟通成本也大幅下降因为大家讨论的是规格而不是“AI怎么想的”。2. spec-driven的核心思路与方案设计2.1 一句话讲清spec-driven到底是什么简单来说spec-driven就是“先写规格说明书再写实现代码”但这里的规格不是那种几百页的传统需求文档而是面向行为、可验证、无歧义的技术契约。传统的需求文档关注“业务上要什么”比如“用户能查看设备实时温度”而spec-driven的规格更接近工程设计里的接口定义要写清楚输入是什么、输出是什么、什么情况下会报错、错误处理路径是怎样的。它的读者有两类一类是AI用来生成符合规格的代码一类是人用来评审、验收、维护。我经常拿做菜打比方。传统需求文档像是告诉厨师“做一道好吃的鱼”而spec是“准备一条500克左右的鲈鱼蒸8分钟出锅淋上热油和蒸鱼豉油要求鱼肉不散、无腥味”。后者才是可执行、可检验的标准。2.2 spec-driven方案选型背后我为什么选它当初在纠结要不要引入spec-driven时我对比过三个方向一是继续裸用vibe coding靠对话约束二是回到传统的测试驱动开发先写测试再实现三才是spec-driven。裸用vibe coding的问题前面已经说了上下文漂移、无法协作、边界失控。传统TDD其实是个好方案但对AI开发有个尴尬的地方测试本身也要人来写而且写测试的过程往往比写实现还费脑很多人根本坚持不下去。spec-driven正好卡在两者之间它的规格文档既可以当作生成代码的输入又可以当作写测试的依据一举两得。从实操效率看写一份规格的时间成本大概是我直接写代码的三分之一但能把返工率从百分之六十降到百分之二十左右。这个投资回报率非常可观尤其是对于需要迭代的项目省下来的是后期调试和重构的时间。2.3 spec-driven方案的核心构成模块一个完整的spec-driven开发闭环由四部分构成行为规格定义功能的输入、输出、异常处理、边界条件这是核心部分。AI生成代码时主要依赖这部分内容。验收标准明确什么样的实现算是完成通常用可量化的指标描述比如“解析结果误差不超过0.5摄氏度”“超过3次重试后返回超时错误”等。非功能约束写下性能要求、内存限制、依赖限制等防止AI在实现时引入你不需要的东西比如嵌入式场景常见的实时性要求。变更记录维护规格的版本历史每次需求变化时先改规格再改代码这样项目的演进过程完全可追溯。这四部分不是要都写到最细而是根据项目复杂程度灵活取舍。我自己做嵌入式小项目时重点写行为规格和验收标准非功能约束只写硬指标变更记录用最简单的Changelog形式。2.4 什么样的项目最适合用spec-driven根据我这半年的实践有几种项目类型用spec-driven收益特别大硬件相关开发尤其是嵌入式固件。因为硬件逻辑一旦写错轻则数据不对重则烧板子必须前置定义清楚。接口封装类工作。比如给你的代码库设计对外API或者驱动模块给上层提供接口规格就等于接口文档一举两得。多人协作的AI辅助开发。规格是大家共同的基准线谁改了什么、为什么改看规格历史一清二楚。需要长期维护的模块。AI生成的代码如果不做约束三个月后回头看跟天书一样有规格至少能帮你快速理解设计意图。反之如果只是临时脚本、一次性原型验证、极其简单的工具函数那直接用vibe coding裸写就行了引入spec反而是过度设计。3. 从零搭建一套spec-driven的完整实操流程3.1 第一步先写一个麻雀虽小五脏俱全的spec模板直接把我用到的mini版规格模板放出来大家可以照着改格式不需要花哨重点是把该锁死的信息锁死。# 功能规格温湿度传感器数据读取模块 ## 1. 功能概述 本模块负责通过 I2C 接口读取 SHT30 温湿度传感器数据 转换为物理量后通过回调上报。 ## 2. 接口定义 - 初始化函数void sht30_init(I2C_HandleTypeDef *hi2c) - 读取函数int sht30_read_temperature(float *temp, float *humidity) - 输入无 - 输出temp 为温度值(摄氏度)humidity 为湿度值(百分比) - 返回0 表示成功-1 表示 I2C 通信失败-2 表示数据校验失败 ## 3. 行为规则 - 读取超时时间固定为 100ms - I2C 连续失败 3 次后返回 -1 - 校验失败时不修改输出参数的值 - 温度计算误差不超过 ±0.5 摄氏度 ## 4. 边界与异常处理 - 传感器未应答返回 -1 - 校验和错误丢弃本次数据返回 -2 - 参数为空指针直接返回 -3 ## 5. 验收标准 - 使用模拟 I2C 数据验证时正确解析温度、湿度数值 - 模拟断线场景能稳定返回 -1 - 连续读取 1000 次无死锁或异常崩溃这份模板看着简单但实际写的时候有几个坑要注意。一是输出参数别写“成功时赋值”要把失败场景下参数的行为也定义清楚因为这个直接决定调用方如何防御性编程。二是错误码最好统一规划别让AI自己发明错误码否则不同的模块之间错误码纠缠在一起排错会让人崩溃。3.2 第二步把spec喂给vibe coding工具的正确话术写好了spec接下来就是让AI干活。我试过直接丢整篇markdown给AI也试过拆成片段逐步喂实测下来拆成功能点逐步喂的效果最稳。比如上面的规格文件我不会一次性全都给AI而是先给“接口定义”部分让它写出函数签名和基础框架然后追加“行为规则”让它填充重试逻辑和错误处理最后给“边界与异常处理”让它补齐防御性代码。这个过程用一句话引导就行“请按照附录spec实现以下内容严格遵守接口定义和错误码约定。不确定的地方先提出疑问不要自行假设。”特别强调“不确定的地方先提出疑问”这半句因为AI最容易犯的毛病就是遇到规格没写清楚的地方自动脑补一个合理值。它脑补的时候往往会跟你的真实需求发生偏差与其事后返工不如让它先开口问。还有一个细节写spec的时候要刻意留一部分内容不写比如具体用什么校准算法把这个留给AI去发挥。规格不是要把AI当无脑打字机恰恰相反要给它留一点可选空间这样它能给出的方案往往比你预设的更好。3.3 第三步规格驱动的验证闭环代码生成完不等于闭环结束还需要验证。我现在的验证流程分三层第一层是让AI自己对照规格做一次审查具体做法是把spec和代码一起发回去请它对每一项行为规则核查实现是否一致输出一张对照表。这个方法很实用能把大部分明显漂移抓出来。第二层是拿规格写测试。怎么从规格快速生成测试我一般用两种方式一是让AI按照验收标准生成单元测试二是自己手动覆盖几个关键边界。比如在上面的例子里“参数为空指针返回-3”这条就是必测项几乎是白送的测试用例。第三层是代码评审。评审时不用去抠AI生成了什么代码而是看它有没有偏离规格。我们有句口头禅“不带规格看代码就是耍牛氓。”同事之间评审时手里一定要握着最新的规格文档逐条比对这个习惯建立之后评审质量提升极快。3.4 规格与代码的同步演进策略实际操作中需求变更是家常便饭。我强烈建议定一个死规矩任何需求变更先改spec再谈改代码。哪怕只是把一个字段名从temp改成temperature都应该先更新规格再用“请根据最新spec更新实现”的指令让AI做同步。这么做的原因很简单规格是唯一的真相源。如果反过来先在对话里改了需求AI改了代码然后你忘了更新规格那下次任何人包括AI自己再读这个模块看到的分裂状态会直接让上下文混乱。一次两次可能无所谓积累多了代码库就变成了一个思路上的“缝合怪”。我在Git项目里会把spec文件放在docs/specs目录下每个模块对应一份markdown和源码一起提交这样规格和代码始终在同一个提交记录里追溯起来一目了然。3.5 工具链搭配markdown就够了别盲目上重型平台关于spec-driven的工具选型我先把结论放在前面不要一上来就上一堆平台化工具最朴素的markdown加Git版本控制已经能覆盖大多数项目需求了。我试过用Notion管理规格也试过用专门的API设计工具但最后都回到了本地markdown文件。原因是规格文档最重要的是贴近代码、便于diff、方便AI读取这三样是云端文档工具很难同时满足的。markdown文件天然满足这些要求可以放在代码库里面可以查看历史变更可以直接被AI读取。如果你的项目确实需要更结构化的规格管理可以考虑给markdown增加一些约定比如固定模板、统一字段命名、用表格写接口参数而不是引入新工具。工具越多维护负担越重最终结果往往是规格文档没人更新又回到了裸code的状态。3.6 嵌入式场景下的spec-driven实践补充因为热词里有嵌入式vibe coding这里补充一下我在这类项目里整理出来的特殊经验。嵌入式项目最棘手的是硬件约束比如寄存器时序、DMA通道分配、Flash大小限制。这些信息光靠AI的理解往往是模糊的必须在规格里明确写清楚硬限制。例如“本芯片Flash共64KB固件当前占用约40KB新模块不得超过8KB”这类信息AI看到之后就不会写出奢侈的查表算法。另外嵌入式开发里中断上下文和主循环上下文的区别也是AI重灾区。AI经常默认所有代码运行在同一个上下文中拿memset、printf直接放进中断服务函数里。为了规避这个问题我通常在规格里加一条“中断服务函数中禁止调用阻塞函数和动态内存分配”效果非常显著。硬件相关的调试往往是规格写不全面的地方所以嵌入式领域用spec-driven时还需要特别注意“硬件行为不确定的地方要先跑最小验证代码确认后再写进规格”。4. 常见问题与排查技巧实录4.1 问题一AI无视规格依旧自行发挥怎么办这是我最开始遇到、也是几乎每个转spec-driven的人都会撞上的问题。明明规格里写了“超时100ms”AI生成的代码里却变成了“超时500ms”而且注释还写得理直气壮。排查思路大概是这样的一是检查喂给AI的上下文是否太长导致它遗漏了规格细节。对话窗口内容一大AI的局部注意力容易集中在最后几段text前面的设定就容易丢。处理办法是把规格拆小一次只喂一个模块的规格。二是试试在代码块注释里直接嵌入关键约定比如在函数定义的注释里重复一遍“读取超时固定为100ms”让AI在生成代码的时候就近看到约束。三是用“请逐条对比规格并指出不一致之处”的指令做二次审查强制AI自己检查矛盾点。这三板斧下来九成以上的无视规格问题都能解决。4.2 问题二规格写太细AI变得束手束脚反而低效另一条很容易走的极端是规格写得事无巨细连循环用什么变量名、函数内部先做什么后做什么都规定清楚结果AI从“生成器”变成了“翻译器”生成的代码质量反而更差。后来我把规格分成两个等级一级是“契约级”比如接口定义、错误码、边界行为必须严格遵循二级是“建议级”比如用什么排序算法、怎么组织文件结构允许AI自由选择。遇到“建议级”内容我会在规格里用“可以”“建议”之类的措辞遇到“契约级”内容用“必须”“禁止”之类的强约束词。AI对这两类词的分辨能力比我预想中强值得充分利用。4.3 问题三规格和代码不一致该信谁项目进行到中后期常常会出现“规格是旧的代码是新的”这种分裂状态。这时候最忌讳的是直接改代码去凑规格正确做法是先确认哪个版本的意图才是你真实的意图。我的操作习惯是当发现不一致的时候先翻Git提交记录看规格和代码谁先被修改。如果代码先改了说明当时肯定有一个未记录的需求变更需要补进规格如果规格先改了说明AI或人工实现没跟上需要催代码更新。等到规格和代码重新对齐之后再决定下一步动作。这个流程虽然听起来绕了一下但能避免很多拍脑袋决策导致的反复。4.4 问题四怎么判断规格写得“够了”不少朋友问我到底写到什么程度算“够了”能开始动工了。我自己的标准是当看到这份规格的人或AI能在不看其他任何资料的条件下独立做出符合预期的实现就算够了。这里又得回到我前面的观点规格不是要准确到每个if语句的分支而是要准确到“无法产生第二种理解”。一个实用的判断方法是“角色扮演法”假装自己完全不懂项目背景只拿规格问自己几个问题——输入合法吗非法输入怎么处理网络中断怎么办数据异常怎么办如果每一个问题都能从规格里找到明确答案那就可以放心交给AI去实现了。4.5 v2个进度难解的坑额外补充再记录一个我刚开始实践时踩的比较深的坑写spec往往没有写代码有成就感容易写着写着偷懒想跳过步骤直接让AI干。这种时候强烈建议把“spec先行的原则”当作纪律来执行哪怕只写一个十五分钟的迷你规格也不要直接裸聊需求。一旦允许了“这次先不写spec直接改吧”的例外接下来就会有一有二最后整个规范就名存实亡了。我自己的办法是给每个项目的spec文件设置一个“未完成禁止输入”的红线检查spec没有更新完就绝不把新需求发给AI这种“强制仪式感”把产出规范和实际使用了硬绑定起来。5. 关于工具选型和扩展的一些个人心得5.1 用AI辅助写spec本身的效率就很惊人其实spec文档的撰写也可以交给AI来辅助生成。我长期用的一套手法是先用vibe coding的方式跟AI聊一遍需求让它生成一份draft spec然后我再人工逐个字段调整、补边界、加约束。毕竟人对硬件限制和业务规则的敏感度是AI比不了的但AI比人强的是语法组织能力和查漏补缺的速度两者结合效率最高。举个例子有一次我要写一个串口协议解析模块的规格如果自己从零写至少要一个小时。我先让AI根据我口述的协议帧格式生成草案然后我再花二十分钟把边界条件、错误码、校验方式补充完整整体半小时搞定而且覆盖到的细节比纯手写更全。5.2 spec-driven和AI工具的未来组合潜力我目前正在尝试的一个方向是把spec文件本身作为项目全局上下文的“锚点”让AI在每次会话开始时自动加载对应模块的规格文件然后基于规格展开工作。现在很多AI编码工具已经支持项目级上下文绑定把规范挂进去之后即使隔了很长一段时间再回来继续做AI也保持着对模块结构的正确认知。在嵌入式这种对正确性要求高的领域我进一步在尝试把spec转成机器可校验的格式例如直接转成头文件里的静态断言、寄存器定义等让规范直接进入编译期检查。这个方向目前还在实验但效果已经让我坚定了spec-driven路线值得持续投入。6. 最后分享几条实操中养成的硬规矩第一永远让规格先走一步。哪怕要改一行代码也先在规格里改掉对应的描述。第二不要信任AI的“我觉得应该”。只要规格里没有明确规定的内容AI自作主张的实现一律视为bug要么改代码要么改规格并做好记录。第三定时把规格文件当作rebase基准。每次代码经过较多增删之后我会专门安排一次“规格对齐日”把spec和当前实现彻底核对一遍消除隐形漂移。第四在团队协作中评审任何AI生成的代码前先问一句“这代码对应的spec是哪个版本”。答不上来的评审直接驳回流程到目前为止运转得还算顺畅。踩过这么多坑之后我的切身体会就是vibe coding更像是开一辆动力强悍的车跑赛道是爽但没有路标和护栏很快就得翻。spec不是用来限制AI的枷锁而是给AI和人都划清了路线的导航它在保住速度的同时让你知道自己正在往哪里去。