高 bug 率的人该怎么对待:一场项目争论背后的对事、对人与系统
- 约 18 分钟读完 - 全文 6802 字高 bug 率的人该怎么对待:一场项目争论背后的对事、对人与系统
引子是一次真实的项目讨论。组里有位同事,bug 率明显比其他人高——高到 QA 看见他的名字心里先咯噔一下。评审会上大家争论怎么处理,冒出来的意见很能代表一个团队的所有本能:
- 「对事不对人,先别急着怪他,看看是不是模块本身烂。」
- 「烂模块别人也在写,怎么就他 bug 多?这就是人的问题,该敲打敲打。」
- 「要我说这是架构问题,没测试没 CI,谁写都得出事。」
- 「都 2026 年了,他连 AI 辅助 review、AI 生成测试都不用,bug 多不是活该吗?」
- 「你们都想简单了,这模块需求天天变、接口文档是过期的,这是设计问题。」
五种说法,五种药方,互相打架。这场争论当时没有结论,但它逼我把一个问题想透了:「高 bug 率」根本不是一个问题,而是一组完全不同的问题共用的一个症状。 这篇文章就是我后来理出来的完整处理路径:先把症状变成数据,再用方法论找全病根,然后对照行业里的成熟做法,最后才轮到那个最初的问题——对事,还是对人。
一、第一步:把「高 bug 率」变成数据
「bug 率高」是一个印象,不是事实。争论对错之前,至少要回答三个数据问题:
- 分母是什么? 按提交算、按功能点算,还是按代码行算?写核心复杂模块的人和写边角页面的人,天然不在一个 bug 密度量级上。不比单位产出只比绝对数量,是争论里最常见的暗坑。
- 分布长什么样? 他的 bug 集中在某个模块,还是均匀散布在他写的所有代码里?同一个模块换谁写都 bug 多,是系统的问题;同一个人换什么模块都 bug 多,才轮到看人。 这一条能砍掉一半的争论。
- bug 的类型是什么? 是低级错误(空指针、边界、拼写),还是逻辑误解(需求理解错、边界场景没想到),还是集成事故(改了 A 弄坏 B)?三种 bug 指向三种完全不同的病根。
没有这三组数据,会议上的所有「我觉得」都只是立场。这也引出一个朴素的道理:对事不对人的第一步,是先把「事」看清楚——多数争论不是因为价值观不同,而是因为大家在争论一个没被测量过的东西。
二、第二步:逐条验证,五种说法归并成四种病根
拿着数据回到会上的五种说法,逐条对照,会发现它们归并完只剩四种病根:
| 会上的说法 | 数据特征 | 归并后的病根 | 正确药方 |
|---|---|---|---|
| 「就是人的问题」 | 跨模块一致偏高,明知规范仍绕过,同类错误重复犯 | 能力、习惯或态度 | 先教练,无效再惩戒 |
| 「架构问题」+「AI 用得太少」 | 全组在该模块都偏高;逃逸率高(漏到线上才发现);重复性低级错误占比高、测试覆盖率远低于组均 | 工程系统缺位:无测试、无 CI、高耦合、反馈慢,AI 工具链未流程化 | 补工程系统(含 AI 工具链默认化),不怪任何人 |
| 「设计问题」 | bug 集中在「需求理解偏差」类;返工率高 | 需求模糊、接口契约缺失 | 修设计流程,不归咎执行者 |
| 「对事不对人」所暗含的一次性事故 | 无模式,单次事故 | 运气 + 单点疏忽 | 复盘机制,不动人 |
为什么「AI 用得太少」和「架构问题」并成一行?因为用要素维度分解(后文的鱼骨图),AI 工具链属于「机」这一格:团队没把 AI review、AI 生成测试、CI 静态检查做成流程默认项,这是系统缺位,不是哪个成员的问题。只有一种情况它退化回「人」:工具链已经是默认项,而个人拒绝使用——那属于习惯与技能问题,交给后文的 Just Culture 框架处理。
这个 2026 年的新变量值得多说两句,因为它有正反两面。
正面:「AI 用得不够」首先暴露的是系统问题,不是个人懒惰。 如果团队没有默认开启 AI 辅助 review、没有用 AI 生成测试基线,那么「某个成员不用 AI」暴露的是工程系统的缺失——别人不用你也发现不了,只不过他 bug 多先暴露了。正确的动作是把 AI 工具链做成流程默认项,让所有产出都过一遍机器筛子,而不是点名批评谁「不拥抱新技术」。
反面:AI 生成的代码不经 review 直接提交,是 2026 年高 bug 率的新来源。 见过不止一个案例,当事人解释「这是 AI 写的,我大意了」——这不能作为免责理由。AI 是放大器,责任心仍是人的。用 AI 放大产出可以,用 AI 稀释 review 不行。规矩要立在团队层面:AI 生成的代码,提交人负全责。
另外单独提醒「设计问题」:它是最容易被冤枉成「人的问题」的一种。需求一句话、接口靠口头、边界场景靠猜——这种环境下产出的 bug,记在程序员头上是冤案。判断方法很简单:把他的 bug 逐条归因,如果一半以上可以追溯到「需求没说清」,那该改的是需求评审流程,不是他。
三、第三步:病根怎么找全——三层归因方法论
五种说法来自一场会议的即兴发挥,够用吗?不够。它漏掉的病根我在不同团队真实遇到过:排期压垮质量、度量口径本身失真、外部依赖的脏数据、频繁切换任务导致的认知断裂……经验枚举永远有盲区,需要的是一套能生成假设、保证不重不漏的方法论。我用三把刀。
第一把刀:引入链分解——按 bug 的生命周期切
每个 bug 都有完整的生命周期:需求 → 设计 → 实现 → 验证 → 集成 → 发布 → 运行。沿链条逐环问「这一环会不会是病根」,得到一阶的病根空间:
| 环节 | 典型病根 | 会上的对应说法 |
|---|---|---|
| 需求 | 需求模糊、频繁变更、边界未定义 | 设计问题 |
| 设计 | 接口契约缺失、状态机没画清、边界场景没想 | 设计问题 |
| 实现 | 人的能力/习惯、AI 生成未审查 | 人的问题 |
| 验证 | 无测试、review 走过场、AI 工具链缺位 | 架构问题 |
| 集成 | 高耦合、改动半径不可控、无回归机制 | 架构问题 |
| 发布/运行 | 环境差异、脏数据、依赖服务不稳定 | (会上没人提——这就是盲区) |
会上的说法覆盖了链条的前五环,却漏了最后一环——线上 bug 里有相当比例是环境、数据、依赖造成的,把这类算在程序员头上是彻头彻尾的冤案。
第二把刀:鱼骨图 6M——按要素维度切
链条切法还有交叉项漏网,再换制造业用了半个世纪的鱼骨图(人机料法环测)做一次正交检查:
graph LR
R["高 bug 率"] --> M1["人<br/>能力 / 习惯 / 态度 / 状态"]
R --> M2["机<br/>工具链 / AI / 编译检查 / IDE"]
R --> M3["料<br/>需求质量 / 文档 / 输入数据"]
R --> M4["法<br/>流程 / 评审 / 排期 / 协作方式"]
R --> M5["环<br/>环境 / 依赖服务 / 任务切换频率"]
R --> M6["测<br/>测试覆盖 / 度量口径 / 统计方法"]
6M 的价值在于它逼你检查平时想不到的维度:
- 法里面藏着排期——连续赶工期的团队,bug 率上升是生理现象,不是态度问题;
- 环里面藏着任务切换——一个人同时被拉在三个项目里,认知断裂本身就是 bug 发生器;
- 测里面藏着最容易被忽略的一条:度量口径本身可能就是病根。bug 怎么算、谁录入、严不严格,直接扭曲「谁 bug 率高」的结论。度量一旦成为目标就不再是好的度量(古德哈特定律),拿失真的数据去惩人,是双重错误。
两把刀交叉,病根空间基本不重不漏:链条保证流程维度不漏,6M 保证要素维度不漏。
第三把刀:控制变量——把相关性变成归因
枚举出假设只是开始,归因要靠实验设计。核心思想是方差分析:bug 率是人、模块、需求、流程的多元函数,每次只动一个变量,看结果怎么变:
- 换人写同模块:bug 率不降 → 模块/需求的问题;降了 → 继续往下查人;
- 同一个人写别的模块:还是高 → 人的嫌疑上升;正常了 → 原模块的问题;
- 同一需求给两个人分别实现:bug 模式相似 → 需求/设计的问题;差异大 → 实现者的问题;
- 横向对照组:全组在同类模块上的 bug 基线是多少?没有基线,「偏高」二字本身就不成立。
这一套换组实验做完,多数「人的问题」会现出系统问题的原形,剩下真正属于人的部分,才轮到第五节处理。
最后补一个贯穿全程的视角:每个线上 bug 都是两个失败的乘积——引入失败 × 拦截失败。 只问「谁引入的」是在追责,同时问「为什么六道防线一道都没拦住」才是在建设。前者最多改变一个人,后者改变的是所有人的未来 bug 率。
四、第四步:行业里的成熟方法论与工具地图
三把刀都不是我的发明:引入链分解借自 IBM 的 ODC,鱼骨图来自石川馨,控制变量是实验设计的基本功。行业里的原版比我的速记法成熟,这一节直接对照。
归因层:ODC(正交缺陷分类)
ODC(Orthogonal Defect Classification,Chillarege 等人 1992 年提出) 的核心思想比「按环节分类」更深一层:把每个缺陷沿正交维度(缺陷类型、触发条件、影响、来源、发现阶段)打标签,然后看分布特征而不是个案——某一类缺陷在某个阶段的分布异常,直接反推哪个流程环节出了系统性问题。缺陷不是待修的孤案,而是流程的测量数据。这套方法在 IBM、电信和航空软件里跑了三十多年。小团队不必全套上 ODC,但最低限度要做到:缺陷入库时强制填「引入阶段」和「缺陷类型」两个字段——没有这两个字段,第一节要的三组数据永远凑不齐。
度量层:DORA 指标与缺陷逃逸率
「bug 率高」的行业标准度量不是我拍脑袋的三个数据问题,而是:
- DORA 四键指标(《Accelerate》):其中的变更失败率就是「高 bug 率」最权威的同行参照系——顶级团队与垫底团队的差距是数量级的,而且它和其他三项(部署频率、变更前置时间、恢复时间)联动,单独压 bug 率而不管交付节奏是缘木求鱼;
- 缺陷逃逸率:bug 在哪一环被发现(开发自测 / CI / 测试 / 线上),逃逸越靠后成本越高——这是「拦截失败」的标准度量;
- 缺陷聚集热力图:Microsoft Research 多年的实证结论——缺陷不是均匀分布的,少数模块集中了多数缺陷(与改动频率和复杂度强相关),CodeScene 这类工具可以直接画出热度图。这直接支撑了「换人写同模块」实验的必要性:热点模块的 bug 率天然高,不控变量就是在冤枉人。
防线层:shift-left 与工具链
「拦截失败 × 引入失败」的乘积视角,行业里叫 shift-left(防线左移),配套工具链已经高度成熟:
| 防线 | 方法论 | 工具(2026 年现网主流) |
|---|---|---|
| 需求/设计 | 设计评审、ADR(架构决策记录)、契约先行 | 设计文档模板 + 评审清单 |
| 实现 | 测试金字塔(Google 的 small/medium/large 分层)、TDD、小步提交 | JUnit/pytest + 覆盖率(只做参考,不做 KPI——古德哈特定律) |
| 静态检查 | 静态分析、安全扫描 | SonarQube、CodeQL、Semgrep、Snyk |
| 测试有效性 | 变异测试(测试的测试)、模糊测试 | PIT(Java)、fuzzing |
| 代码审查 | AI review 设为 CI 默认门禁 | CodeRabbit(部署最广)、Greptile(全库上下文检出率最高)、Qodo(审查+测试生成)、GitHub Copilot Code Review、Cursor BugBot |
| 测试生成 | AI 生成单测基线 | Diffblue Cover(Java)、Qodo |
| 集成/发布 | Trunk-Based Development、小 PR、CI 门禁、灰度 | GitHub Actions / GitLab CI + 特性开关 |
两点强调:其一,2026 年的 AI review 工具已经过了尝鲜期——CodeRabbit 接入仓库数以百万计,Greptile 靠全代码库索引拿到最高检出率——「不配 AI 审查门禁」在今天的含义,和五年前「没有 CI」一样,属于工程系统缺位,这印证了第二节把 AI 折进工程系统的判断。其二,覆盖率、检出数这些指标一旦变成 KPI 就失效,它们只能用来找盲区,不能用来排名。
机制层:错误预算与安灯——「宽猛相济」的工程制度
第六节要讲的「宽与猛怎么配比」,行业里其实早已制度化了,两个现成机制:
- Google SRE 的错误预算(error budget):服务的可靠性目标不是 100%,预算内随你折腾(宽),预算耗尽立刻冻结发布、全员还债(猛)。什么时候该宽、什么时候该猛,不再依赖管理者的手感,而是事先约定的数字——这是「小惩大诫」最优雅的工程实现:惩戒自动触发、剂量事先封顶、对事不对人。书架上有我的《Google SRE 工作手册》笔记,错误预算一章值得细看;
- 丰田的安灯(andon)与防错(poka-yoke):流水线上任何人发现异常都可以拉灯停线——「对事不对人」不是口号,是给一线员工的制度性权力;防错设计则直接把「人会犯的错」设计成「犯不了的错」,比任何惩戒都高明。bug 率治理的终极目标不是把人管得更紧,而是让低级错误在机制上不可能发生。
所以这一节的结论是:归因用 ODC 的思想,度量用 DORA 与逃逸率,防线用 shift-left 工具链,宽猛用错误预算制度化——我的「三把刀」只是这些成熟方法论的穷人版速记,团队有条件就应该直接上原版。
五、第五步:轮到「人」之后——Just Culture 三分法
排除了系统性因素,剩下的才轮到「人」——而处理人,最怕一刀切。安全科学里的 Just Culture(公正文化)把行为分成三类,每一类的正确回应完全不同:
| 行为类型 | 特征 | 正确回应 |
|---|---|---|
| 无心之失 | 认真做事,但人都会犯错 | 安慰 + 改系统,绝不罚——罚了以后没人再上报错误 |
| 侥幸冒险 | 明知规范但绕过了(侥幸没出事) | 教练谈话,明确后果预期 |
| 明知故犯 / 屡教不改 | 重复再犯,警示无效 | 及时、小剂量、成比例的惩戒 |
用「对事不对人」处理第三类,是在纵容——恶意和惰性会学习你的宽容;用「敲打」处理第一类,是在制造恐惧文化——从此 bug 都被藏起来,团队失去学习的输入。冲突从来不在两种哲学之间,而在用错了对象。
六、更高处的统一:系辞与 Dalio
「对事不对人」和「该敲打敲打」这两种看似对立的姿态,恰好各有一部经典背书,而两部经典在更高处说的是同一件事。
《系辞传》讲「小惩而大诫,此小人之福也」。注意上下文——它的对象是「不威不惩」的小人,即内在荣辱感已经失灵、只有外部后果能触动的人;注意落点——「福」:惩罚是为受罚者本人的长远利益服务的。《系辞》下传还有一句「无有师保,如临父母」:管教的心是父母之心,不是权力之心。
Dalio《原则》讲对事不对人、极端求真(radical truth):当面指出问题比背后留情更仁慈,这是 tough love。
两者在最高处是同一个标准:评价一次干预,不看「有没有怪罪人」,而看「它有没有让反馈回路闭合、有没有让这个人变得更好」。 惩罚如果是为了发泄、为了立威,就同时背叛了系辞和 Dalio。孔子论政说「宽以济猛,猛以济宽,政是以和」——宽(对事不对人)与猛(小惩大诫)不是二选一,是因人因事因时动态配比的阴阳两端:只宽不猛,组织失去边界;只猛不宽,组织失去真话。
而两者共同信仰的,是《系辞》最看重的那个字——几:「几者,动之微,吉之先见者也。」反馈必须早、必须真、必须被感觉到。Dalio 用真话送达信号,「小惩」用后果送达信号;信道不同,信源相同。等到 bug 率高到要开会讨论才处理,已经是「不俟终日」的反面教材了。
七、落地:一棵判断树
graph TD
A["有人 bug 率明显偏高"] --> B{"看分布<br/>同一模块换谁写都高?"}
B -- "是" --> C["系统问题<br/>补测试 / CI / 解耦 / AI 工具链<br/>不归咎任何人"]
B -- "否, 集中在他" --> D{"看 bug 类型"}
D -- "需求理解偏差类居多" --> E["设计问题<br/>修需求评审与接口契约流程"]
D -- "低级重复错误居多" --> F["工程系统缺失<br/>AI review / 测试基线设为流程默认项"]
D -- "集成事故类居多" --> G["架构耦合问题<br/>修边界与回归机制"]
D -- "跨模块一致的逻辑疏忽" --> H{"看人: 无心之失<br/>还是明知故犯 / 屡教不改?"}
H -- "无心之失" --> I["教练 + 结对 + 改系统<br/>绝不罚, 保护上报文化"]
H -- "侥幸冒险 / 屡教不改" --> J["小惩大诫<br/>及时 / 小剂量 / 成比例<br/>动手前自问: 如临父母还是发泄立威?"]
动手惩戒之前,还有最后一道自检,三个问题按顺序问:
- 什么类型? 无心之失和善意试错,绝不罚——罚了就没人上报了。
- 多早? 越早代价越小,「小惩而大诫」的精髓不在「惩」而在「小」。问题在几微阶段,一次明确而克制的谈话就够;拖到「恶积而不可掩」,就只剩「何校灭耳」的选项了。
- 为谁? 这一下是为了回路闭合和他的成长,还是为了我的情绪?答不出前者,就不该动。
八、收尾
回到那场争论。会后我做了归因分析,结论有点扫兴也有点解脱:那位同事 60% 的 bug 出在同一个无测试、需求三天一变的老模块——是系统问题;剩下的低级错误里,AI review 能拦掉一大半——是工程系统问题;真正属于他个人的,是重复犯同类边界错误、提醒后照旧——这部分,一次正式的、明确的、带着「这是为你好」的谈话处理了。三个月后,他的 bug 率回到了组内均值。
所以「对事还是对人」是个伪问题。完整的答案是:先对事(数据归因),再对系统(方法论找全病根),最后才对人(Just Culture 分类处理);对事不对人是道的常态,小惩大诫是道的边界——两者的根,都是让反馈回路不失灵。
延伸阅读:书架上《原则》笔记里有 Dalio 的完整决策框架,《系统之美》讲透了为什么「人的行为来自系统结构」,《系辞传全文与白话译文》里有「小惩大诫」的原文语境。