3001 字
约 10 分钟
13
S06-A10 · 缺陷密度的长期统计:谁的数据能信

季度会上有人放了一张图:缺陷密度比去年同期下降 30%。没人反驳数字,但也没人相信结论。理由随手就能凑齐——重构砍掉了三分之一代码,需求拆得比去年细,还有一批问题被登记成"体验优化"。三个说法都成立,说明这个比值已经不能单独承载"质量变好"的判断。

缺陷密度不是测量,是除法。分子分母都能被口径搬动,搬动之后趋势就从观测量变成叙事工具。这篇只谈一件事:把密度用于跨年比较时,先固定什么、放弃什么、什么时候干脆不用。


1. 分母:四种算法,四个偏差方向

同一批缺陷,换分母能得到三种相反的结论。

分母 算法 偏差方向 什么时候"自动变好"
代码行(KLOC) 缺陷数 / 千行存量 分母是存量,新增代码的问题被摊薄 删代码、抽公共库、生成代码与注释被计入
功能点 / 需求数 缺陷数 / 已交付条目 粒度由拆单习惯决定 把一个大需求拆成十个条目
变更量(churn) 缺陷数 / 变更行数 分母随开发强度波动 一次大重构把分母顶起来
时间(人月 / 迭代) 缺陷数 / 单位时间 分母受产能与休假影响 扩编、并行迭代变多

代码行口径最危险的一点:删代码会让密度变差。一次成功的收敛(删掉重复实现)在报表上表现为质量下降,这会直接压制重构意愿。反过来,把注释、生成代码、迁移脚本都计入分母,等于给自己发了一张免费的改善券。

密度 = 缺陷数 / 分母
// 分母的选择不看精度,看"能不能被同一批人搬动"
// 存量口径:适合看长期技术债,不适合看版本质量
// 变更口径:适合看"这段改动有多脏",不适合跨团队比较
// 时间口径:适合看趋势斜率,绝对值没有意义

选口径的判据只有一条:分母要尽量不受被考核方的行为影响。存量代码行受重构影响,需求条目受拆单影响,变更行受提交粒度影响,时间受排期影响——四个都不干净,只能选最难操纵的那个,并把它写进文档。

实务上更稳的做法是双分母并行:变更口径看版本质量,时间口径看长期斜率。两条线同向,结论可以采信;一条改善一条恶化,说明改善发生在口径而不是发生在代码里。单条曲线永远可以讲出故事,两条互相牵制的曲线很难同时撒谎。


2. "下降"的三种解释:先证伪,再采信

密度下降有三个都成立的解释,只有第一个是好消息。

解释 伴随证据 证伪方式
质量真的变好 逃逸率同步下降、复发率下降、修复时长缩短 三个伴随指标至少两个同向
缺陷被发现得晚了 生产缺陷占比上升、单次修复时长上升、事故数上升 按发现阶段拆分密度曲线
统计口径变了 分母定义、归因规则、登记入口之一被改过 口径变更必须留版本与生效日期

第二种解释最容易被误读成好消息:编码期缺陷少、生产缺陷多,总密度可能持平甚至下降,因为难发现的问题本来就更少。判断方法只有按发现阶段拆开——编码期、测试期、预发、生产四段,任何一段的占比突变都说明密度曲线换了含义。

─── 口径快照,随报表一起归档
metric: defect_density
version: 3
effective_from: 2026-01-01
numerator: defects_filed        // 按单计数,不按严重度加权
denominator: churn_lines        // 变更行数,含删除、排除生成代码
scope: [repo_a, repo_b]
attribution: fix_location       // 归修复位置;引入位置另存一列

长期统计要固定的不是数字,而是口径。口径变一次,历史曲线就断一次;断点必须标注,不能把两段拼成一条"持续改善"的线。实操做法:口径版本进报表页脚,口径变更次数本身做成一个指标——一年改三次口径的团队,密度趋势的可信度接近零。

─── 拼接检查:一条曲线能不能跨过断点读
断点 2025-07:分母从 KLOC 改为 churn_lines
断点 2026-01:归因从 fix_location 改为 dual_field
// 允许:分段比较(断点前后各算各的斜率)
// 禁止:把断点前后的点连成一条线并标注"同比"
// 需要:断点当季单独出图,标注受影响幅度

断点处理有个便宜的办法:在断点季度同时报告两套口径,用一个季度的重叠样本估算口径差。重叠缺失时,只能承认两段不可比,这比假装连续更省事。


3. 归因:分子是怎么被造出来的

分母决定尺度,分子决定方向。缺陷单的归因规则比统计工具更能左右结论。

