第四章:旧SDK代码摸底——把老家伙们一个个揪出来

说实话,迁移项目刚启动那会儿,我最怕的不是改代码,而是连旧SDK用在哪都不知道。你想想看,一个跑了四五年的系统,几十个模块,上百个文件——靠肉眼去翻?不现实。

所以咱们的第一步,就是全面摸底。把旧SDK的足迹全部画出来。我习惯把这步叫做「代码考古」——不是贬义,是真的像考古一样,得一层层挖。

4.1 摸底的三条主线

旧SDK的引用一般分三类。我个人经验里,至少九成的问题都藏在这三个地方:

类型 特征 典型例子
注解 类、方法、参数上的标记 @OldAuditLog, @Legacy("v1")
API调用 直接new旧SDK的类、调静态方法 AuditSDK.submit(…), new AuditClient()
配置 XML、properties、YAML里的SDK参数 audit.sdk.endpoint, sdk-config.xml

我曾经接手过一个项目,团队说「我们没怎么用旧SDK」。结果我一扫配置,发现老SDK的Endpoint还在application.yml里躺着——只是没人记得删。嗯,这就是摸底的价值。

4.2 搜索策略:从粗到细

我不会一上来就写脚本。先做两件简单的事,能省很多时间。

  • 全局文本搜索:用IDE或grep搜旧SDK的包名,比如com.old.audit。这一步能覆盖九成以上的引用。
  • 扫描导入语句:所有import com.old.audit.*的地方,一个都跑不掉。

我习惯这么搜:

# Linux/Mac 命令行搜jar引用
grep -r "com\.old\.audit" src/ --include="*.java" --include="*.xml" --include="*.properties"

# 如果想统计出现次数
grep -rc "com\.old\.audit" src/ --include="*.java" | grep -v ":0$"
小技巧:先搜包名前缀,再逐步缩小。别直接搜全类名——旧SDK可能有子包你没注意到。

4.3 处理注解:别漏了反射场景

注解这块比较隐蔽。旧SDK可能在运行时用反射读取注解,这种代码在IDE里很难直接发现。我遇到过一回,一个自定义注解@LegacyRule根本没在任何地方显式import,但通过AOP切面拦截了所有带它的方法。

怎么查?

  1. 搜注解名称:比如@OldAuditLog,搜所有.java和.kt文件。
  2. 搜注解的元数据:如果旧SDK定义了一个@interface,搜@interface OldAuditLog就能找到定义位置。
  3. 检查AOP配置:Spring的@Aspect或@Pointcut里,可能用@annotation(com.old.audit.OldAuditLog)这种方式引用。
注意:别忽略@Inherited注解——父类上有旧SDK的注解,子类也会继承。我之前排查时漏了这一层,导致子类中的逻辑没被迁移。

4.4 API调用:最难缠的「硬编码」

直接调用的API比较好查,但有几类容易漏掉:

  • 工厂模式:通过AuditFactory.create()拿到的对象,实际返回的是旧SDK类。
  • 静态导入:import static com.old.audit.AuditUtils.submit;,然后代码里直接写submit(...),看着像本地方法。
  • 反射调用:Class.forName("com.old.audit.AuditClient"),这种最恶心,IDE搜不到字符串里的包名。

对反射调用的场景,我的建议是:运行时挂个切面或字节码工具,把所有Class.forName和Method.invoke的调用日志打出来。我当年用Arthas的watch命令抓过一次,十几分钟就定位了三个隐藏点。

4.5 配置扫描:容易被遗忘的角落

配置文件的格式五花八门。我的经验是列一个清单,然后逐个目录扫:

配置文件 典型路径 旧SDK参数示例
YAML src/main/resources/application.yml audit.old.endpoint: http://local:8080
Properties src/main/resources/*.properties audit.sdk.version=1.2.3
XML src/main/resources/spring/*.xml <bean class="com.old.audit.AuditService"/>
JSON 配置文件、环境变量注入 "audit_provider": "old_sdk"

我个人还会检查Dockerfile和docker-compose.yml,有时候旧SDK的镜像环境变量写在里面,项目代码里反而没有。

4.6 自动化扫描脚本(推荐)

手动搜一遍后,最好写个脚本做二次确认。我给你个Python脚本的骨架,在项目中跑一遍,输出汇总报告:

import os
import re

OLD_SDK_PACKAGES = ["com.old.audit", "com.legacy.audit"]
SCAN_EXTENSIONS = [".java", ".xml", ".properties", ".yml", ".yaml", ".json"]

found = []

for root, dirs, files in os.walk("src"):
    for file in files:
        if any(file.endswith(ext) for ext in SCAN_EXTENSIONS):
            path = os.path.join(root, file)
            with open(path, "r", errors="ignore") as f:
                for line in f:
                    for pkg in OLD_SDK_PACKAGES:
                        if pkg in line:
                            found.append(f"{path}: {line.strip()}")
                            break

print(f"共发现 {len(found)} 处引用")
for item in found:
    print(item)
产出物:摸底结束后,你应该得到一张清单——旧SDK引用的文件列表、具体行号、引用类型(注解/API/配置)。这份清单就是后续迁移的「作战地图」。

4.7 摸底流程全景图

下面这张图概括了上面讲的所有步骤。我建议你把它打印出来,贴在工位旁边,做摸底时照着走一遍。

旧SDK代码摸底流程 旧SDK代码摸底流程 开始摸底 三条主线依次排查:注解 · API调用 · 配置 注解 搜索@标注 + 反射场景 API调用 全局搜索包名 + 工厂/反射/静态导入 配置 扫描YAML/XML/Properties/JSON/Docker 编写自动化扫描脚本 输出:引用清单 清单包含文件路径、行号、引用类型——是后续迁移的作战地图

4.8 避坑指南:来自实战的教训

千万别只看源代码目录:我曾经发现一个Spring Boot项目的lib/目录下残存着旧SDK的jar包,但代码里压根没import——结果运行时通过SPI自动加载了。所以摸底一定要包含lib/、WEB-INF/lib/、build/这些目录。

还有一次,我排查一个微服务项目,发现在gradle.properties和pom.xml里引用了旧SDK的版本号,虽然代码里没直接调用,但依赖传递带了一堆旧类进来。嗯,所以构建文件也要纳入摸底范围。

记录时要分类:把发现的问题按「直接引用」「间接引用」「配置残留」「反射调用」分四类。后期迁移时可以按优先级处理,反射调用和间接引用通常最慢。

摸底不是一次性工作。我建议你做完一次全面扫描后,在CI流水线里加一个检测步骤——比如用grep或自定义插件,每次提交都检查是否新增了旧SDK的引用。这样后续迁移过程中,不会有人不小心又引入老代码。