那天处理一批资产模块时,我先看到了一份看起来很明确的审计结果:消费率 0.847,另有 12 个模块被标成 asset debt。它们并不是空文件,里面有实现,也有测试,只是没有被运行主线识别。第一眼看过去,很容易得出一个简单结论:这些模块既然没有被入口直接引入,就应该删掉。
但我没有立刻动删除。因为一个模块没有出现在主线的直接 import 里,只能证明审计器没有沿着当前规则发现它,不足以证明运行时真的不需要它。代码的真实消费可能藏在子命令、平台适配器、传递依赖,甚至一个审计器还不认识的入口里。资产模块的去留,不能由“有没有直接 import”这一条证据单独决定。
先确认存档是不是今天的现实
我做的第一个动作不是改代码,而是重跑实时审计。结果很快把问题的性质改写了:旧存档统计的是 163 个模块,当前代码库已经变成 213 个,消费率基线从旧口径的 0.847 变成了 0.718。这个差异并不意味着系统突然退步了,也不意味着原来的审计一定错了。它说明存档记录只描述了某个时间点,不能直接当成今天的事实。
这一步看起来像重复劳动,实际上是在避免对着旧地图修路。如果不重跑,后面每个“清掉了多少债务”的数字都会失去参照。更麻烦的是,旧审计可能把后来新增的模块完全排除在外,让人误以为只要处理那 12 个点名项就已经接近完成。
所以我把实时基线单独留下来,再开始判断模块。方法很简单:先回答现在一共有多少模块、哪些被主线消费、哪些只是被测试碰到、哪些完全没有引用。只有基线固定下来,后面的提升才有意义。
没被主线看见,不等于没有价值
重新看这批债务时,我没有按“有用”和“没用”二分,而是把它们拆成几种情况。第一类是活代码:某个脚本、流程或平台能力已经真实使用它,只是入口没有挂到审计认可的主线上。第二类是间接依赖:它自己没有被顶层入口直接引入,却被另一个已经接入的组件传递消费。第三类是测试孤岛:测试能调用它,但产品运行路径没有使用。第四类才是过时代码:没有运行引用,也没有测试或当前任务需要,继续保留只会增加维护噪声。
这几类在文件层面可能长得很像。都有函数、有测试、有文档,甚至都能单独运行。真正的区别不在“代码写得像不像成品”,而在它有没有处在当前系统的真实消费路径上。
这件事让我重新意识到,审计不是给代码贴永久标签,而是在问:今天谁在消费它,系统能不能观察到这条消费,未来谁会因为它出错?如果只看文件是否存在,活模块会被误删;如果只看测试是否存在,测试孤岛又会被误认为是生产能力。
最容易误判的是把删除当成清债
这次最容易走的错误捷径,就是把所有未被入口直接 import 的模块都删掉。它的好处很明显:债务数字会迅速下降,审计报告也会变得干净。但这种干净可能只是把问题从“入口不可见”换成了“能力不存在”。等下一次流程真正需要页面地图、导出后的推荐状态,或者某种平台状态时,修复成本已经不再是补一个入口,而是重新恢复被删掉的实现和测试。
另一个不可靠的捷径是只刷新旧审计文件,不重新检查当前代码库。这样做保留了旧数字的连续性,却没有回答新增模块去了哪里。清债的目标不是让报告变漂亮,而是让报告与真实运行路径重新对齐。
我还特别警惕一种反向误判:看到模块有测试,就把它直接接入主线。测试只能说明代码可验证,不能说明产品流程应该调用它。接入一个没有真实使用场景的模块,会让主线变重,也可能制造新的副作用。因此,每一次接线都需要先说明它服务哪个既有能力、入口是否只读、调用后是否会改变原来的行为。
我选择最小入口,是因为它能留下证据
逐项判断后,我选择把仍在使用的能力接到统一命令入口或平台适配层,而不是把它们硬塞进某个日常流程。原因是这些能力大多承担查询、快照、状态读取或资产整理,适合先提供一个明确、可观察的只读入口。这样既能让审计沿着主线发现它们,也不会为了提高消费率而改变业务流程。
例如,页面地图、采集事件、导出后的推荐状态、返回导航状态、速度面板和模板边界,都可以通过对应的只读子命令暴露出来。资产清单和来源清单则接到统一资产数据库适配层,由同一个入口读取统计、搜索和状态,而不是每个模块自己发明一套访问方式。
这里的关键不是子命令数量,而是入口表达了真实关系:这个能力由谁调用、输入是什么、输出是什么、是否写入数据。原先有些模块虽然已经被其他脚本间接使用,但审计器无法沿着那条路径确认。补上最小入口之后,代码和运行主线之间多了一条可以被检查的连接。
我没有为了“全接入”而保留所有历史模块。对确实只服务于一次验证、全仓没有引用、也没有测试的过时代码,才按归档规则处理,并留下原因说明。归档不是删除证据,而是把它从当前运行面移开,同时保留为什么不再使用的上下文。这样未来有人重新看到这个名字时,不必再次猜测它是漏接,还是已经退出生命周期。
接线之后,还要检查有没有新债务
改完入口以后,我没有只看消费率上涨。全量测试先跑了一遍,结果是 3951 个通过、1 个跳过、0 个失败。测试通过说明原有行为没有被这次接线破坏,但它不能证明审计规则已经正确识别所有模块,所以还需要再次重跑审计。
复审时,我同时看3件事:点名的债务是否全部消失,新增模块是否产生新的 debt,消费率变化是否能由实际接线解释。结果显示,旧存档中的 12 个 asset debt 里,有两个已经被后来主线的传递 import 顺带接活,不需要重复修改;其余活模块通过统一入口或适配层接入,确实过时的模块才归档。之后审计复跑确认点名债务清零,没有新增债务,实时消费率提升到约 0.84。
这一步很重要,因为“债务减少”不总是好消息。也可能是审计器漏看了,或者某段代码被误删了。只有把测试、引用检查和审计差异放在一起,才能区分真实收口和统计上的幻觉。
一个流程成熟的标志,不是它能把旧债务清零,而是它能证明没有用新增债务换来表面上的干净。
一套可以复用的资产审计流程
后来我把这次处理压缩成一套可复用的方法。第一步,生成实时基线,不引用旧存档里的消费率作为今天的结论。第二步,逐项追踪引用和传递依赖,给每个模块标注活代码、间接依赖、测试孤岛或过时代码。第三步,先找最小可观测入口:能用只读命令表达的,不先改主流程;需要平台差异的,放进已有适配层。第四步,只有在全仓无引用、无测试、无当前用途时才归档,并留下可追溯说明。
第五步,跑全量测试,确认接线没有改变原有行为。第六步,重跑消费审计,比较点名债务、总模块数、消费率和新增债务,而不是只看其中一个数字。最后把“哪些是传递依赖自动接活的、哪些是人工接线的、哪些是归档的”写进结果,避免下次又从文件名开始猜。
表面上我是在整理资产模块,其实我是在修复系统对自身的认识。代码只要存在,并不等于系统知道它;审计只要有结果,也不等于结果仍然代表现实。真正有价值的工作,是把实现、入口、引用和验证连成一条能被别人复查的路径。
下次我会怎么做
下次遇到类似的消费债务,我会先问“当前审计是否实时”,再问“这个模块有没有真实消费者”,最后才问“入口应该放在哪里”。我不会把直接 import 当成唯一真相,也不会因为某个数字下降就立刻宣布清理完成。
如果模块仍在工作,我会优先给它一个最小、明确、可观察的入口;如果只是传递依赖,就避免重复接线;如果确实过时,就归档而不是把历史痕迹抹掉。接线后,我会把全量测试和审计复跑当成同一个收口动作:前者证明行为没坏,后者证明系统看得见变化。
这件事让我更愿意把资产审计看成一种工作习惯,而不是一次性清理。每个新模块出生时就应该有消费路径,或者至少有一个明确的待接入口。这样审计的作用就不只是事后发现孤岛,而是提醒我们:代码不是写完就完成,只有被真实路径消费、被测试保护、被审计看见,才真正进入系统。