dowsing 是什么
它要解决的问题
在大型代码库——尤其是 monorepo——中,团队反复面对同一个问题:
在我们真正拥有的时间里,修哪里最划算?
技术债是无限的,重构候选也是无限的。时间不是。
传统静态分析(ESLint、SonarQube)看的是某一时刻的快照,并把所有问题列为同等重要。它产出成千上万条告警,却给不出优先级。而修那些复杂但从来没人碰的代码,回报几乎为零。
核心洞察
用 CodeScene 开创这一领域的 Adam Tornhill 说得很简单:
并非所有代码都等价。只有既改动频繁又复杂的代码——hotspot——才是真正的问题,投资在那里回报最高。
而「改动频繁」这件事,早就写在你的 git 历史里了。
把代码结构(静态分析)与变更历史(版本库挖掘)交叉起来,就是行为式代码分析。
dowsing 增加了什么
dowsing 把这个洞察,专门针对 TypeScript monorepo 重做了一遍。
1. 包边界是一等公民
CodeScene 支持 39 种语言,monorepo 是作为「一堆文件夹」后期适配上去的。它并不理解 npm/pnpm workspace,也不理解 Turborepo/Nx 的包边界与依赖图。
dowsing 直接读取你的 workspace 配置,在包依赖图之上做行为分析。下面这些能力都由此而来。
2. 它能找到隐藏耦合
两个彼此没有依赖的包,却总在同一个提交里被一起修改——这种耦合从不出现在结构里。
Nx 或 Turborepo 的 affected 告诉你要重新构建什么,但看不到这个。只看依赖图,你永远发现不了。
一个真实的检出:
@acme/admin-web ↔ @acme/customer-web 耦合度 33%,共变更 91 次,无依赖关系对这一对运行 dowsing why,会看到 server-api.ts 和认证页面在两边以几乎相同的形式存在,被诸如「给两个 Web 应用都加上会话过期后的重新登录引导」这类提交一起修改。这不是共享的抽象,是复制粘贴的代码。
3. 它会检查改动频率是否真实
git 的行级 diff 无法告诉你语义是否变化。重命名标识符、重新格式化、修改注释,在行 diff 上都像是大改动,会把改动频率撑大。CodeScene 自己也把「重命名类型会夸大 churn」列为已知弱点。
dowsing 比对每个版本的 AST,识别出语义未变的修改。实测数据:hotspot 前 20 个文件的 568 个版本中,55 个(9.7%)是虚假 churn,最严重的文件有 42% 是水分。
4. 它公开每个分数的推导过程
CodeScene 的 Code Health 不公开公式、阈值与权重。你无法复现、无法验证、也无法调整。
dowsing 全部公开。
health = 10 − Σ(weight × severity)并且会显示哪个坏味道扣了几分,精确到函数名和行号。也就是说:你可以手工复算。
它还会同时打印只用代码行数得出的分数。如果两者接近,那个 health 分数不过是文件长度换了身衣服——而你应该能自己看出这一点。
5. 它测量修改是否奏效
排名是一个预测。改动落地之后,dowsing diff <base> 会在两个版本上分别读取每个变更文件,报告复杂度实际移动了多少。
📉 复杂度实测(base → head):
cognitive -41 / cyclomatic -22 / LoC -111于是「这是回报最高的修复」就成了一个实测值,而不是预测。dowsing 自己不改代码——那是代理的工作,而这个测量与谁改的无关。
dowsing 不适合什么
- 多语言代码库。 它全押在 TS/TSX 上。要广度,请用 CodeScene。
- 没有 git 历史、或历史很浅的仓库。 改动频率算不出来,工具一半的功能就不工作。在 CI 中,
fetch-depth: 0是必需的。 - 评价个人。 bus factor 与 ownership 是面向团队的风险指标。不要拿它当绩效考核。Tornhill 也给出同样的警告。