第 4 章 API 变更分析:新增、修改、废弃、删除
SDK 版本升级,说白了就是看它的 API 变了什么。
我刚接触兼容性测试时,拿到新版 SDK 就一股脑跑用例。后来发现,很多坑都是 API 悄悄改出来的。所以我现在的习惯是——先做个 API 变更分析,把四种变化摸清楚再去动测试用例。
这一章,咱们就聊聊怎么分析 API 的四种变更:新增、修改、废弃、删除。
4.1 新增 API
新加了个方法、新加了个类、新加了个常量。看起来人畜无害,对吧?
事实上,新增 API 通常不会破坏现有功能。但它可能带来两个隐患:
- 符号冲突:你项目里自己定义的同名函数,跟新 API 撞上了。
- 依赖冲突:新 API 内部引用了另一个第三方库,导致你的项目里同样的库版本不一致。
4.2 修改 API
这个最头疼。改了参数类型、改了返回值、改了方法签名,甚至改了内部行为逻辑。
举个例子:原来 getUser(int id) 改成 getUser(String id)。编译直接报错,倒还好。就怕那种签名不变、行为变了的哑修改。
// 旧版本:返回值可能为 null
public User findUser(String name);
// 新版本:保证不返回 null,找不到时抛异常
public User findUser(String name);
你看,编译没问题,但原来依赖 null 判断的代码全崩了。我就在项目里遇到过这种坑——线上日志突然出现一堆 NullPointerException,查了两天才发现是 SDK 悄悄改了内部约定。
4.3 废弃 API
打上了 @Deprecated 注解,或者标记为 deprecated。意味着将来会删除。
废弃是个缓冲期。我习惯的做法是:
- 检查自己项目里有没有用到这些废弃 API。
- 有的话,立即替换成新推荐的替代 API。
- 替换后做全量回归——因为替代 API 的行为可能不完全一样。
你可能会问:“既然还能用,能不能先不改?”
嗯,我建议别拖。我以前在一个项目里拖了三个版本,结果某次升级直接把废弃 API 删了,编译失败,紧急加班改了一个通宵。所以,见到废弃就立改。
4.4 删除 API
删了就是真的没了。编译直接报错,这是最直观的破坏。
需要做的是:
- 确认在你的项目中是否引用了该 API。
- 如果有,查它的替代方案是什么。
- 如果没有引用,也要小心——你的间接依赖(比如第三方库)是否引用了它。
举个例子:新版 SDK 删除了 Utils.encode(),你的项目没直接调它,但你的网络库 NetLib 调了。一升级,NetLib 加载失败,整个功能瘫痪。
jdeps 或 gradle dependencies)把全项目的 API 引用扫描一遍,再对照删减清单。
4.5 如何快速找出这些变更
手动一个一个翻 API 文档不现实。我常用的方法:
| 方法 | 适用场景 | 命令或工具 |
|---|---|---|
| 源码 diff | 有 SDK 源码时 | diff -r old_src/ new_src/ |
| JAR 对比工具 | 只有二进制包时 | javap -public -classpath new.jar + old.jar |
| API 变更检测专用工具 | Java 生态 | japicmp, clirr, revapi |
| 官方发布说明 | 所有 SDK | CHANGELOG.md / Release Notes |
说说我的习惯:小版本升级,我直接用 japicmp 做二进制对比,几秒钟出一份变更报告。大版本升级,我会先读官方 Release Notes,再用 diff 扫一遍。
4.6 变更分析与兼容性决策矩阵
拿到变更清单后,怎么判断要不要做兼容性适配?我列了一张决策表:
| 变更类型 | 影响范围 | 推荐动作 | 测试优先级 |
|---|---|---|---|
| 新增 | 仅新增功能,不影响旧代码 | 可选,建议至少跑主流程 | 低 |
| 修改(签名不变) | 运行时行为可能变化 | 必须逐项验证 | 高 |
| 修改(签名变化) | 编译失败 | 必须适配代码 | 高 |
| 废弃 | 暂时不影响,但需替换 | 必须替换,替换后回归 | 中 |
| 删除 | 直接编译/运行失败 | 必须找到替代方案并适配 | 最高 |