首页 / 资讯中心 / 文章详情

打破代码黑盒:从vibe coding到可维护的AI生成代码

打破代码黑盒:从vibe coding到可维护的AI生成代码 ★ FEATURED ARTICLE
最近团队里聊得最多的词就是 vibe coding。先亮个态度我不反对 vibe coding我自己在原型验证、脚本工具、甚至是某些一次性数据分析上也靠它把效率拉满过好几倍。但真正让我开始警惕的是第二周、第三周发生的事——代码能跑可没人能说清楚它到底是怎么跑的包括当时看着 AI 把代码一段段吐出来的那个同事。这种状态就是我标题里说的“代码黑盒”。更准确一点AI 生成了一大坨代码通过了编译、通过了冒烟测试、看起来也能出正确结果但没人能回答“这段代码的边界条件是什么”“这个变量为什么会出现在这里”“如果线上传入一个异常值它会怎么走”。这不是个别现象我大概问了一圈身边至少一半用过 vibe coding 的同学都遇到过。这篇文章就是想认真聊聊当黑盒已经留在代码库里作为开发同学你到底应该做什么。先说明白这篇文章不是让你别用 vibe coding也不是教你“事后骂 AI”。恰恰相反我默认你接下来还会继续用 AI 写代码。所以这里面的核心问题其实是你怎么把“AI 生成的、你不理解的代码”变成“你理解的、敢改的、能维护的代码”。整个过程有一套可复用的流程我从接手一个实际模块开始完整走了一遍今天把每一步的经验和踩过的坑全部摊开讲。1. 先把事情说清楚vibe coding 为什么会留下“代码黑盒”1.1 “黑盒”到底指什么不是看不见而是说不清很多人对黑盒的第一反应是“没有源码”但代码黑盒恰恰相反——源码就在 IDE 里你甚至能逐行读但它对你来说依然是黑盒。我自己的定义是黑盒代码是指“行为无法被解释”的代码。你可以运行它、看到输入输出、甚至能在上面改几个参数试试效果但如果你被问到以下任何一个问题就会卡住这段代码的完整执行路径是什么这个分支条件在什么业务场景下会触发这段逻辑依赖了哪些隐藏的外部状态如果数据不符合预期它会报错、降级、还是静默返回错误结果如果你回答不了其中任意两个那它对你就是一个黑盒。vibe coding 最典型的副产品就是这种“能编译、能跑、但没人真正消化过”的代码。它能跑很多时候只是因为 AI 见过类似模式而你的测试数据和真实数据刚好长得差不多。注意黑盒不代表代码质量差。有些 AI 生成的代码写得非常漂亮结构清晰、命名规范。但漂亮和“你理解了”是两回事。真正的问题在于知识缺口而不是代码本身的优劣。1.2 黑盒代码是怎么长出来的三个典型场景我在复盘了几个实际项目之后发现黑盒代码的来源基本可以归为三类第一类一次性大段生成然后合入主线。这是最常见的。开发者给 AI 一个稍微详细一点的 promptAI 吐出 300 行代码开发者扫了一眼、在本地跑通了直接 commit、 push、发 PR。评审同事看着一大段生成代码也很难真正逐行理解基本只能看看风格、看看有没有明显问题。于是这段代码就以“没人真正理解”的状态进入了代码库。第二类用自己不熟悉的技术栈做的“试探性生成”。比如一个以 Python 为主的后端团队突然要处理一段前端脚本逻辑或者一个写 Java 的人让 AI 生成了一段 Rust 工具。因为技术栈不熟开发者很难对生成的代码做出有效判断也不知道该问 AI 哪些问题。这时候黑盒的厚度是最高的。第三类多轮迭代式 vibe coding 导致的历史堆积。一开始只是让 AI 改一个小功能然后第二个人让 AI 再改一个参数后面又有人让 AI 加一个判断……每一轮改动 AI 都会基于上一版本继续生成中间没有任何人完整读过整块代码。最终这个模块变成一个“层层加码”的怪东西逻辑肯定是能跑的每一轮都验证过但整体上谁也不敢碰。这三类场景有一个共同点生成速度远大于消化速度。代码库的增长速度超过了团队的理解速度黑盒的规模自然就越来越大。1.3 黑盒代码的第一个危险信号“能跑”不等于“可用”在代码评审里我经常听到一句话“它跑得好好的为什么要动它”这句话对稳定运行的系统有一定道理但对黑盒代码来说是最大的陷阱。“能跑”只代表在当前输入、当前环境下能跑。而“可用”要求的是在可预见的输入范围内跑得对、在异常情况下有合理表现、在需求变化时可以被安全修改。黑盒代码恰恰在最薄弱的两个环节出问题边界条件。AI 生成代码时默认输入都在一个“正常”的范围里。但真实场景中一定有边界值、空值、超长字符串、并发冲突、时区偏移。你如果不知道代码怎么处理这些它一旦出错你连排查方向都没有。需求变更。业务逻辑三周一小改、三个月一大改。黑盒代码只要被改一次修改者就是在猜。猜对了是运气猜错了就得花几倍的时间去回溯“原来这段逻辑是什么”。所以我个人有个判断标准当你需要修改一段代码而你不能在三分钟内说出“它的完整行为描述”时这段代码就已经不再“可用”了只是还在“能跑”而已。这时候你要做的不是继续加功能而是先把黑盒拆开。2. 动手之前别急着重构先给黑盒补上“安全网”2.1 第一步永远是确认基线当前行为到底是什么我见过一些同学接手黑盒代码的第一反应是“看不懂那就重写吧”。这是最容易犯的错误——你连现有的行为都没完整观测过重写出来的一定是另一个故事而不是“修好的版本”。正确做法是先确认当前行为基线。就好比你要给一台运转中的机器换零件第一步不是拆螺丝而是记录这台机器现在每个旋钮在什么位置、输出什么声音、温度多少。具体到代码上分三步走把代码 checkout 到一个干净的分支打一个 git tag比如baseline-before-refactor。这个动作成本极低但能给你无限次反悔机会。跑一遍项目里已有的测试记录通过率。别急着补测试先搞清楚“现在哪些行为有保障”。手工过一遍核心入口准备一组有代表性的输入记录实际输出。重点覆盖正常值、边界值、空值、错误值。这里有个小技巧用“录屏 日志”的方式记录基线而不是凭记忆。把每次输出的关键数值打印出来、保存好后面写测试和对比重构结果时这就是最硬的证据。提示这一步最忌讳的是“我以为它是这么跑的”。黑盒代码的行为必须以实测为准。AI 生成的代码尤其容易在某些细节上出乎意料——比如一个并不需要的排序操作、一个取绝对值的多余处理。你只有实测过才能发现这些“隐藏行为”。2.2 用特征测试把“黑盒行为”钉死在代码里当基线确认之后下一步是为黑盒代码补上“特征测试”也叫黄金测试Characterization Test。这个概念很多人听过但真正实践的时候有几个要点。特征测试的思路一句话不预设“正确行为”而是把“当前行为”用测试固定下来。传统测试是“当输入 X 时应该输出 Y”特征测试是“当输入 X 时现在输出 Y以后也必须输出 Y”。它不是为了验证逻辑对不对而是为了防止你在重构过程中悄悄改变行为。写特征测试时要注意三点测试输入要覆盖你在基线段记录的全部场景包括那些你觉得“不太对劲”的输入。如果现有行为确实有问题那也没关系——先把它钉住后面再单独修补。断言要断言最终输出不管是返回值、数据库记录还是调用外部接口的参数。不要断言中间变量否则特征测试会变成“测试代码怎么写的”而不是“测试行为是什么”。一次不要写太多。我的经验是一个模块先写 5~10 个最核心场景的特征测试足够建立一个“基础安全网”。写完运行全绿这时候你才算有了动手改代码的资格。这里用一段简单的伪代码表示特征测试的样子# 用 pytest 固定当前行为注意先写“现在的行为”不是“想要的行为” def test_calculate_price_VIP_3_items_current(): # 这是在基线段观察到的实际输出 assert calculate_price(100.0, VIP, 3) 88.0 def test_calculate_price_normal_1_item_current(): assert calculate_price(100.0, normal, 1) 100.0 def test_calculate_price_empty_cart_current(): # 边界输入记录当前行为 assert calculate_price(0.0, VIP, 0) 0.0等这些测试全部通过你会获得一个非常宝贵的心理优势接下来你对代码做任何修改跑一遍测试就知道有没有改变行为。这个安全网比任何代码评审都更直接。2.3 确定边界黑盒不能无限蔓延给黑盒建安全网还有另一个重要步骤划定黑盒的影响范围。黑盒代码之所以危险不只是它本身难以理解还因为它会把它不理解的部分传染给周围的代码。比如某个函数内部直接修改了全局配置、某个模块在 import 的时候偷偷建立数据库连接、某个工具函数被十几个地方调用但谁也不知道它内部是怎么处理异常的。你如果只盯着黑盒本身看很容易忽略它对外部造成的影响。我的操作习惯是三步找出所有入口和出口。用 IDE 的“Find Usages”把调用这个黑盒模块的地方全部列出来整理成一份清单。检查出口处的数据契约。黑盒返回给外部的数据外部调用方期待什么格式、什么范围如果返回值和预期不符这是谁的问题决定隔离策略。如果黑盒太大、一时半会儿拆不完我会在它和外部世界之间加一层“适配层”Facade/Adapter让外部调用方只依赖适配层的接口而不是直接依赖黑盒内部实现。这个适配层不是多余的脚手架它是你的隔离带。有了它即使黑盒内部的逻辑短期内不可能完全清晰你至少可以把“受影响的范围”控制在一个固定的边界内。其他业务代码不会被黑盒拖下水后面的拆解也就有了一个稳定的操作面。3. 从外到内拆黑盒把“能跑”变成“能维护”安全网和隔离带搭好以后才真正进入“拆黑盒”的阶段。这里我必须强调一个顺序问题永远从外往里拆而不是从里往外拆。先搞清楚“这段代码在整个系统里是怎么被使用的”再研究“它内部是怎么实现的”。从里往外拆的最大问题是你会在一个无关紧要的内部细节上钻研半天却完全不知道它对外的价值是什么。3.1 先画图再动手调用关系就是你的地图拆黑盒的第一步不是打开文件逐行读而是把整个模块的“地图”画出来。我这里说的画图不需要任何复杂的工具笔和纸、或者一个白板就行。真正的心法就一条把代码的执行路径变成一张你能一眼看懂的图。具体画什么我一般画三张调用结构图黑盒模块被谁调用它内部又调用了谁箭头代表调用方向标注参数和返回值。状态变更图黑盒内部是否存在状态变化哪些代码读了全局状态哪些代码写了全局状态重点是找“写”的点那是副作用最容易藏身的地方。异常路径图哪些地方会 throw哪些地方会 catchcatch 之后是重新抛、吞掉、还是返回默认值画完这三张图之后你会发现两个特别有价值的东西。第一哪些部分其实是“独立的小岛”——只依赖参数、不依赖外部状态、没有副作用这些是最容易拆解和测试的部分。第二哪些部分是“中心枢纽”——几乎所有路径都要经过这里这里的风险也最高值得你优先深入理解。注意画图不是为了给别人看是为了逼你自己走一遍每一条调用链。如果你画到某个地方卡住了不知道那个函数是从哪被调用的那这恰恰就是黑盒最需要被打开的地方。这个“卡住”的信号比任何静态分析工具都准确。3.2 识别“坏味道”黑盒最容易藏身的五个地方画完图你会对模块的结构有个大致的印象。但 500 行的黑盒代码到底从哪几行开始读我总结了五个黑盒代码的典型“藏身处”基本上一抓一个准第一处超长函数。一个函数超过 20 行还不拆分基本就可疑了。黑盒代码里的超长函数往往是“生成过程”的产物AI 把一堆逻辑按顺序塞在一起中间没有语义边界。这样的函数是黑盒的核心载体。第二处魔法数字和魔法字符串。代码里出现0.88、3、VIP这种直接量却没有任何解释。在黑盒代码里这些数字往往对应着某段 AI 从训练数据里“看来的”业务规则但它没有说出来。你不理解这些数字的含义就永远无法理解这段代码。第三处嵌套分支地狱。if套if套switch再套if每一层缩进都是一层隐含的业务条件。AI 很喜欢生成这种代码因为它能忠实地把每个判断条件都保留下来但人类读起来非常痛苦。第四处隐藏的全局状态。函数定义里没有参数却读取了“某个地方的某个变量”。这种状态既难追踪、又难测试是最容易导致“改一个变量,三处行为跟着变”的根因。第五处大而全的 try-catch。一个try包住几百行catch里打印一行日志就完事。这是黑盒代码的“隐形杀手”——它把所有异常都消化掉了导致你根本不知道哪里可能出错因为出了错你看不见。找到这些坏味道之后不要急着全部处理。抓最核心的一处开始通常是那条你画图时“卡住”最多次的路径。3.3 小步重构一次只打通一条链路拆黑盒的核心手法说出来你可能觉得太简单一次只改一点点每改一次就跑一遍特征测试。但真正能做到的人很少因为大家总想着“这段代码这么烂我恨不得一次重写”。我自己拆过一个 500 多行的黑盒模块最深的体会是把“重构”当“考古”不要当“翻新”。你的任务不是把代码改成你理想中的样子而是把代码里的“真实行为”挖出来让它变得透明。每一步都应该是“行为等价变换”——改完之后测试照旧全绿。具体节奏我习惯这样安排每一步都有明确产出拆函数把一个超长函数按语义拆成多个小函数。拆的过程中不要动任何逻辑只是搬代码。跑测试确认全绿。提取变量把魔法数字和魔法字符串改成有名字的常量。这步几乎零风险但信息增量很大——当你把0.88改成VIP_DISCOUNT_RATE的时候你很可能会突然理解这段代码的真实意图。消除副作用把隐藏的全局状态改成显式参数传递。这一步稍微危险一点但收益也最大——它会逼你去回答“这个状态到底从哪里来、到哪里去”。重新组织分支把嵌套 if 改成早返回、或者把条件提取成有名字的谓词函数。改完之后代码的可读性会有质的提升。每完成一步都要跑一遍特征测试。如果测试变红了你有两个选择要么撤销这一步修改git 的好处就在这要么仔细对比输出差异判断是“行为变了”还是“测试写错了”。我的经验是测试变红 80% 的情况是测试本身遗漏了某个场景的输入而不是逻辑真的改坏了。但这不代表可以忽略红测——每一次变红都是一个信号它在告诉你“你对这块行为的理解还有盲区”。等你把一条主链路完整打通之后黑盒其实已经不再是黑盒了。你至少能向另一个同事讲清楚“这个模块从入口到出口做了什么、依赖什么、边界在哪里”。这可能听起来不算什么了不起的成果但对比一下这是从“完全不敢碰”到“心里有底”的本质区别。4. 一个完整的拆盒实例订单金额折算模块的重构过程前面讲了很多方法论这一章用一个实际发生过的案例把它串起来。为了不涉及任何具体项目信息我把这个模块称为“某跨平台系统的订单金额折算模块”。过程是我自己经历和复盘过的真实性没问题。4.1 初始状态一段看起来没问题但没人敢动的代码这个模块的背景很简单系统中需要根据用户类型、商品数量、当前促销规则计算订单最终金额。这个功能由 AI 在 vibe coding 的流程里生成同样看着能跑、测试也通过但谁都说不出完整的逻辑。我第一次拿到代码时的状态是这样的文件 599 行核心函数有 47 行内部嵌套了 5 层 if 和 2 个 switch出现了 3 个没有任何说明的魔法数字0.88、0.15、0.1有 2 个函数看起来是“纯计算”但它们内部竟然读取了一个模块级的全局变量这个变量在别处被异步更新异常处理是一个巨型的 try-catchcatch 之后只打印日志测试数量0。这个模块的调用方有 7 处其中 3 处直接拿返回值做金额比较一旦逻辑出错直接影响订单状态判断。这就是一个标准的“高风险黑盒”。4.2 拆解过程从“整体黑盒”到“每行都清楚”整个过程我按前面说的流程走大概花了一个下午加一个上午。这里按时间线记录关键节点下午第一步确认基线写特征测试。我先构造了 18 组输入覆盖了不同用户类型、不同商品数量档位、叠加促销、空购物车、数量为 0、价格为负虽然是异常输入但我故意放进去观察行为。用日志记录所有输出。然后写特征测试把这些行为钉死。这个阶段花了 40 分钟是一次性投入后面所有的改动都靠它兜底。下午第二步画调用图和状态变更图。我花了半小时梳理这个模块和外部世界的所有联系。结果发现了一个之前没人注意到的问题全局变量promotionCache在另一个线程里每 5 分钟刷新一次但当前模块的其中一个计算函数会在读取这个变量之后做金额计算而另一个函数则直接用调用方传入的促销参数。这导致同一种促销条件在 5 分钟窗口的前后可能计算出两个不同的结果。这个 bug 在黑盒状态下被掩盖了很久因为没有人会把“这种奇怪现象”和“隐藏的全局状态”联系起来。下午第三步拆函数。我把 47 行的核心函数拆成了 6 个小函数一个负责用户类型映射、一个负责基础折扣、一个负责促销叠加、一个负责最终金额计算。这步没有改任何逻辑只搬代码特征测试全绿。拆完之后我对整个模块的理解已经有了质的提升——至少每条逻辑链都有一条清晰的路径。上午第四步提取常量和消除副作用。把0.88提取成VIP_DISCOUNT_RATE0.15提取成MAX_SINGLE_ITEM_DISCOUNT0.1提取成PLATFORM_COMMISSION_RATE。这一步让代码意图变得非常清楚——原来这些数字对应着三个完全不同的业务规则之前的黑盒把它们揉在一起当然没人能看懂。同时我把全局变量改成显式参数传递虽然改动了 3 个调用方但因为特征测试存在整个过程没有引入任何新问题。上午第五步重组分支和补充缺失测试。最后把嵌套 if 改成早返回把条件提取成命名清晰的谓词函数。同时针对我拆解过程中新发现的几个“隐性分支”补充了测试。到这里模块从一个 599 行的黑盒变成一个结构清晰的 187 行代码配 23 个测试全部通过。4.3 复盘这次拆盒花了多少成本值不值整个过程实际耗时大约 6 个小时包含中途查资料、反复确认行为的时间。成本不低但我要说这个投入非常值。拆完之后这个模块的收益非常明确后续任何需求变更平均从“需要 2 天揣摩测试”变成了“半天定位测试”之前存在的一个“5 分钟窗口期促销金额不一致”的隐性 bug 被彻底修复模块拆出的纯函数可以直接单测覆盖率从 0% 提升到 100%仅这个模块新来的同事接手这个模块读代码的时间从“半天都看不懂”变成了“半小时能讲清楚”。当然我也会提醒你不是所有黑盒都值得花 6 个小时去拆。如果一段代码是明显的临时代码、即将被废弃、或者没有任何后续改动计划那把它用适配层隔离起来就好不拆也罢。拆盒的核心价值在于降低后续的维护成本如果这个模块再也没有后续那“能跑”就够了。判断标准就一条它会继续被修改吗会就值得拆不会就别动。5. 常见问题与排查技巧实录拆完一个黑盒之后我积攒了不少排障经验。这一章直接给你整理成速查形式都是我实际踩过的坑遇到对应症状可以直接对照排查。5.1 症状一改一个变量三处行为跟着变这几乎可以断定是隐藏的共享状态在作祟。某个模块级的变量被多处读取/修改你只改了其中一处的赋值另外两处读到的就变了。排查方法不要直接搜变量名那只能找到“写”的地方未必能找到所有“读”的地方。我推荐用“单向数据流”的思路重新审视这个变量——画出它从创建、到修改、到被读取的所有路径。如果路径数量超过两条而且修改点分散在不同模块里这就是典型的“状态泄漏”。解决办法把共享状态从“隐式全局”改成“显式依赖”。最直接的手法就是把全局变量变成函数参数让数据的流向变得可见。如果你担心改动太大先加适配层让黑盒内部只能通过适配器的接口访问状态把影响范围锁死。5.2 症状二加了半天日志还是找不到逻辑入口这种情况经常出现在 vibe coding 生成的事件处理、消息驱动代码里。你只知道这个函数“被调用了”但不知道是谁在什么条件下调用它的。加日志只告诉你“它确实跑了”却给不出“为什么要跑”的答案。排查方法从调用栈的返回值往前倒推。先在这个函数里打印inspect.stack()或调用方的执行上下文然后一级一级往上追。另一种思路是放弃在代码里找入口直接查事件的发布源——谁发布了这条消息它是在什么业务动作之后发布的这条路往往比在代码里翻腾更高效。提示处理这种黑盒git blame是个被你低估的工具。找到这几十行代码是哪一次 commit 被加进来的看那次 commit 的 message 和关联变更文件通常你能拼出“它当初是为了解决什么问题而存在”的完整故事。5.3 症状三AI 生成的代码逻辑跑偏却不报错这是 vibe coding 最容易踩的坑也是黑盒最隐蔽的风险。AI 生成的代码往往是“看起来合理但细节错误”的典型比如对空数组求和时返回了None而不是0、日期转换漏掉了时区处理、字符串比较时忽略了大小写。最麻烦的是这类错误不会抛异常所有测试都过但线上数据就是不对。排查方法对输入输出做差分测试。不要只看“正常输入”把所有可能的空值、边界值、异常值喂进去观察输出是否在你预期的“合理范围”内。这里的核心不是对结果的正确性做判断而是建立“行为一致性”标准——同一个输入每次跑出来的输出必须完全一致。如果出现不一致说明你的代码里存在“隐藏的随机性”或“外部状态依赖”这通常是逻辑跑偏的源头。另外一个我常用的技巧是让 AI 自己解释它的代码。把这段黑盒代码交给另一个 AI 模型问它“这段代码每一步做了什么哪些边界条件没有被显式处理如果数据异常会出现什么行为”AI 虽然不能保证解释完全正确但它给出的“候选解释”往往能给你指出需要重点验证的地方。然后再用手动测试去验证这些盲区。6. 一点个人体会黑盒不是灾难而是你没拆过的代码整套流程跑下来我最深的感受是vibe coding 最危险的地方不是它生成了烂代码而是它鼓劢了一种“不看完整代码就交付”的习惯。开发可以借助 AI 提速但代码的维护责任永远在人类身上。黑盒本身不是灾难灾难是你知道它是黑盒却任由它留在代码库里直到某次事故逼着你去面对。现在我自己处理 vibe coding 生成的代码已经形成了一套条件反射生成代码后先花 20 分钟粗略读一遍确认没有“自己完全无法解释”的部分如果有立刻打上标记后面补特征测试无论多赶涉及核心业务逻辑的代码必须在合入前完成至少 3 个关键行为的特征测试。最后再分享一个真实经验拆过一次黑盒之后你再让 AI 帮你写同类代码它会“听话”很多。因为你已经知道这类模块应该怎么组织、有哪些边界条件、哪些逻辑靠什么常量控制你能给出更精确的 promptAI 生成的代码自然更贴近你的预期。我拆完订单金额折算模块之后让 AI 重新生成一个类似的新模块一次跑通而且代码结构几乎不用改。这说明拆黑盒虽然在这一刻花了你 6 个小时但它也为后续所有和 AI 协作的过程省下了数倍的时间。这就是我坚持“黑盒必须拆”的根本原因。
阅读完成 · 觉得有帮助?
咨询建站