版本管理体系
版本管理这事儿,说简单也简单,说复杂能让你头疼好几天。我见过不少团队,刚开始随便写个版本号,等 SDK 一发布,用户就开始骂娘——为什么新版本把我的旧接口删了?为什么这个版本不能降级?归根结底,就是没把版本管理当回事。
今天咱就聊聊怎么搭一套靠谱的版本管理体系。核心就三块:语义化版本(SemVer)、API 兼容性定义、以及 SDK 版本策略。
语义化版本(SemVer)
SemVer 的规则其实就一句话:主版本号.次版本号.修订号(MAJOR.MINOR.PATCH)。但很多人只记住了格式,没理解背后的含义。
- 主版本号(MAJOR):当你做了不兼容的 API 修改。说白了,就是用户得改代码才能用新版本。
- 次版本号(MINOR):向下兼容的新功能。用户升级后,旧代码还能跑,但能体验新东西。
- 修订号(PATCH):向下兼容的问题修复。仅改 bug,不增加任何新能力。
我个人习惯在每次发布前,先问自己三个问题:
(1)这次有没有破坏用户现有代码?
(2)有没有新增公开 API?
(3)是不是只修了 bug?
然后对号入座。
我曾经接手过一个 SDK,版本号从 1.23.0 直接跳到 2.0.0,结果发现只是改了一个内部日志级别……这就是典型的滥用主版本号。用户一看 2.0.0,以为天塌了,其实啥也没变。
API 兼容性定义
先搞清楚三个概念,这是版本管理的基石。
| 类型 | 定义 | 例子 |
|---|---|---|
| 向后兼容 | 新版本 SDK 能完全替换旧版本,用户代码无需修改。 | 旧版 getUser(id) 返回一个对象,新版返回相同结构的对象+新增字段。 |
| 向前兼容 | 旧版本 SDK 能处理新版本客户端发来的数据(通常指数据格式容错)。 | 新版客户端增加了一个可选字段,旧版服务端能忽略该字段继续正常工作。 |
| 中断性变化 | 升级后用户代码必须修改,否则编译或运行时出错。 | 删除方法 getUser(id),改为 fetchUser(id);改变方法签名;移除旧枚举值。 |
你想想看,在 SDK 世界里,向后兼容 是最重要的。用户升级你的库,最怕的就是“啊,我的代码编译不通过了”。所以每次改动 API 时,我都会问:这个变化能被旧调用者无感接受吗?
向前兼容则更多体现在通信协议或序列化格式上。我记得有一次给 Secrets SDK 加了一个 metadata 字段,后端直接忽略旧客户端不传这个字段的情况——这就是向前兼容。但如果你把旧字段改名,那就不兼容了。
SDK 版本策略
有了 SemVer 和兼容性定义,还得有一套实际的版本策略。我总结了几条硬规矩:
- 每次发布都必须更新版本号,哪怕只是改了 readme。不要攒了一堆改动才发一个版本,否则用户根本不知道哪个版本包含了哪个修复。
- 主版本号升级时,必须提供迁移指南。我曾经犯过这个错——直接发了 2.0.0 的 jar 包,用户一脸懵逼:我该怎么从 1.x 迁移?后来不得不连夜写文档。
- 次版本号内不要引入任何不兼容变化。如果实在不小心引入了,立刻 rollback,或者发一个紧急主版本。
- 修订号只能修 bug,不新增 API、不改签名。哪怕修 bug 导致行为变化,但只要输入输出一致,就属于 PATCH。但如果修 bug 导致原本能运行的代码报错,那就是不兼容,得升主版本。
另外,对于 Pre-release 版本(比如 2.0.0-alpha.1, 2.0.0-beta.2),我建议严格限制使用场景。只在内部测试或给早期尝鲜者用,且必须标注“不保证兼容性”。用户一旦用了 Pre-release,就要自己承担风险。
我还会用一张决策流程图来帮团队快速决定版本号。下面这张图就是咱们课程的核心——版本号决策流程。
这张图我贴在团队白板上好多年了。每次有人问“这个改动该升什么版本”,我就让他对着图走一遍。基本不出错。
最后说一个实用技巧:版本号不要跳着升级。比如从 1.2.3 直接跳到 2.0.0,中间得说明为什么跳过 1.3.x 和 1.4.x。用户会怀疑你是不是偷偷藏了不兼容变化。我一般会在 CHANGELOG 里清晰列出每个版本的变化,哪怕只是改了个 typo。
• 用 SemVer 定规矩
• 用兼容性定义判断变化性质
• 用版本策略落地执行
三者缺一不可。