返回博客

一个 grep 差点删掉正在用的 Spring Bean

Issue 描述写的是"某张表的 Java 代码已是死代码,PHP 侧已接管,删掉"。乍看不复杂:grep 一下表名,看看有没有引用,没有就派删除任务。

结果翻车了——或者说,险些翻车。

现象

Issue 继承了一个前提:某张配置表已不再被 Java 读取,全部逻辑已迁移到 PHP。照这个前提,grep -l <table_name> 在整个 Java 后端树搜了一遍,只返回一个文件——Entity 的定义文件本身。

"只有自己定义自己,没有外部引用"——看起来死透了。

但在真正派出删除 agent 之前,我多做了一步:换 类名 再 grep 一遍。

rg -l "BaseMerchantNumber" --type java --type xml /path/to/backend

输出 11 个文件,包括:

  • MiniOrderServiceImpl.java(第 176 行和第 743 行,活跃调用)
  • IPaymentFactory.java(接口签名)
  • TaxWechatAutoConfiguration.java(Spring @Autowired,两个模块都有)

这不是死代码,这是核心支付链路上的配置源。

根因

SQL 表名和 ORM 实体类名看起来像两件事,但代表同一个 artifact。

Issue 作者用表名做了 grep,只找到 1 个文件,就得出"无引用"的结论。但 Java ORM 代码(MyBatis/JPA)访问这张表完全不用写表名字符串——它用的是实体类名 BaseMerchantNumber、Mapper 接口 BaseMerchantNumberMapper、Spring Bean 名 baseMerchantNumberService。这几个标识符跟 SQL 表名字面上毫无关联,普通 grep 完全搜不到。

更隐蔽的是 TaxWechatAutoConfiguration:它在 @Autowired 注入 Service 接口,但文件里没有出现任何跟表名相关的字符串。如果只搜表名,这个关键连接点永远不会出现在结果里,而它在 Spring 启动时就会被加载。

此外,数据库里这张表有 1 行数据,status=enablemch_id 和 PHP .env 里的配置值相同。行数为 1 不是"空表近似死代码"——恰好说明它是启动时被读取的配置种子行

坑:单关键词 grep 得出「无引用 = 死代码」

SQL 表名和 ORM 实体类名是同一个 artifact 的两张脸。grep 表名只能覆盖写了原生 SQL 的地方;ORM 代码、Spring Bean 注入、接口签名全部用类名而不是表名——用表名 grep 看到"1 个文件",实际上漏掉了所有 ORM 层的引用。

另外,DB 里 1 行数据比 0 行数据更可疑,而不是更安全:0 行可能真的没用过,1 行往往是被读取的启动期配置。

正解

删代码之前,对「这是死代码」的前提做三轴验证,任一轴显示"存活"就停手:

轴 A — DB 侧数据核查

SELECT COUNT(*) FROM <table_name>;
SELECT MAX(updated_at) AS last_write FROM <table_name>;
SELECT * FROM <table_name> ORDER BY id DESC LIMIT 5;

行数为 0 才能继续;有行且近期有写入说明在用;有行但无近期写入需要继续验 B/C 轴。

轴 B — 所有标识符的代码引用

for ID in "table_name" "EntityClass" "IServiceInterface" \
          "MapperClass" "beanName" "getterMethod"; do
  echo "=== $ID ==="
  rg -l "$ID" --type java --type xml --type yml /path/to/backend
done

只有每个标识符的搜索结果都指向它自身的定义文件,才算"可能是死代码"。任何一个出现在 Service / Controller / Config / Test 里,前提即为假。

轴 C — 运行时调用链追踪

从 B 轴找到的引用文件,往上追溯到入口点(HTTP Handler / @Scheduled / @KafkaListener)。只有调用链断掉或入口点已禁用,才真正是死代码。

本例的追踪结果:MiniOrderServiceImpl.ordering()@RestController 小程序侧下单接口 → 线上活跃路由。前提完全错误。

正解:三轴全过才派删除任务

只有三轴都指向"死",才把删除任务派出去,并在任务描述里附上:穷举标识符列表、涉及文件清单、构建验证命令、单次 revert 的回滚计划。

三轴验证总共花了 5 分钟,省去了一次 agent 派发无功而返,以及如果 agent 手脚快一点可能造成的构建中断。

一句话外卖

删代码之前,Issue 的「这是死代码」只是别人的假设——SQL 表名、ORM 类名、Spring Bean 名是同一实体的三张脸,只搜一张就下结论,迟早翻车。