抖音质量效能部不传之秘:用AI精准预估“可能出事”的模块

发布时间:2026/7/17 21:22:05
抖音质量效能部不传之秘:用AI精准预估“可能出事”的模块 在抖音质量效能部搬砖那几年每隔一段日子就会被拉进同一个“紧急群”——某个看起来人畜无害的代码提交上线后把核心链路炸出一片红。复盘的时候大家总在叹气要是能提前知道这模块“不干净”哪怕多分一个测试人力进去也好啊。后来我们还真搞出来了。没搞什么玄学就是用 AI 做了一件事每次代码变更自动给这个变更的“出事概率”打分。从“到处救火”慢慢变成了“精准拆弹”。离开抖音后一直想把这套思路完整写下来核心方法不涉及业务机密今天就把这层窗户纸捅破。一、先别急着上模型想想你究竟要预测什么很多人一上来就去找数据集、跑 XGBoost结果折腾一个月连基线都出不来。我们最开始也犯了这个错。真正要预测的不是“哪个模块有 Bug”而是——“这次代码变更引入线上故障的概率有多高”。这两者差别巨大。模块历史 Bug 多只代表它旧债多不代表这次改动一定出事一个从来不出事的模块被一个新人改了核心逻辑往往才是大雷。所以我们的预测对象是commit变更。每一次 MRMerge Request提上来模型就给它一个风险分。分高了测试就重点关照甚至直接卡发布。二、把脏活干好样本标注比调参重要十倍有监督学习第一步是拿到“有问题的变更”和“没问题的变更”作为正负样本。听起来简单做起来全是坑。我们当时的做法是打通两个数据源Git 仓库的 commit log拿到 diff、作者、时间、改动文件列表线上故障/bug 跟踪系统明确记录哪些故障是由哪个 commit 引入的正样本能追溯到某个 commit 是故障根因的标为 1。负样本上线后安安稳稳跑了两周没惹出任何线上工单的 commit标为 0。这里头有三个血泪经验① 千万别用“修复 Bug 的 commit”直接做正样本。因为修复 Bug 的那个 commit 本身是解决问题的它的 diff 特征通常是“加个判空”“修个边界”和真正“写出 Bug”的 diff 截然不同。我们一开始没区分模型学废了以为加 if 判空是高风险行为闹了大笑话。② 窗口期要卡死。一个 commit 上线后到底观察多久没出事才算负样本我们试下来双周迭代下观察 10 个自然日比较稳。有些模块上线后三天才因为定时任务暴露问题窗口太短会把大量“还在路上”的 Bug 标成负样本污染训练集。③ 别做“一视同仁”的随机采样。业务代码里不出事的变更占了 95% 以上。如果你直接全量丢进去模型会学到一招绝技永远预测低风险准确率贼高屁用没有。我们后来做了下采样让正负样本比例控制在 1:5 左右同时给正样本加高权重模型才学会认真对待那些危险信号。三、真正值钱的不是算法是这一把特征抖音的代码库极其庞大业务线几十条语言主要是 Java/Kotlin/Go/C。我们设计特征的时候只有一个原则让不懂模型的人看了也觉得“有道理”。否则你推不动研发老大会觉得你在搞玄学。我们最终沉淀出五类特征每一条都是跟开发、测试反复扯皮磨出来的1. 变更规模 —— 越大越容易出事但有个例外新增行数、删除行数、总修改行数涉及文件数、涉及目录数是否改了底层库如网络层、存储层单独的布尔特征改了直接 20% 风险分起步例外是什么纯 import 整理和格式化代码。这种变更动辄几百行但几乎零风险。我们后来用正则扫 diff 内容如果 90% 以上的改动行只是空白/缩进/import 替换就直接压权重。2. 代码复杂度 —— 你写的圈复杂度最后都变成锅改动代码块的最大圈复杂度是否新增了嵌套超过 3 层的 if/for是否修改了已有高复杂度函数圈复杂度 15 的老代码这个特征抓“屎山上的新包”非常准。有一回直播推荐模块一个小 MR只改了 5 行但扎在一个 1200 行、圈复杂度 38 的老函数里。模型直接给了高危。开发不信结果上线当晚缓存击穿故障单编号我到现在都记得。3. 历史债 —— 这文件/这模块以前老出事现在大概率还有事该文件过去 90 天线上故障触发次数该模块目录粒度最近 3 次迭代的 Bug 密度上次该模块出故障距今的天数越近越危险因为可能修了又引入新问题4. 人的因素 —— 有些哥们儿写的代码就是自带“气场”我们给每个提交者算了一个开发者缺陷注入率此人过去半年提交中引入过线上故障的比例。这个值不是固定不变的会按时间衰减最近一次闯祸加权最高。另外还加上提交者入职时长新人前 3 个月是故障高发期这次改动是否他首次接触该模块首次触达 高复杂度基本等于点炮当时组里有个实习生能力很强但业务不熟模型给他的 MR 几乎每次都标高。头两个月他很不爽后来真出过两次 P1 故障他主动请我们吃饭说“这玩意儿比我 Code Review 靠谱”。5. 过程质量 —— 代码评审时就已经露出马脚了CR 评论总数、提出问题的评论数是否有“需要修改”的 CR 意见被作者忽略直接合入这个极狠一旦出现风险分至少拉高 30%关联需求变更次数提测期间需求还在变的Bug 率明显更高这些特征从 GitLab/Jira 的 API 里都能抓到。我们把它们拼成一张宽表一条 commit 一行大概 60 多个特征最后模型训练全用这个。四、模型选型别整花活树模型能打十年我们也试过把 code diff 扔进 TextCNN、CodeBERT 里学语义效果一言难尽——有时候它会给“修了个文案”的 MR 打高分因为训练语料里很多故障 commit 也改了文案。纯粹捣乱。最后老老实实用LightGBM。快、稳、可解释性好几千个 commit 几分钟训完AUC 能做到 0.82~0.85看业务线已经完全够用了。随手贴一段我们核心训练的骨架脱敏后的伪代码import lightgbm as lgb params { objective: binary, metric: auc, boosting_type: gbdt, num_leaves: 31, learning_rate: 0.05, feature_fraction: 0.8, scale_pos_weight: 5, # 正负样本不平衡下的正样本权重 } model lgb.train( params, train_data, valid_sets[valid_data], early_stopping_rounds50, verbose_evalFalse )评估的时候我们不只看 AUC而是看业务指标召回率Top20%。意思是模型给出最高风险的前 20% MR能抓住多少真正的故障变更。在我们几个核心业务线上这个值稳定在75%~80%之间。翻译成人话就是只重点测试 20% 的变更就能提前拦截四分之三以上的潜在线上事故。五、怎么让开发信你SHAP 可解释性是你唯一的武器光报个“高风险”三个字研发迟早会骂你是算命先生。我们一开始吃过亏被人在周会上当面质疑“凭什么说我的 MR 有风险”后来我们上了SHAP给每一个预测结果附上归因。MR 详情页里会显示“本次变更风险评估为 高主要因为【修改文件历史故障密度高】、【开发者当前迭代首次接触该模块】、【圈复杂度增量达 12】。”这玩意儿一上风向立马变了。研发看完不是甩锅而是主动去优化那坨高复杂度代码。有个客户端大佬看到 SHAP 归因里“圈复杂度”排第一直接重构了播放器模块后面三个迭代该模块故障清零。这个结果比任何模型指标都有说服力。六、工程落地别让它成为摆设要把它塞进发布流水线模型训完如果只生成一份报告邮件结局一定是躺在邮箱里吃灰。我们做了三件事把它真正用起来1. CI 自动触发预测提 MR 时Jenkins 流水线自动调模型推理接口。如果风险分为“高”自动在 MR 评论区发警告并给测试负责人打 IM 消息。2. 质量卡点高风险 MR强制要求至少两位高级工程师 CR 通过对应的自动化回归套件必须全部通过不允许跳过开发自测 checklist 由 AI 辅助生成重点验证场景基于 diff 涉及的接口3. 低风险快速通道对于风险分极低的 MR我们给了一条“轻测通道”只跑冒烟用例即可合入。这大大缓解了测试团队的人力瓶颈也是测试 leader 愿意全力推这件事的动力——他可以把人挪去真正危险的地方。模型服务本身用 Flask 简单包了一层后来迁移到公司内部的推理平台。每季度用最新数据全量重训一次防止模型老化。冷启动问题我们通过组织级架构知识图谱解决新模块没有历史数据但可以根据它依赖的底层库、所属业务域、代码结构相似度从相近模块“继承”一个初始风险分。七、不是所有团队都值得现在上模型最后泼盆冷水。如果你所在的团队业务刚起步两个月才发一次版样本量不到 500没有规范的 commit 和故障记录连哪个 commit 导致事故都查不出来质量文化薄弱开发根本不 care 预测结果那劝你先别急着上 AI。先搞数据基建把“谁写的代码搞出了事”这件事追踪清楚。没有这个基础模型就是空中楼阁。对于样本不够的小团队其实可以先搞一个加权规则引擎把前面那些特征加权求和人工调阈值。虽然糙但比裸奔强一百倍。写在最后离开抖音质量效能部有一阵子了回忆起来最让我自豪的不是模型 AUC 有多高而是有一天一个开发哥们儿在群里说“我靠这个 AI 比我老婆还了解我会不会写出 Bug。” 虽然作为技术人我更希望他把精力花在别让自己老婆有机会了解这方面。AI 从来不能取代测试工程师但它可以把测试从繁琐的全面覆盖里解放出来把有限的精力聚焦在最容易出事的地方。所谓“精准测试”不是炫技是你真真切切少接了半夜三点的告警电话。希望这篇文章能让你在自己的项目里少背几口锅。