通过语义化版本规范你的版本控制
在软件管理的领域里存在着被称作“依赖地狱”的死亡之谷,系统规模越大,加入的包越多,你就越有可能在未来的某一天发现自己已深陷绝望之中。
在依赖高的系统中发布新版本包可能很快会成为噩梦。如果依赖关系过高,可能面临版本控制被锁死的风险(必须对每一个依赖包改版才能完成某次升级)。而如果依赖关系过于松散,又将无法避免版本的混乱(假设兼容于未来的多个版本已超出了合理数量)。当你项目的进展因为版本依赖被锁死或版本混乱变得不够简便和可靠,就意味着你正处于依赖地狱之中。
简述
语义化版本(Semantic Versioning,简称 SemVer)是一种版本控制规范,旨在通过明确的版本号规则来管理软件包的版本。它通常由三部分组成:主版本号、次版本号和修订号,格式为 MAJOR.MINOR.PATCH
。
- 主版本号(MAJOR):当你做了不兼容的 API 修改时,增加主版本号。
- 次版本号(MINOR):当你做了向下兼容的功能性新增时,增加次版本号。
- 修订号(PATCH):当你做了向下兼容的问题修正时,增加修订号。
- 先行版本号和编译信息等额外信息可以加在三者之后,作为版本定义延伸。
遵循语义化版本规范可以帮助开发者更好地理解软件包的变化,避免版本冲突,并提高软件的可维护性。
详细说说
主要规范
- 使用语义化版本作为版本控制的软件都必须定义公共 API。
- 标准的版本号必须是
X.Y.Z
格式,X、Y、Z 均为非负整数,且不能以零开头,每个元素都必须以数值递增。X
是主版本号(MAJOR)Y
是次版本号(MINOR)Z
是修订号(PATCH)
- 标记了版本的软件发行之后,版本号不能被修改,该版软件也不能被修改。任何新的修改都必须发布为新的版本。
- 主版本号为
0
的软件定义为开发阶段(Dev),这样的版本不应该被视为稳定版。 - 版本号递增必须遵循以下规则:
MAJOR
:进行了不能向下兼容的更改必须递增,如 API 的重大变更或删除。MINOR
:只进行了向下兼容的功能性新增,如添加新功能或改进现有功能。PATCH
:只进行了向下兼容的问题修正,如修复 bug 或性能优化。
先行版本号和编译信息
在 X.Y.Z 的标准版本号后,可以用一个连接符连接一串句点分隔的先行版本号,也可以再(或直接增加在标准版本号后)添加一个加号连接的编译信息。
这两者都需要遵循这样的规则:
- 多段之间使用句点分隔——如
1.0.0-alpha.1.1
。 - 每一段都必须是只包含字母、数字和连字符的字符串,且不能留白,数字也不能以 0 开头。
与此同时有一点需要注意,“版本编译信息”在版本优先级比较时是可以被忽略的,也就是说 1.0.0+202512
和 1.0.0+202516
的优先级可以认为是相同的。
版本优先级比较
通常来说,我们只需要知悉这样的比较规则就行:
- 比较从左到右,首个有差异的位决定版本优先级
- 数字比较时,数字越大优先级越高
- 字符串比较时,字母越靠前优先级越高(如
alpha
<beta
<rc
<final
) - 三段式版本号相同时,正式版本 > 先行版本
提示
个人建议,永远不要在一个项目内使用多种格式的先行版本号/编译信息,虽然在语义化版本的规定内,这是可行的,也是有约定的比较方式的。但你也不想在一个项目内看到 1.0.0-alpha.1
、1.0.0-beta.alpha
、1.0.0-rc.3
、1.0.0-1234
等多种格式的版本号吧?这会让人很困惑。
额外分享
在语义化版本的 FAQ 中,这样一段问答让我印象深刻:
对于公共 API,若即使是最小但不向下兼容的改变都需要产生新的主版本号,岂不是很快就达到 42.0.0 版?
这是开发的责任感和前瞻性的问题。不兼容的改变不应该轻易被加入到有许多依赖代码的软件中。升级所付出的代价可能是巨大的。要递增主版本号来发行不兼容的改版,意味着你必须为这些改变所带来的影响深思熟虑,并且评估所涉及的成本及效益比。
细想来,我接触软件开发的这些日子里,确实见过不少、自己也写过不少因为不兼容的改动而导致的版本混乱。很多时候,开发者在做出不兼容的改动时,并没有充分考虑到对现有用户的影响,导致用户不得不花费大量时间来适配新版本。
这也让我意识到,作为开发者,我们需要对自己的代码和版本充分负责,确保每一次的版本更新都是经过深思熟虑的。
版本号不仅是技术标记,更是开发者对世界的承诺。