4. 适配改造前的准备工作:基线版本确认,依赖树分析,文档收集,差异工具准备

说实话,很多团队在 SDK 版本升级时栽跟头,不是代码改不动,而是准备工作没做透。我个人习惯,把升级前的准备工作拆成四块:基线版本确认、依赖树分析、文档收集、差异工具准备。缺一块,后面就得踩坑。

核心原则:升级前投入 1 小时做准备工作,能省下后期 3 天的排查时间。别急着改代码,先摸清楚现状。

4.1 基线版本确认

基线版本,就是你当前项目里真实在跑的版本。你以为的版本,和实际编译的版本,往往是两回事。有一次我接手一个项目,依赖的五六个 SDK 版本号全写在 README 里,但没人知道当前编译用的是哪个。一查,发现开发机和 CI 机的版本都不一致,联调出问题先互怼了一个下午。

怎么确认基线?我推荐三步走:

  1. 锁定构建文件:去 build.gradle、pom.xml 或 package.json 里,找到目标 SDK 的版本声明。不要看 README,不要听别人说,只看构建文件。
  2. 跑一次全量编译:确保本地环境能 clean build 成功。编译过程中,构建工具会把最终解析的版本打印出来,仔细看。
  3. 校验产物:从编译产物(jar/aar/war)里提取版本号。比如解压 jar 包看 MANIFEST.MF,或者用 java -jar 跑一下看启动日志。

我曾经吃过一次大亏。项目里同时引了 Guava 28.0 和 Guava 30.0,但 pom.xml 里只写了一个版本号。实际运行的是 28.0,因为另一个库的依赖规则把它拉下来了。结果生产上报警,查了一整天才发现是隐式依赖覆盖了显示声明。后来我在每个项目里都加上 dependency:tree 校验,再也不信表面版本号了。

在三方库的发布页上,经常会看到语义化版本号,比如 2.1.0、3.0.0-RC1。别只看大版本,小版本和预发布版本也可能有破坏性变更。我建议用 gradle 或者 maven 的 locked 版本管理,直接把版本号锁死在构建文件里。锁定后配合 gradle dependencies 或者 mvn dependency:tree,跑一遍确认。这个动作我建议集成到 CI 流水线里,每次构建前自动做依赖校验。

4.2 依赖树分析

依赖树分析,说白了就是搞清楚你的项目里到底引了多少库,它们之间怎么嵌套的。你想想看,一个 SDK 往往不是孤立的,它自己又依赖了别的库。你不分析依赖树,就不知道升级会波及哪些间接依赖。

依赖冲突,说白了就是同一个库引了不同版本。比如 A 库依赖 Guava 28.0,B 库依赖 Guava 30.0,JVM 只能选一个。你想想看,选 28 还是 30?编译可能能过,但运行时,A 库调了 28 才有的 API,结果 B 库的 30 删了它,这就崩了。

我一般用这两个工具做依赖树分析:

构建工具 命令 输出要点
Maven mvn dependency:tree 列出所有直接与传递依赖,标记冲突版本
Gradle gradle dependencies --configuration compileClasspath 分 configuration 展示依赖树,标注选中的版本
Gradle gradle dependencyInsight --dependency guava 针对单个库,分析为什么选了当前版本

跑完依赖树,把输出的文本保存下来,这就是你的“依赖快照”。升级后跑一次新的依赖树,对比差异,就能精确知道哪些库的版本变了。我见过一个案例,团队升级 Netty,只改了 pom 里的版本号,没跑依赖树。结果底层一个老版本 netty-handler 还被别的库间接引着,生产上跑了三个月,突然报 SSL 握手失败。一查,是俩版本序列化协议不兼容。

避坑指南:依赖树分析不是一次性的。每次发版前我都跑一遍,哪怕只改了一个版本号。把依赖树快照放进 Git 仓库,对比 diff 就能知道你改了啥。

4.3 文档收集

文档收集这件事情,听起来像样子活,但做扎实了能救命。我记得有一次,一个 SDK 的 2.0 版升到了 2.5,我第一件事就是去翻变更日志(CHANGELOG)。发现一行小字写着“Deprecated method send()”,赶紧通知前端改了调用。这在线上跑着,发版后一调旧 API 就抛 NoSuchMethodError,那可就变成事故了。

需要收集的文档清单:

  • 发行说明 / 变更日志:看有没有破坏性变更、弃用 API、配置文件格式变化
  • API 参考手册:对比新旧版本的 public API 签名
  • 迁移指南:官方出的迁移文档,通常附带最佳实践和代码示例
  • 已知问题 / 兼容性矩阵:看新版本是否支持你的 JDK、框架、操作系统
  • 示例代码:新版本怎么用,对比旧版本的用法差异

我会把这些文档集中到一个文件夹里,按版本号命名,比如 docs-v2.0-to-v2.5/。不嫌麻烦的,还可以用脚本把关键变更摘出来,形成一份自己的“内部迁移笔记”。

实操建议:别只依赖 PDF 和网页。很多 SDK 的文档只在 GitHub 的 Releases 页面里。直接看 tag 对比,用 diff 工具比对 CHANGELOG.md 的两个版本,比肉眼扫读快得多。

4.4 差异工具准备

差异工具准备,这一步很多人会跳过。其实它是最省时间的环节。用手工对比两个版本的 API 变化?那不得累死。工欲善其事,必先利其器。我常用的工具链有这几类:

工具类型 名称 用途
API 差异分析 Revapi / japicmp 对比两个 jar 包的 public API 变化
依赖差异分析 diffoscope 对比构建产物级别的差异
字节码对比 classpy / jd-gui + diff 看字节码层面的方法增减
文本 Diff meld / Beyond Compare 对比配置文件、CHANGELOG、测试用例

我个人习惯用 Revapi。它能直接对比两个 jar 包的 public API 变化。跑一遍,API 增删改就一清二楚。数据也能集成进 CI,一旦发现 breaking change 就阻断构建。我记得有一次用 Revapi 扫出来一个库的返回值类型变了,从 List 改成 ImmutableList,光看 CHANGELOG 根本没提。

还有一个工具叫 gradle diff 插件,可以对比两个版本的构建产物差异。它会列出来哪些 class 变了,哪些新增了,哪些删除了。配合依赖树快照一起看,效果翻倍。

重要提醒:差异工具不是你跑一次就完事的。我建议把它做成一个脚本,每次升级前跑一遍,输出差异报告。然后把报告附在升级工单的附件里,这样别人 review 你的 MR 时,也能快速了解变更范围。

最后,把这四块工作的产出汇总成一个“基线资产包”:

  • 基线版本快照(写在文件里的)
  • 依赖树文本(格式化保存的)
  • 文档文件夹(按版本整理的)
  • 差异报告(工具输出的)

有了这些,你再进入写代码阶段,心里就有底了。遇到问题,回头查基线,一查一个准。

SDK 适配改造准备工作流程 基线版本确认 锁定构建文件 · 全量编译 依赖树分析 mvn/gradle dependency tree 文档收集 CHANGELOG · 迁移指南 差异工具准备 Revapi · japicmp · diffoscope 输出基线资产包 快照 + 树 + 文档 + 差异报告 ⚠ 避坑提示 依赖快照纳入 Git 版本号只用构建文件 💡 效率要点 差异报告附 MR 中 集成 CI 自动阻断

嗯,准备工作做到位了,别急着动代码。把基线资产包存好,接下来才能放心地去分析具体要改哪些地方。这四个步骤环环相扣,核心就是一句话:先看清楚再动手。