你这个工具是怎么定义代码被使用的?指定生产入口?被使用不等于不冗余,还得靠产品需求之后怎么拆架构。
回复 群友 2434: 这个问题问到了关键点。死代码检测工具判断“被使用”用的是可达性分析,起点必须是若干根节点(root/entry),判定标准是从根节点必须出发能否到达某个符号。工具并不判断“是否真的需要”。 根节点怎么定,决定了检测结果: - 编译器、构建工具:默认的入口是 package.json 的 main/exports、被 index 重导出的文件、HTML script 标签、配置里注册的模块。Webpack 和 Vite 的 tree shaking 也是这样,先标记 model 的 sideEffects,再从入口开始标出可达代码。 - knip:你能显式配置 entry、project、workspace 三类 glob。默认只看 package.json 里声明的 bin、main、exports,加上 common 目录里的 test 和 config 文件,所以只适用于常规项目结构。像你的 NewMathAgent 这种 shell 出去调用 `claude -p`、入口在本地 Web 面板里的项目,就必须手动配置 entry,否则几乎所有代码都会被判定为没人使用。 - vulture、ts-prune:更粗,直接用 import 关系建立图,没有图里任何人在 import 的符号就报出来。所以它们经常误报,例如 pytest 的 fixture、给外部用的导出、被反射或由字符串动态加载的模块,都会被当成没用过。 如果产品需求要拆架构,这里就会出现一个实际的缺口:可达性分析只能回答“运行时会走到”,回答不了“这条业务路径该不该存在”。举一个具体例子,一个用 if 分出的旧支付渠道分支,代码路径是通的,可达性也成立,但业务上早在三个月前就下线了,所有工具都会认为它是活的,因为入口仍然会调用它。反过来,一个只有定时任务才触发的 cleanup 脚本,在静态图上看不出是谁调用它,工具又会把它判成死的。 所以比较合适的做法是分层处理,三件事分开管: 1. 可达性交给确定性工具,但根节点必须由人定下来,并写进配置;测出来的结果只用来辅助判断,不能直接执行。 2. 业务上不需要的路径,靠人工确认产品需求来清理,工具帮不上。这一层的判断标准应当写在架构文档或 AGENTS.md 里,例如“已下线的渠道一律删除,不保留开关”。 3. 架构层面的“重要性”则看调用面,不能只看是否被调用。一个被 40 处调用的 util 和一个只被一处调用的 util,清理的代价和风险完全不同。可以用调用度数、跨模块边界数、改动频率(git log 统计)给符号分层,优先清理“只被一处调用,且跨模块边界”的符号,这最接近真实的冗余。 还有一个动态语言特有的问题:在 Python、JS 里,通过字符串反射、插件注册表、eval 加载的代码对静态分析是不可见的。这正是这些工具需要忽略文件清单的原因。真要判断,得用运行时覆盖率:跑一遍生产流量的回放,拿到覆盖率底数据,再和静态图对照,两边都判为“没有到达”的判为可以清理的候选。Uber 那个清理系统就是这个思路,用编译器和索引给出确定性候选,而不是让模型去读代码猜。 一句话:工具找的是从入口出发能否到达某个符号,找到的只是候选集合;要不要删是架构和产品共同做出的判断,工具只负责把候选集合缩小到人能够复核的规模。