用 Knip 清理 AI Coding 留下的冗余代码

最近用 Knip 清理了两个项目,再让 agent 翻 Git 记录,发现不少冗余来自快速实现和页面重构。聊聊这些旧代码为什么能躲过 lint,以及它们如何影响下一次 AI Coding。

3 分钟阅读

最近,我拿 Knip 跑了两个现有项目,清理掉了不少冗余代码和未使用文件。清完以后,我又让 agent 翻 Git 记录,看看这些东西是怎么留下来的。

结果很熟悉:有些是“快速实现一下这个需求”的遗留,有些是“重构一下这个页面”之后的临时文件。新功能早就用上了,旧代码还在。

我们已经习惯了 agent coding。几句话的小需求,最后变成数十个文件、数千行的 PR。看着功能能跑,就继续下一个需求。至于这些变更里有多少是必要的,多少是临时方案,又有多少已经被后面的实现替代,很容易就略过去了。

这次清理让我比较在意的,就是这些已经没用、却一直留在仓库里的东西。

为什么 lint 没有早发现?

拿 TypeScript 项目来说,严格一点的 lint 可以检查未使用的变量、参数和 import。不过,文件里的引用都成立,不代表这个文件还在被项目使用。

举个简化的例子。原来路由指向旧页面,旧页面引用旧组件,旧组件又调用几个工具函数。重构之后,路由切到了新页面,旧页面却没有删除。

旧页面里的 import 仍然在使用,旧组件里的函数也有调用方。从这些文件内部看,一切正常。但从应用入口看,已经没有任何路径能走到这套旧实现。

常见的未使用变量规则检查不到这里。它们解决的是局部问题,整片代码是否还被项目需要,要沿着文件之间的引用继续找。

如果项目连基本的 lint 都没有,这两类冗余就会一起留下来。

Knip 多看了一层

Knip 官方文档列出的核心能力,是查找 JavaScript 和 TypeScript 项目中未使用的文件、导出和依赖。

它会从入口文件出发,沿着引用关系分析项目,并结合 package.json 和框架、工具插件识别更多入口。纳入检查范围、却无法从入口抵达的文件,就可能被报告为未使用。

放到前面的例子里,即使旧页面、旧组件、工具函数之间还有引用,Knip 也有机会发现:这一整条链已经和应用入口断开了。

以 npm 为例,官方给出的接入命令是:

npm init @knip/config
npm run knip

以上为接入示意,具体环境要求与配置以官方文档为准。

拿到报告之后,就可以让 agent 对照引用关系清理。Knip 本身也支持自动修复--fix 能处理部分未使用导出和依赖,删除文件还需要加上 --allow-remove-files

这里有个边界:Knip 得先知道项目有哪些入口。运行时拼接的加载路径、框架自动发现的文件,都可能需要补充配置;对外发布的库,也不能把仓库内无人调用的公共 API 直接删掉。这些情况在官方问题处理文档里有说明。

翻 Git,比看删除了多少行更有意思

我让 agent 继续追查来源,是想知道:这些代码原本就是多余的,还是后来才失去作用?

在这两个项目里,能追溯到不少快速实现需求、重构页面的过程。临时实现留下了,替换后的文件留下了。单看当时的需求,事情已经做完;隔一段时间再看仓库,里面却堆着好几轮改动的遗留。

旧代码有正常的命名、类型和目录结构,搜索时照样会出现。下一次 agent 修改相关功能,可能把它当成当前实现的参考,沿着已经过时的做法继续写。仓库越乱,光是分清哪套代码还有效,就越费劲。

这和我之前写过的文档漂移问题有点像:过期的内容还在,后续的判断就可能建立在错误的前提上。

Knip 当然判断不了业务架构是否合理。所有代码都有人调用,项目照样可以是一座屎山。架构仍然需要有经验的工程师兜底。

但那些已经没人用的文件、导出和依赖,是可以先清掉的。这次在两个项目里跑下来的效果,让我觉得 Knip 值得推荐给同样大量使用 AI Coding 的人。