3. 语义化版本核心:Major.Minor.Patch 各自承诺什么

说实话,版本号这东西,我见过太多团队把它当成单纯的数字游戏了。

有人觉得 2.0 比 1.0 听起来更厉害,就直接跳版本。有人懒得升级 Patch,就在 Minor 里塞各种修复。这些做法,说白了都是在给自己埋坑。

所以咱们得先搞清楚一件事:Major.Minor.Patch 这三个数字,到底各自在承诺什么?

3.1 Patch:我修了个 Bug,仅此而已

Patch 是最简单,也是大家最该「闭着眼睛升级」的部分。

它的承诺只有一条:只修 Bug,不做任何功能变更。

更具体一点:

  • 修复了崩溃、内存泄漏、空指针异常
  • 改进了文档注释或日志输出
  • 优化了性能,但接口和返回格式完全不变

在我之前的项目里,有一次紧急发了个 Patch 版本修复登录闪退。结果运营同学没看 Release Notes,以为是大版本更新,全量推了渠道包。结果用户完全无感,零投诉。这就是 Patch 该有的样子——不动声色,解决问题。

关键原则: 如果你在 Patch 里加了新参数、改了返回字段名、或者换了默认行为方式,那你违背了承诺。用户因为信任你而自动升级,结果 API 坏掉了——这是对信任的消耗。

3.2 Minor:我加了新东西,但没破坏旧东西

Minor 的承诺是「向后兼容」。说白了就是:我给了你一些新能力,但你现有的所有代码,一行都不用改。

在实际开发中,Minor 版本可以包含:

  • 新增 API、新增方法重载
  • 新增可选的请求参数
  • 扩展枚举值(非破坏性增加)
  • 标记某些接口为 @Deprecated(但不能直接删)

这里有个我踩过的坑。有一次我在 Minor 版本里,给一个接口增加了必填参数。当时觉得「加个参数嘛,用户肯定能理解」。结果线上直接炸了,因为旧版 SDK 根本没传那个参数。我紧急回滚,补发了一个 Patch。

这个教训让我记住了:「新增」和「破坏」之间只有一线之隔。 判断标准很简单——你的改动,会不会让用户的编译过程报错?会,那就得进 Major。

我个人习惯: 每新增一个公共 API,就升 Minor。每次升 Minor 时,我都会跑一遍完整的兼容性测试用例,确保所有旧参数、旧签名仍然能正常工作。

3.3 Major:我要变革,你做好准备

Major 版本号是三个里面权重最高的。它的承诺是:一切都可能变,协议不再保证兼容。

什么时候该升 Major?

  • 删除或重命名现有公共 API
  • 修改方法签名(参数类型、返回类型变了)
  • 修改默认行为(比如之前是同步现在变异步了)
  • 升级底层依赖的 Major 版本
  • 数据序列化格式不兼容

注意:Major 版本不是开发团队拍脑袋定的。我在项目里见过有人觉得「改动挺大」就把版本升到 2.0,结果实际上只改了几个内部私有方法——用户根本不受影响。这种滥用,会让用户对你的版本号失去信任。

版本段 承诺内容 典型示例 用户升级策略
Major 可能破坏现有 API 删除接口、修改签名 必须评估,不能无脑升级
Minor 向后兼容,功能新增 新增接口、增加可选参数 可以安全升级,有时间再测
Patch 内部修复,无 API 变化 修 Bug、优化性能 闭眼升级,几乎无风险

3.4 三个版本段的实际配合

你想想看,这三个版本段不是各自独立的。它们合在一起,才构成了一个完整的变化语言。

举个例子,一个 SDK 从 1.0.0 发展到 2.1.3:

  • 1.0.0 → 1.0.1:修了个并发 Bug(Patch)
  • 1.0.1 → 1.1.0:新增了统计分析功能(Minor)
  • 1.1.0 → 1.1.1:修复了一处内存泄漏(Patch)
  • 1.1.1 → 2.0.0:重构了核心引擎,API 不兼容(Major)
  • 2.0.0 → 2.1.0:新增国际化支持(Minor)
  • 2.1.0 → 2.1.3:三个 Patch,修复了若干边缘 case

看到没有?每个数字的变化,用户都能准确判断出变动幅度。这就是语义化版本带来的信任基础。

语义化版本演进示例 1.0.0 初始发布 1.0.1 Patch: Bug修复 1.1.0 Minor: 新功能 2.0.0 Major: 不兼容 2.1.0 Minor: 新能力 版本演进解读: 1.0.0 → 1.0.1:Patch,用户无感升级 1.0.1 → 1.1.0:Minor,用户代码无需改动 1.1.0 → 2.0.0:Major,用户必须评估并修改代码 2.0.0 → 2.1.0:Minor,用户可按需升级
警惕「空壳 Major」: 我曾经见过一个项目,没有任何 API 变更,只是改了底层数据库,就直接把版本从 1.x 跳到 2.0。用户以为会有破坏性变更,结果发现接口完全一样,反而开始怀疑「这个团队是不是不靠谱」。升 Major 可以,但一定要在 Release Notes 里清楚说明,到底改了哪些不兼容的部分。

3.5 如何利用版本号做兼容性决策

现在我们来聊聊实践层面。你拿到一个新版本,如何快速判断能不能升?

  1. 看 Major:如果 Major 变了,直接锁定。不能在没做回归测试的情况下升级。
  2. 看 Minor:Minor 变了但 Major 没变,原则上安全。但我建议至少跑一下核心场景的冒烟测试。
  3. 看 Patch:只有 Patch 变了,我真的会闭眼升。但前提是对方严格遵守了语义化版本规范。

反过来,如果你是 SDK 的发布者,也需要靠版本号来降低用户的决策成本。一个清晰的版本号,能让用户在一秒之内就做出「升或不升」的判断。 这就是语义化版本真正的价值。

一条铁律: 我每次发版本前,都会问自己一个问题:「如果我是一个外部用户,看到这个版本号,能不能准确知道该怎么升级?」答案如果是否定的,那说明版本号定错了。