陷阱 表现 对趋势的影响 处理
一次改动报多个单 同一根因拆成三个现象单 分子虚高,修完"大幅改善" 按根因合并,另记根因单
跨模块缺陷归给谁 引入在 A 模块、暴露在 B 模块 热点模块背锅,源头隐形 双字段:引入位置 + 暴露位置
环境问题算不算 配置、依赖、数据问题计入或不计 计入则基础设施差被记成代码差 单列"环境类",不并进代码密度
严重度主观 定级靠开会争 等级结构变化伪装成质量变化 趋势只看单数,等级只用于排优先级

严重度的污染是隐性的:定级标准一旦收紧(把所有 P2 降成 P3),加权密度立刻下降,而缺陷一个没少。趋势统计用计数,不用加权分;加权分留给资源分配。

─── 归因双字段:让"引入位置"和"暴露位置"同时可查
SELECT introduced_module, exposed_module, COUNT(*) AS n
FROM defect
WHERE found_stage = 'prod'
GROUP BY introduced_module, exposed_module
ORDER BY n DESC;
// 两个位置差异大的组合 = 耦合与测试缺口同时存在的地方

把密度当考核指标,反噬是可预测的:隐匿不报、拆单、降级、把缺陷改称"需求变更"。这四种行为都不违反流程,只让数据失去解释力。所以密度的正确位置是观测,不是目标。真要考核质量,换成一组更贴近后果的量:

─── 密度按发现阶段分解,判断"下降"发生在哪一段
stage        code   test   staging   prod
defects       83     61      12       9
density      0.41   0.30    0.06     0.04
// code 段下降而 prod 段上升 = 发现点后移,不是质量改善
// 四段同时下降才算质量真的变好
替代度量 定义 回答的问题
逃逸率 生产发现缺陷 / 全部缺陷 门禁是否有效
修复时长 从登记到关闭的中位数 响应能力,不随单数波动
复发率 同源缺陷再犯 / 已归纳根因的缺陷 改进是否落地
同源聚集 单一模块缺陷占比 风险是否集中

4. 排错:读数反直觉时的五种情况

现象 更可能的原因 处理
密度骤降但事故没少 分母被重构顶起,或归因改了口径 拉分母曲线与口径版本对照
某模块密度极高 它承接了别的模块的问题 看引入位置分布,别只看修复位置
人均缺陷数下降 扩编,或缺陷进了暗账 用绝对单数与逃逸率交叉验证
密度平稳但交付变慢 缺陷变重、修复变长,单数掩盖成本 引入修复时长与返工率
阈值沿用三年没人质疑 度量老化 每年按当年分布重算分位

最后一条值得单独说:阈值会老化。三年前"密度低于 0.8 算健康"是从当时的分布切出来的;语言、结构、测试手段全变之后,这个数只剩仪式意义。阈值必须每年从当年分布重算,并接受它会上移——上移不是退步,是分母与工具一起变了。


5. 边界:什么时候这套统计不值钱

场景 密度统计的解释力 该换成什么
团队少于三人 接近零,单数波动主导 直接看事故与修复时长
缺陷单本身不规范 低,归因字段常年空着 先修登记流程,再谈统计
系统处于重写期 负值,旧代码与新代码混算 新旧两套代码分别记
变更量年际差异超一倍 低,分母主导趋势 用时间口径看斜率
只有半年数据 低,跨不过一个版本节奏 至少覆盖两个完整发布周期

统计周期要长到能跨过版本节奏:单个版本的缺陷分布受排期、假期、大客户上线影响,单版本比较几乎必然误判。两个完整周期是下限;跨年比较才有决策价值。还有一条容易被跳过——缺陷与变更量是乘法关系,密度下降常常只是因为变更量涨了。看到改善,先把分母画出来。

─── 用密度前先过的四道判据
usable: false
reasons:
  - 缺陷登记字段完整率 < 80%     // 归因字段常年空着
  - 数据跨越的发布周期 < 2       // 跨不过一个节奏
  - 期间口径变更次数 >= 2        // 曲线已断
  - 团队规模 < 3                 // 单数波动主导
// 任一条命中:密度只作为叙事,不作为判据

四道判据里最容易忽略的是第一条:缺陷单本身不规范时,所有统计动作只是在给噪声加权。先修登记流程,再谈密度。


6. 自测

  1. 用代码行做分母,一次成功的重构会让密度朝哪个方向走?为什么这会压制重构意愿?
  2. 密度下降而事故数上升,最可能对应三种解释里的哪一种?用什么拆分方式确认?
  3. 为什么说"口径变更次数"本身应该是一个指标?
  4. 跨模块缺陷只记修复位置,会系统性地高估哪个模块的风险?
  5. 严重度加权分为什么不适合用来做趋势判断?
  6. 团队只有三人时,密度统计该被替换成哪两个量?
S06-A10 · 缺陷密度的长期统计:谁的数据能信
https://www.clxhxhhr.top/posts/6308/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。