
这事过去两周了我还是没缓过来。起因是我在团队里搞了个小实验——用历史缺陷数据训练一个模型让它学会“闻”代码里的坏味道。目标很简单提交代码前自动扫一遍标出高风险区域省得每次靠人肉眼 review 那些陈年代码。训练数据用的是我们仓库过去五年的缺陷记录和对应的代码提交。模型本身不复杂一套基于代码 AST 的向量化表示加上一个缺陷倾向性分类器跑了几轮交叉验证准确率还不错。有意思的事情发生在第一次全库扫描的时候。它在一个没人敢动的模块里标记了一个极高风险函数。模块是订单对账的核心代码注释里写着“2009/03/15 老张”。那个函数逻辑极其扭曲各种硬编码的魔数、嵌套五层的 if-else、还有一段明显是反着写的金额比较。模型给它的风险评分是 0.97而我们人工标注过的已知缺陷区最高才 0.82。我把这段代码摘出来找了一个在公司待了 13 年的架构师看。他盯着屏幕沉默了两分钟然后说了一句让我后背发凉的话“这是当年 CTO 写的。那时候公司刚起步对账老差 1 分钱他半夜临时糊上的。”这段代码在线上跑了十年从未被改过。不是因为没问题是因为没人敢碰也没人在回归测试里覆盖到它的异常分支。AI 把它揪出来了。目录为什么我想训练一个“闻Bug”的AI屎山是怎么形成的CTO留下的时光胶囊训练过程让AI学会闻代码的“味道”模型发现的不止一个Bug是一类反模式如果没有AI这类Bug能藏多久这件事改变了我对测试的认知你也可以训练一个不需要大模型为什么我想训练一个“闻Bug”的AI我们团队的自动化用例覆盖率早过了 85%线上事故却没见少。复盘时发现一个规律真正造成线上问题的不是新功能逻辑而是改老代码时触发的隐藏副作用。那些十年前的代码没人记得当初的上下文单元测试要么没有要么写的假回归用例又不可能把每行代码的每个分支都跑一遍。人工 review 面对这种代码其实也无力。reviewer 的注意力会被新业务逻辑吸引老模块的改动只要编译过、看起来不突兀大概率直接放行。我就在想有没有办法让机器把全仓库扫一遍专门找出那些“看起来就像会出Bug”的代码这个想法的核心假设是缺陷不是均匀分布的它们会聚集在某些特定模式的代码里。 如果能让模型学会识别这些模式我们就能在代码合入前精准预警。屎山是怎么形成的CTO留下的时光胶囊模型标出的那个高风险函数场景是这样的对账系统需要比对内部订单金额和外部支付渠道的回调金额在分账场景下要把总金额按比例拆给多个商家。正常写法是总金额先转成 int分按比例乘除最后再转回元。但这函数用了一堆浮点数直接做加减末尾还用了一个魔数 0.001 做误差容忍。更诡异的是有一行判断写了 if (Math.abs(diff) 0.001 diff 0)。第一眼看过去这个条件永远为 false因为 diff 大于 0.001 就不可能是负数。但仔细看上下文才发现他在前面把 diff 取了一次反这行判断其实是生效的——以一种极度扭曲的方式。当年的背景是支付渠道回调金额偶尔会有浮点尾差导致对账报错技术团队排查了很久没找到根因。CTO 为了保上线半夜写了这个容忍逻辑硬把误差吃掉了。这段代码的问题不在于逻辑错误而在于它的“隐蔽正确性”。 它在当前数据下恰好正确但只要上游金额计算的精度规则稍有变化这个 0.001 的硬编码就会产生真正的对账差异。而且没有任何测试用例能发现它因为异常分支的触发条件被浮点误差掩盖了。训练过程让AI学会闻代码的“味道”说回模型本身。它的目标不是理解业务逻辑而是学习“缺陷倾向模式”。我把这个模式拆成了三个维度。第一个维度是代码结构复杂度。圈复杂度、嵌套深度、函数长度这些硬指标是基础但更关键的是“修改频次与缺陷的关联”。一个函数如果改 10 次有 7 次伴随缺陷修复那它的结构大概率有问题。第二个维度是代码克隆率。如果一段代码是通过复制粘贴产生的并且在不同模块里有轻微变体它的缺陷概率会显著上升。因为当初的 bug 很可能也被复制了只是还没暴露。第三个维度是注释与代码的一致性。我用了一个小模型来比对函数注释和实际实现。注释说“计算退款金额”实际代码里却有扣减积分的逻辑这种不一致是高危信号。训练数据是过去五年里 4300 多个缺陷修复的 commit提取出每次修复前的“坏代码”和修复后的“好代码”做成成对样本。模型结构就是 CodeBERT 做 backbone后面接一个二分类头输入是函数的 AST 序列化表示输出是 0 到 1 的风险分。验证集上做到 0.79 的 precision 和 0.83 的 recall。不算惊艳但作为辅助筛查工具足够了。模型发现的不止一个Bug是一类反模式CTO 那坨代码被标出来之后我顺着模型的高分结果继续排查发现它不止找出一个具体问题。它实际上标记出了一类反复出现的反模式“带误差容忍的浮点金额计算”。全仓库一共扫出 14 处类似模式分布在对账、结算、优惠券分摊、运费计算四个模块里。其中 3 处已经暴过线上问题并被修复剩下的 11 处里有 5 处在特定条件下可以重现计算偏差。虽然大部分偏差在 1 分钱以内但叠加高频业务场景后部分月份的对账差异能累积到几百元。这类问题靠人工 review 几乎不可能全揪出来。因为它分散在多个模块每个单独看都像是正常的容错处理只有拉通全局才知道这是一个系统性的设计缺陷。一个缺陷是事故一组同模式缺陷就是架构负债。如果没有AI这类Bug能藏多久CTO 那段代码藏了十年。不是没有测试而是所有测试都在验证“正常情况下对账金额正确”没有任何一个用例去验证“误差容忍的边界值”。因为当年写用例的人根本不知道有这个容忍逻辑——它写死在代码里没有在需求文档里出现也没有在接口说明里暴露。这种“代码里偷偷做了容错”的情况在老旧系统里其实很常见。开发人员为了解决一个紧急问题在底层做了兜底但没告诉任何人。测试人员按照接口文档写用例永远触达不到那个分支。代码就这么安安静静地躺在线上成了一个定时炸弹。AI 能把它找出来靠的不是聪明而是“不挑食”。它不会因为这段代码在核心模块就不敢报也不会因为注释写着“临时”就放松警惕。它会平等地扫描每一行代码用数据训练出的嗅觉做判断。这件事改变了我对测试的认知以前我认为测试的价值在于“验证”确认系统做了该做的事。现在我觉得测试的更大价值在于“发现未知”——找到那些连开发者自己都忘了的隐藏逻辑和假设。传统测试依赖一个前提需求明确预期清晰。但老旧系统的真实状态是代码即需求行为即文档。当你面对十万行无人维护的代码谁也不知道它真正在做什么的时候用例覆盖率的数字是骗人的。AI 在这件事上的优势是它不需要理解业务就能发现异常。它把“未知的未知”转化成“已知的未知”然后人再去判断这个未知是不是风险。这可能是 AI 测试最被低估的价值。不是用 AI 替代人写用例而是用 AI 做人做不到的全量扫描。你也可以训练一个不需要大模型这个方案的门槛没有想象的高。核心三件套Git 历史、缺陷记录、一个轻量级代码模型。我用的 CodeBERT 是开源的一张消费级显卡就能跑推理训练用 colab 的免费 GPU 也能搞定。真正的工程量在数据清洗上——你需要从 Git 历史里把“缺陷修复”的 commit 挑出来和对应的“缺陷引入” commit 配对。这一步可以用 SZZ 算法自动标注准确率 70% 左右再加人工抽样校验就够了。如果你不想训模型还有更简单的路径用现有 code embedding 工具把仓库所有函数向量化再用已知缺陷函数做相似度检索。这个方法虽然粗暴但对于找出“复制粘贴型缺陷”特别有效。不管用什么技术路径核心逻辑是一样的让数据说话让模式显形。你现在维护的系统里有没有那种“没人敢动、动就出事”的代码区域你有没有想过里面可能也藏着一颗十年前埋下的雷本文部分内容参考了霍格沃兹测试开发学社整理的相关技术资料主要涉及软件测试、自动化测试、测试开发及 AI 测试等内容侧重测试实践、工具应用与工程经验整理。