全部公式
dowse 如何给你的代码打分,没有任何隐藏。本页是完整清单。每个数字的出处见为什么是这些指标。
每个阈值都是假设
它们都有文献依据,但没有一个保证适合你的仓库。全部可配置。
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、循环、case、catch、三元运算符。 |
| 认知复杂度 | 对嵌套加罚(Campbell / SonarSource)。逻辑运算符链只算一次,而非逐个运算符。 |
用 --complexity=loc|cyclomatic|cognitive 切换。
变更耦合
默认采用对称的 Tornhill / Code Maat 形式。
degree(A,B) = 2·|A∩B| / (|A|+|B|) 对称耦合度
confidence(A⇒B) = |A∩B| / |A| 有方向(可选)默认过滤条件:
| 项目 | 默认值 | 来源 |
|---|---|---|
| 最小修订次数 | 5 | Code Maat |
| 最小共享提交数 | 5 | Code 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-only | AST 连名称都一致(空白、注释、分号) |
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 Complexity | cognitive > 15 | 3.0 | 最直接反映理解成本 |
| Complex Method | cyclomatic > 10 | 2.5 | 测试需要覆盖的路径数(McCabe) |
| Large File | LoC > 400 | 1.5 | 属于间接信号,因此权重较轻 |
| Many Arguments | args > 4 | 1.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。
dowse 按包计算这个值。 现有工具只给出整个仓库或单个文件的结果,两者都无法告诉你代码库的哪一部分会变得无法维护。
前提条件
必须先通过 .mailmap 合并身份并剔除 bot。如果一个人用多个邮箱提交,bus factor 会被高估——包看起来比实际更安全。dowse 在收集 git 历史时就完成了这两步。
被排除了什么
完整说明见这些数字可信吗。简要地说:
| 对象 | 原因 |
|---|---|
| 合并提交 | 它们本身不携带修改 |
| bot 提交 | 会扭曲 ownership |
| 纯格式化提交 | 只改空白的提交会撑大 churn |
.git-blame-ignore-revs 条目 | GitHub 同样遵循的标准 |
| 生成代码与锁文件 | 虚假 churn 的典型来源 |
| 大 changeset | 纠缠提交——只从耦合中排除 |
排除计数总是会被打印。 悄悄排除会让你把输出读成「全部分析过了」。