上周代码评审,A 同学兴冲冲地打开一个仓库,说他花一个下午用 Vibe Coding 写完了一整套库存扣减接口。界面整洁,代码风格统一,单元测试也全绿,会议室里顿时一阵兴奋。可当我顺口问了一句“两个门店同时卖同一件库存商品时怎么保证不超卖”,他愣了一下,然后开始翻看 AI 生成的代码,翻了半天也没有找到对应的锁或事务处理。那个瞬间我就知道,这个“效率奇迹”上了生产之后大概率要出事。
这不是我故意唱反调。Vibe Coding 现在确实火得一塌糊涂,身边几乎每天都有人跟我说,用自然语言描述需求,AI 就能把代码写出来,省下的时间能按天算。我承认它在原型验证、脚本填充、胶水代码这些场景下效率惊人。但真正到了“要交付给业务长期跑”的系统里,我见过太多表面光鲜、内核却无法自洽的 AI 生成代码。所谓无法自洽,不是指几个小 bug,而是整个系统内部的逻辑相互打架:这个模块认为状态长这个样子,那个模块认为状态长那个样子;这条路径默认数据一定存在,那条路径却在拼命删除数据;需求里明明说过边界条件,生成代码却像完全失忆一样。
今天我不打算泛泛而谈,想把代码评审和故障排查中反复遇到的几类“自洽性逻辑漏洞”摊开来讲明白。如果你正在用 AI 编程工具提效,或者正准备跟风拥抱 Vibe Coding,那么这篇文章值得你花十分钟看完。它能帮你省下的是上线后熬夜查事故的时间。
1. Vibe Coding 热得发烫,但交付现场凉得很快
1.1 大家都在欢天喜地地吹什么
现在聊 Vibe Coding,几乎绕不开这几个口号:不用懂语法也能写程序、一个下午顶过去一周、AI 已经把程序员从重复劳动中解放出来了。我确实在不少圈子里看到,有人用很简短的自然语言描述,就生成了一段看起来相当专业的业务代码,录屏发到网上,评论区一片“太强了”。
这些演示给了很多人一个错觉:软件开发的核心困难已经被 AI 抹平了,剩下的只是“开口说话”。但稍微有点交付经验的人都知道,写代码从来不是工程里最难的环节。真正难的是把需求翻译成无歧义的逻辑,是在无数个“如果”分支里保持状态的一致性,是让多个模块在半年后仍然彼此兼容。
Vibe Coding 把“写代码”这个环节变成了快进模式,却一点都没有降低前面说的那些工程难度。它反而把难度悄悄转移了:以前你得靠写代码过程中不断思考来自洽,现在你对着模型说完话,它用一种极端自信的口吻把代码吐出来,仿佛逻辑已经闭环,于是很多人在这一步就失去了警惕。
1.2 “能跑”和“能交付”之间隔着一条巨大的自洽性鸿沟
我在评审和排查中反复看到一种现象:AI 生成代码在 demo 数据上跑得非常好,演示路径永远走同一条,参数永远是那几个,顺序永远不变。一旦把数据换成稍微复杂一点的真实场景,各种逻辑漏洞就全冒出来了。
原因很简单,程序的“能跑”只是在某个特定输入子集上的行为正确。而工程意义上的“能交付”,要求的是在整个状态空间上行为自洽:任意合法输入都有合理输出,非法输入能被识别并兜底,并发发生时数据不被撕碎,多个模块组合后不会相互推翻彼此的假设。Vibe Coding 生成的代码恰恰最缺的就是这种全局自洽性,因为它每次生成的都是“局部正确”的片段。
打个比方,单看一块瓷砖,花纹漂亮,边缘整齐。但铺满一整面墙之后,你才发现不同批次的瓷砖色差严重,花纹对不上,边角拼缝宽窄不一。AI 写代码也是这么个德行,局部看哪哪好,整体看处处裂。
2. 自洽性漏洞的四种典型形态:接口、状态、边界、语义
2.1 接口契约的“三张脸”
在真实系统里,模块之间靠接口通信。接口的本质是契约:我输入什么、你保证返回什么、异常怎么表达、数据格式长什么样。AI 在生成单个模块的时候,往往会根据自己的“脑补”定义一个接口,但它没法保证这个接口和你其他模块的预期完全一致。
我遇到过一个特别典型的例子。A 同学让 AI 生成订单服务,里面一个方法返回订单状态。同一个项目里,另一个模块是另一位同事用 AI 生成的,它把状态读取出来之后,直接用三重判断去比较字符串。结果实际跑起来才发现,订单服务返回的状态是“已支付”,而消费方代码里等的是“PAID”。数据在输出端看起来很正常,到消费端就完全不认。这不是什么高深错误,就是契约对不上。
这种“三张脸”的问题最阴险的地方在于:不在接口层面统一数据契约,类型系统很难拦得住全部。AI 各自为战生成代码,只要你没有提前定义好统一的 DTO、枚举、状态机,它们就会各写各的,看起来逻辑都对,拼在一起就互不兼容。
2.2 状态管理的“多头马车”
系统复杂到一定程度,状态一致性就是生命线。库存扣减、订单流转、支付状态、会话登录态,这些一旦出现多头写入,几乎必然产生矛盾。
Vibe Coding 下特别容易出现这种局面:开发者对 AI 说“给库存模块加一个缓存,减少数据库压力”,AI 就加了一个本地缓存。过一会儿又对 AI 说“库存扣减后要立刻同步给前端展示”,AI 就在另一个文件里直接改了库存表。缓存、数据库、前端展示三条路径各走各的,没有任何一个模块负责协调它们之间的关系。等并发量一上来,前端显示库存还有 3 件,数据库里已经变成负数,缓存里还躺着旧值。
这类逻辑漏洞说白了不是 AI 不会写代码,而是它根本没有能力在一个全局视角下管理状态的读写顺序、失效时机和冲突解决策略。你让它补一个功能,它就只盯着那个功能,不会主动回头审视整个状态流的自洽性。
2.3 边界条件与异常路径的“集体失明”
AI 生成代码时最让人头大的问题,就是它对“正常路径”极其擅长,对“边界和异常”极其敷衍。你跟它说“写一个解析用户上传 CSV 文件的函数”,它大开大合,很快就能写出读取、遍历、解析正常行的代码。可你翻到函数结尾,往往会发现异常处理非常薄弱:文件格式不对怎么办、某一列缺值怎么办、文件超大怎么办、编码不是 UTF-8 怎么办,很多情况下都是一带而过,甚至干脆 throw new RuntimeException 了事。
生产环境不跟你讲情面,用户不会老老实实按你 AI 训练数据里的“标准输入”来操作。空值、越界、重复提交、并发竞争、下游超时,哪一条都能把看似优雅的代码打成筛子。我在排查事故的时候经常发现,AI 生成代码的异常路径更像是“装饰品”:try-catch 结构在,能兜住什么、兜不住什么,作者完全没概念。因为 AI 生成代码的时候只是看到了“要有异常处理”这个 pattern,而不理解这里的业务到底可能出什么错、应该怎么降级。
2.4 自然语言歧义带来的“语义漂移”
还有一个很多人忽略的隐患,就是需求描述本身是自然语言,天然带着歧义。比如“用户下单后取消订单”,这句话至少可以拆成三种含义:用户支付前取消、支付后申请退款、超时自动关闭。你说给 AI 听的时候可能自己心里想的是其中一种,AI 帮你写的时候按另一种理解生成,从代码层面看上去似乎是在实现“取消订单”,但和你心里的业务规则完全两码事。
这个“语义漂移”在 Vibe Coding 的多轮对话中尤其明显。你改了三轮需求,AI 前两轮实现的逻辑,在第三轮可能被部分覆盖,也可能旧逻辑和新逻辑互相共存。你盯着对话窗口觉得很安心,觉得它一直在响应你的诉求,但生成的代码内部,各种不同版本的假设已经打成一锅粥了。
3. 库存系统翻车全记录:AI 生成 3000 行代码,上线 40 分钟就崩
3.1 需求描述得漂漂亮亮,生成速度确实快
前面讲了很多抽象问题,这里我放一个具体的翻车案例。某个模拟项目里,团队想搭建一个门店库存管理系统,核心需求并不复杂:支持多门店管理库存、门店之间可以调拨、销售时扣减库存、库存低于阈值时告警。
A 同学全程用 Vibe Coding 来推进,把需求拆成十几条自然语言指令,AI 在两个小时内生成了差不多三千行代码,包括门店管理、商品管理、调拨单、扣减接口和告警模块。团队当时很高兴,因为代码量大、风格统一、注释也齐全,感觉整个项目像是一夜之间被 AI 承包了。
3.2 自测阶段全绿,其实雷早就埋下了
A 同学也不是完全没有安全意识,他让 AI 顺手写了一批单元测试。测出来的结果确实全绿:新建门店成功、创建商品成功、扣减库存成功、调拨成功。但如果你仔细看那些测试用例,会发现它们全部是顺序执行、单线程、单一门店、唯一商品,数据彼此独立,没有任何交叉。
这里面最致命的一个雷,是扣减接口完全没有处理并发更新。AI 生成的代码大约是“先查库存,再判断够不够,然后扣减,最后更新数据库”的经典三步。在单线程测试里,这套逻辑永远不会出问题。但真实系统里,两个请求同时进来,都查到库存只剩 1 件,都判断“够扣”,然后都去执行扣减,最后库存就变成了 -1,甚至更低。
3.3 上线 40 分钟后的爆雷现场
系统部署之后,最初十几分钟一切正常,门店 POS 端正常扫码,库存数字正常下降。大约 40 分钟过去,有两个门店差不多同时发生了一笔销售,恰好都落在同一个 SKU 上。然后告警就响了:库存扣成了负数。
更麻烦的是,由于 AI 为“调拨单”生成的代码里,对状态的约定是“调拨中”、“已完成”、“已取消”,而销售扣减的那段代码,把“已调拨”认定成一种不可操作的冻结状态。结果一个调拨单刚到“调拨中”,销售系统就不允许动这个商品了。从业务上看,这明显不合理:商品还在仓库里,为什么不让人卖?从代码上看,两段逻辑各自都认为自己是正确的,但它们对“调拨中代表什么”的语义理解完全不同。
3.4 查证过程与漏洞定位的完整链路
事故发生后,整个团队开始排查。他们没有直接去翻代码,而是先把数据库里的异常数据拉出来:SKU 库存负值、调拨单状态卡住、告警信息互相矛盾。然后沿着数据流倒推,才定位到扣减接口的并发问题,以及调拨模块和销售模块的状态语义冲突。
这个排查过程非常能说明问题:如果代码是一个人从头到尾写完的,他在实现调拨单的时候就会本能地意识到“调拨中”这个状态和销售扣减是有关联的,至少要去看一眼那边的判断条件。但 AI 生成代码时,它根本没有“看一眼那边”的意识,每个模块都是独立生成的,状态机自然就各画各的。
整个修复花了团队三个人整整两天时间。而如果一开始就由有经验的人统一设计状态机、统一并发方案,这个故障在评审阶段就该被拦截下来。我自己算了笔账:Vibe Coding 省下的那半个下午,最后用两个整天连本带利还了回去。
4. 为什么大模型生成代码天然带着自洽性缺口
4.1 下一个 token 的赌徒,不负责推理
要理解 Vibe Coding 的逻辑漏洞为什么“无法自洽”,得先看清楚大模型生成代码的本质。它的核心工作方式,是预测一段文本中下一个最可能出现的 token。所谓“最可能”,是基于它看过的大量历史代码统计出来的,而不是基于它对你系统的完整理解推理出来的。
打个通俗的比方,它像一个特别会接话的聊天者,你说“库存扣减”,它立刻顺着惯性说出“先查询库存,再判断是否足够,然后更新”。这段话听起来非常合理,因为绝大多数代码仓库里都能看到这种写法。但它不是像人一样经历过“如果两个请求同时进来会怎样”的推演,它是沿着统计惯性滑过去的。
这种机制决定了它天然追求“局部合理”而非“全局真实”。它生成的代码看起来符合编程惯例,但可能完全脱离你的业务约束、你的部署环境、你的并发模型。这种缺陷不是某一个模型的版本问题,而是生成机制层面的结构性短板。
4.2 上下文窗口的短视症
任何大模型都有上下文窗口限制,哪怕窗口再大,也不可能把一个大型业务系统的全部背景装进去。你在对话里跟它交代了库存模块,它生成的时候能记住你在这个会话里提到的内容。但等到生成调拨模块时,你如果没专门强调销售扣减的规则,它就已经忘了,或者说根本没看见。
这就像一个短视的工匠,他只知道眼前这一块木头的纹理,不知道整个柜子的设计图。他能把这块木头打磨得很光滑,但当你把几块“光滑的木头”拼成柜子时,榫卯对不上、承重结构错位,一切都得推翻重来。
这个短视症还带来一个副作用:AI 不会主动做“全局回归检查”。人在写完一个新模块后,就算不做自动化测试,脑海里也会顺一遍“我这个改动会不会影响之前那块逻辑”。大模型没有这个动作,除非你明确给它指令去 review 整个项目,否则它连看一眼其他文件的意愿都没有。
4.3 没有执行反馈,就不是真正的“调试闭环”
正常人类写代码,会经历一个循环:写代码、编译、运行、看到报错、修代码、再跑。这个循环里的每一步都会给开发者真实反馈,帮他修正对系统的理解。Vibe Coding 的对话模式,本质上是静态文本生成,AI 写完代码后并不会真的把它放在你的系统里跑一遍,不会遇到编译错误、运行时异常、数据错乱,因此它没有机会通过执行结果来修正自己的错误判断。
这就是所谓的自洽性缺口:程序的逻辑自洽,最终必须靠运行时行为来验证,而不是靠“读起来顺”。AI 没有运行反馈,只能用统计规律来假装自己“验证过”。很多人在使用过程中会误以为“AI 写出的代码应该能跑”,但那个“能跑”的错觉,完全是你自己在布置运行验证环境,跟 AI 本身的判断力没有关系。
5. 不反对用 AI,但要用对:五道把自洽性变成纪律的防线
5.1 先框死架构,再放 AI 写零件
第一道防线也是最容易被跳过的一道:不要在空白的项目上让 AI 自由发挥。正确做法是先由人类工程师把架构骨架确定下来,包括模块划分、接口定义、数据结构、状态机设计、并发方案,这些核心决策绝不能交给 AI。AI 适合的角色是“零件加工厂”,而不是“总设计师”。
拿前面库存系统的例子来说,如果一开始就先把“调拨单状态只有调拨中、已完成、已取消,且调拨中不冻结销售”这个规则写成明确的接口文档和数据字典,再让 AI 去生成各个模块的实现,至少状态语义冲突这一类问题能被拦掉大半。架构红线划清楚之后,AI 生成代码的自由度越小,它制造自洽性漏洞的空间就越小。
5.2 用类型系统和契约测试把“自洽”变成可执行约束
光靠文档约束不够,因为文档不会自己检查代码。要真正把自洽性漏洞挡在门外,必须把约束落进类型系统、Schema 定义和契约测试里。
比如订单状态,不要各处传 String,而是定义成强类型枚举,这样 A 模块怎么传、B 模块怎么收,编译器就能帮你验证一部分。接口数据格式,用 JSON Schema 或者 protobuf 这类带契约的文件去描述,任何一端越界都能直接暴露。再配合契约测试,把“模块 A 返回的数据能被模块 B 正确消费”变成一条自动化测试用例,每次改动都跑一遍,比人肉检查靠谱太多。
我在项目里的习惯是:每一块 AI 生成的代码,必须配套一个人工编写或人工严格审查过的数据契约文件。AI 可以去实现内部逻辑,但接口两端的约定必须由人拍板。那些绝大多数“无法自洽”的问题,其实都发生在接口层,把接口层守住,就守住了大半。
5.3 代码评审不许跳步,AI 代码按最高风险对待
很多团队对 AI 生成的代码有一种奇怪的宽容心态,觉得“AI 写的代码应该比新人写的靠谱”。以我的观察,这个心态非常危险。AI 代码的问题往往不在单行语法上,而在跨模块的语义和边界处理上,而这些恰恰是最需要资深工程师拿经验去审的地方。
建议把 AI 生成的代码当作“初筛通过但来路不明的外部贡献”来处理,走完整的代码评审流程,评审重点不是它写得好不好看,而是它有没有破坏系统已有的假设。评审清单至少包括:它是否使用了正确的数据源、它是否处理了所有异常分支、它的写入操作是否考虑了并发、它有没有和已有模块的状态定义冲突。把这些作为强制项,一条不满足就打回修改。
不要觉得这样会拖慢效率。我实测下来,严格执行代码评审的 AI 辅助开发,整体风险能下降几个量级,速度损失远小于上线后修事故的时间损失。
5.4 异常路径测试:故意喂脏数据
AI 生成代码最薄弱的环节是边界和异常,那么针对这一点进行专项测试就很有必要。不要只测“正确路径”,要专门构造各种脏数据:空字符串、负数、重复提交、并发请求、下游超时、超大文件、非法编码,凡是生产环境可能出现的非理想情况,都塞给 AI 生成的函数去跑一遍。
这种“故意找茬”的测试在 Vibe Coding 模式里几乎是必需品。因为 AI 写代码时不会自己去想“我的函数会不会被调用的时候收到空对象”,只有你拿测试用例逼它,它的问题才会暴露出来。我在团队里要求所有 AI 生成的模块必须附带一批异常路径用例,哪怕实现代码本身不是 AI 写的,只要经过 AI 修改,也要补跑这批用例。坚持了半年,回流回来的故障明显变少。
5.5 建立“AI 代码隔离区”和回滚预案
最后一道防线是流程上的,把 AI 生成代码当成一个需要隔离观察的对象,不要直接合入主干。建议的做法是,在分支里让 AI 生成代码,连同它的测试用例、数据契约、评审记录一起提交。合并前必须经过完整的环境验证,包括构建、单元测试、集成测试、小流量冒烟,全部通过再合入。
同时要为高风险模块准备回滚预案。AI 生成代码的故障往往带有“表面上正常,实际已破坏数据”的特点,光是重新部署还不够,可能还需要数据修复脚本。提前把回滚步骤、数据修复思路想好,能把事故影响控制在可接受范围内。别把“让 AI 写得更快”变成“让事故来得更快”。
6. 别再吹牛了:工程交付的账本不认 vibe
说了这么多,我不是想全盘否定 AI 编程。恰恰相反,我现在自己写代码也离不开 AI 工具,它生成模板、写测试数据、做跨语言翻译,效率上的提升是实打实的。我反感的是那种闭眼吹 Vibe Coding 的风气,把一种需要高度约束和强人工把控的开发方式,包装成谁都能上手、怎么玩都不会出事的银弹。
工程交付这件事,最终看的是可验证的正确性,而不是你对话时的那种愉悦感。代码能跑只是起点,逻辑自洽才是底线。所谓自洽,就是无论数据怎么变化、请求怎么并发、模块怎么组合,系统都能按照业务规则稳定输出,不会出现这个模块推翻那个模块的荒诞局面。
以我个人的经验,AI 生成代码在项目里的占比越高,前期对架构约束和测试纪律的投入就要越大。那些看起来“省下来”的时间,最后都会在评审、修复、排查事故的过程中以别的形式还回去。想清楚这一层,再回头听那些“下午写完一个系统”的传奇故事,你就知道里面有多少水分了。
最后给已经在用或准备用 AI 编程的朋友一句实在话:把 AI 当实习生,它能帮你跑腿;把 AI 当架构师,它就敢拿一整套无法自洽的逻辑漏洞给你交差。守住自洽性这道底线,Vibe Coding 才能真正成为效率工具,而不是事故发动机。