Skip to content

全部公式

dowsing 如何给你的代码打分,没有任何隐藏。本页是完整清单。每个数字的出处见为什么是这些指标

每个阈值都是假设

它们都有文献依据,但没有一个保证适合你的仓库。全部可配置。

Hotspot

change_frequency(f) = 时间窗口内改动过 f 的提交数(已剔除合并/bot/纯格式化)
complexity(f)       = 选定的复杂度代理(默认:有效 LoC)
hotspot_score(f)    = rank(change_frequency) × rank(complexity)

rank 是归一化到 0–1 的百分位。只有两项都高的文件才会排到前面。min-max 归一化的值也一并保留在输出中。

从未改动过的文件恰好为 0,无论它多复杂——没人碰的代码,投入没有回报。

向包聚合时两种方式都会给出。

hotspot_sum(pkg)          = Σ hotspot_score(f)
hotspot_loc_weighted(pkg) = Σ(hotspot_score(f) × loc(f)) / Σ loc(f)

复杂度

指标定义
有效 LoC剔除注释与空行后的行数。默认代理。
圈复杂度每个判定点 +1(等同 ESLint complexity):if、循环、casecatch、三元运算符。
认知复杂度对嵌套加罚(Campbell / SonarSource)。逻辑运算符链只算一次,而非逐个运算符。

--complexity=loc|cyclomatic|cognitive 切换。

变更耦合

默认采用对称的 Tornhill / Code Maat 形式。

degree(A,B)      = 2·|A∩B| / (|A|+|B|)     对称耦合度
confidence(A⇒B)  = |A∩B| / |A|             有方向(可选)

默认过滤条件:

项目默认值来源
最小修订次数5Code Maat
最小共享提交数5Code Maat
最小耦合度30%Code Maat
大 changeset 排除> 50 个文件CodeScene

排除大 changeset,是因为纠缠提交——一个提交里塞进多个关注点——会伪造耦合。它们仍计入 churn,只是不参与耦合计算。

degree 的分母

|A| 是触及 A 的全部修订数,包括 A 单独改动的提交。丢掉这些会让分母变小,从而高估耦合度。这在实现过程中是一个真实出现过的 bug。

隐藏耦合

耦合会与包依赖图对照后分类。

kind含义
hidden依赖图中没有边——结构里看不见的耦合
type-only只通过类型相连,没有值依赖
declared-only在 package.json 中声明了但从未被 import
obvious存在依赖关系,一起改动是意料之中

依赖图同时包含 package.json 中声明的依赖,以及 oxc 抽取出的实际 import。

Churn 验证

把 AST 哈希成归一化指纹,跨修订比对。

fingerprint(src) = hash(仅剔除标识符 name/value 后的 AST 结构)
                   运算符、字面量值与修饰符带有语义,因此保留在结构中
判定条件
identical内容完全一致(文件移动、模式变更)
format-onlyAST 连名称都一致(空白、注释、分号)
rename-only结构哈希与节点数一致(标识符被重命名)
changed其他情况
unknown解析失败,或节点数少于 5
effective_commits(f) = |changed| + |unknown| + 1

刻意的非对称

unknown 被算作真实修改。把无法判定的修改错误地当成「虚假 churn」剔除(假阴性),比稍微高估 churn(假阳性)更危险。

不需要类型信息,比对语法 AST 就足够了。

Code Health

health(f)   = 10 − Σ_smell (weight × severity)      下限 1.0
health(pkg) = LoC 加权平均

severity(value, threshold) = min(1, (value − threshold) / (threshold × 2))

severity 在恰好等于阈值时为 0,在三倍阈值处饱和到 1。这样单个离群值不会把分数打到底。

已实现的坏味道与默认权重:

坏味道阈值权重理由
High Cognitive Complexitycognitive > 153.0最直接反映理解成本
Complex Methodcyclomatic > 102.5测试需要覆盖的路径数(McCabe)
Large FileLoC > 4001.5属于间接信号,因此权重较轻
Many Argumentsargs > 41.0与每个调用方的耦合

权重合计为 8.0,因此在默认配置下最差的 health 是 2.0。增加坏味道会降低这个下限。

LoC 基线总是打印在分数旁边。如果差距很小,那个 health 分数就是文件长度换了身衣服。

Ownership 与 bus factor

主要开发者按新增行数判定,覆盖完整历史。git blame 会把一行归给最后碰它的人,这是弱得多的信号。

minor contributor = ownership 低于 5% 的贡献者   (Bird et al.)

bus factor 遵循 Avelino et al. (ICPC 2016):计算 Degree-of-Authorship,再做贪心移除。系数来自 Fritz et al. (ICSE 2010)。

DOA(d,f) = 3.293 + 1.098·FA + 0.164·DL − 0.321·ln(1 + AC)
  FA = first authorship(d 创建了 f 则为 1)
  DL = d 对 f 的修改次数
  AC = 除 d 以外所有人对 f 的修改次数

DOA_N(d,f) = DOA(d,f) / max_d' DOA(d',f)
author(d,f) ⇔ DOA_N > 0.75 且 DOA ≥ 3.293

贪心地反复移除拥有文件最多的作者。当仍有作者的文件低于 50% 时,已移除的人数就是 bus factor。

dowsing 按包计算这个值。 现有工具只给出整个仓库或单个文件的结果,两者都无法告诉你代码库的哪一部分会变得无法维护。

前提条件

必须通过 .mailmap 合并身份并剔除 bot。如果一个人用多个邮箱提交,bus factor 会被高估——包看起来比实际更安全。dowsing 在收集 git 历史时就完成了这两步。

被排除了什么

完整说明见这些数字可信吗。简要地说:

对象原因
合并提交它们本身不携带修改
bot 提交会扭曲 ownership
纯格式化提交只改空白的提交会撑大 churn
.git-blame-ignore-revs 条目GitHub 同样遵循的标准
生成代码与锁文件虚假 churn 的典型来源
大 changeset纠缠提交——只从耦合中排除

排除计数总是会被打印。 悄悄排除会让你把输出读成「全部分析过了」。

基于 MIT 许可发布