Skip to content

为什么是这些指标

dowsing 报告的每个指标都是一个选择,每个选择都有出处。本页讲的就是这些出处,以及更实用的一点——每个指标从哪里开始不再可靠

先记住四件事

在谈具体指标之前,有四条注意事项对全部指标都成立。

  1. 各种复杂度代理彼此高度相关。 LoC、缩进深度、圈复杂度、认知复杂度——作为 hotspot 的一阶近似,它们给出的排序相似。没有一个能完整代表「代码有多难懂」。
  2. 「耦合」有若干互相竞争的定义,区别在分母。用哪一个,数字会有实质差异。dowsing 固定使用一个,并明确说明是哪一个。
  3. 这些指标多数与缺陷相关,而非导致缺陷。 请把它们当作排优先级的启发式,而不是证据。
  4. 历史数据被污染的程度,比公式的精度更影响结果。 未被追踪的重命名、格式化提交、squash——这些造成的偏移远大于任何定义上的细节。这就是这些数字可信吗单独成页的原因。

Hotspot

这个想法来自 Adam Tornhill:hotspot 是你不得不反复打交道的复杂代码。它建立在更早的实证研究之上:

  • Graves et al. (2000, TSE) —— 过去的修改次数比代码行数更能预测缺陷。
  • Nagappan & Ball (2005, ICSE) —— 相对 churn 能高精度预测缺陷密度。

在原始表述里,这是一个二维交叉,而不是固定的乘积。dowsing 实现的是两个归一化排名的乘积,因为一个做优先级排序的工具最终必须给出排名。

LoC 是复杂度的合法替身吗

在文件级别上,基本是的。Jay et al. (2009) 考察了 120 万个文件,发现圈复杂度 ≈ a·LoC + b,R² ≈ 0.90。

重要的但书是:归一化到函数级别后,相关性会减弱。 所以用 LoC 给文件排序没问题,一旦某个文件值得关注,就应该下沉到函数级别——这正是 dowsing health --file 给出逐函数数值的原因。

变更耦合

概念来自 Gall et al. (1998, ICSM)(逻辑耦合),由 Zimmermann et al. (2005, TSE)(关联规则)扩展,并由 D'Ambros et al. (2009, WCRE) 与缺陷建立联系。

|A| 为改动过 A 的提交数,|A∩B| 为同时改动两者的提交数:

流派分母公式对称
Tornhill / Code Maat / CodeScene平均2·|A∩B|/(|A|+|B|)
Zimmermann(confidence)|A||A∩B|/|A|
Jaccard并集|A∩B|/(|A|+|B|−|A∩B|)
minmin(|A|,|B|)|A∩B|/min(|A|,|B|)

dowsing 默认使用对称的 Tornhill / Code Maat 形式,同时也报告有方向的 confidence。这个选择很关键:同一对包,取表里不同的行会显示出差别很大的耦合度,所以一个不告诉你用了哪一个的工具,给出的数字根本无从解读。

默认阈值的出处

来源最小修订数最小共享最小耦合度大提交阈值
Code Maat5530%> 30 个文件
CodeScene101050%> 50 个文件
Zimmermann/ROSEsupport > 1confidence > 0.5> 30 个实体

dowsing 取 Code Maat 的数值,配上 CodeScene 更宽的 changeset 阈值。这些是起点,不是真理。 如果 30% 下什么都没出现,就把它调低——26% 但共变更 90 次的一对,仍然在告诉你一些事情。

认知复杂度

Campbell / SonarSource。 出发点是:圈复杂度是为衡量可测试性而设计的,作为可理解性的度量并不合适。认知复杂度被明确设计为给程序员的直觉赋值。

三条规则:

  1. 忽略那些把多条语句读成一条的简写结构。
  2. 线性流程每被打断一次 +1。
  3. 这种打断每嵌套一层,再加分。
类型嵌套加分增加嵌套深度例子
Structuralifforwhilecatchswitch、三元
Hybridelse ifelse
Fundamental逻辑运算符链、递归、带标签的 break

实际后果是:a && b && c 只算一次而非逐个运算符,而嵌套三层的 if 代价远高于顶层的 if。对一个声称衡量阅读成本的指标来说,这正是你想要的行为。

Code Health

dowsing 的 health 刻意不是 Maintainability Index。

MI 是一个回归多项式,因推导不透明而受到严厉批评(van Deursen, 2014):它的输入全都与规模相关,而平均化会掩盖风险最高的部分。它的系数在不同资料间也不一致。dowsing 不使用它。如果你在别处看到 MI 式的单一数字,请先问它是由什么构成的。

取而代之:

health = 10 − Σ(weight × severity)

每一项都可见,每个权重都可配置,工具会告诉你哪个坏味道扣了几分。它比 MI 简单,而重点在于你能验算它

Ownership 与 bus factor

按新增行数而非 blame 判定 ownership。 git blame 把一行归给最后碰它的人,于是格式化的人会盖过写逻辑的人。统计整个历史中的新增行数,是「谁了解这段代码」更稳定的信号。

minor contributor:ownership 低于 5% —— 来自 Bird et al.,他们发现 minor contributor 数量多与缺陷相关。

bus factor 遵循 Avelino et al. (ICPC 2016):为每个开发者、每个文件计算 Degree-of-Authorship,然后贪心地移除拥有文件最多的开发者,直到仍有作者的文件不足一半。DOA 的系数来自 Fritz et al. (ICSE 2010)

DOA(d,f) = 3.293 + 1.098·FA + 0.164·DL − 0.321·ln(1 + AC)

这是团队风险指标,不是绩效考核

Tornhill 这样警告,我们也一样。「bus factor 为 1」说的是某个包的知识集中在一个人身上。它没有说那个人做错了什么——通常恰恰相反。

这个数字还有一个特有的脆弱之处:如果开发者用多个邮箱提交、而你的 .mailmap 没有合并他们,dowsing 会把他们当成不同的人,从而高估 bus factor。包看起来比实际更安全。这种失效是无声的,所以值得明说。

延伸阅读

基于 MIT 许可发布