侧边栏壁纸
博主头像
孔子说JAVA博主等级

成功只是一只沦落在鸡窝里的鹰,成功永远属于自信且有毅力的人!

  • 累计撰写 285 篇文章
  • 累计创建 125 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

版本命名规范:语义化版本2.0.0

孔子说JAVA
2022-04-11 / 0 评论 / 0 点赞 / 197 阅读 / 5,025 字 / 正在检测是否收录...

semver是语义化版本(Semantic Versioning)规范的一个实现,目前是由 npm 的团队维护,实现了版本和版本范围的解析、计算、比较。

1、简介

在软件管理的领域里存在着被称作“依赖地狱”的死亡之谷,系统规模越大,加入的包越多,你就越有可能在未来的某一天发现自己已深陷绝望之中。

在依赖高的系统中发布新版本包可能很快会成为噩梦。如果依赖关系过高,可能面临版本控制被锁死的风险(必须对每一个依赖包改版才能完成某次升级)。而如果依赖关系过于松散,又将无法避免版本的混乱(假设兼容于未来的多个版本已超出了合理数量)。当你项目的进展因为版本依赖被锁死或版本混乱变得不够简便和可靠,就意味着你正处于依赖地狱之中。

  • 作为这个问题的解决方案之一,我提议用一组简单的规则及条件来约束版本号的配置和增长。这些规则是根据(但不局限于)已经被各种封闭、开放源码软件所广泛使用的惯例所设计。为了让这套理论运作,你必须先有定义好的公共 API。这可以透过文件定义或代码强制要求来实现。无论如何,这套 API 的清楚明了是十分重要的。一旦你定义了公共 API,你就可以透过修改相应的版本号来向大家说明你的修改。考虑使用这样的版本号格式:X.Y.Z(主版本号.次版本号.修订号)修复问题但不影响 API 时,递增修订号;API 保持向下兼容的新增及修改时,递增次版本号;进行不向下兼容的修改时,递增主版本号。

  • 我称这套系统为“语义化的版本控制”,在这套约定下,版本号及其更新方式包含了相邻版本间的底层代码和修改内容的信息。

2、语义化版本(semver)格式

semver的格式是点号分割的三段数字,形如MAJOR.MINOR.PATCH,还可以表示为X.Y.Z。中文涵义表示为主版本号.次版本号.修订号,例如1.0.0,每段的数字有特定的含义。版本号递增规则如下:

  1. MAJOR 表示主版本号(即大版本的升级),两个MAJOR版本之间的API是允许不兼容的,比如2.x.x与1.x.x的API是不兼容,著名的例子如Python3和Python2不兼容,Anjuar2与Anjular1也是不兼容。(场景:当你做了不兼容的 API 修改
  2. MINOR 表示次版本号(小版本的升级),两个MINOR版本之间的API要求是必须兼容的,MINOR版本的升级往往是程序功能(feature)的增加,可以将MINOR号码理解成feature号。著名的例子如java1.8就是向后兼容的java1.7的API,同时 java1.8中还增加了流式编程的新功能。(场景:你做了向下兼容的功能性新增
  3. PATCH 表示修订号(bug的修复),PATCH的变动应该不影响程序的API和feature,只是对特定版本的程序已知bug进行修复。(场景:当你做了向下兼容的问题修正

image-1649644022609

注意:

  1. 先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。
  2. 当主版本号升级后,次版本号和修订号需要重置为0,次版本号进行升级后,修订版本需要重置为0。

3、先行版本号(Pre-release Version)

先行版本号可以作为发布正式版之前的版本,格式是在修订版本号后面加上一个连接号(-),再加上一连串以点(.)分割的标识符,标识符可以由英文、数字和连接号([0-9A-Za-z-])组成。例子如下:

1.0.0-alpha
1.0.0-alpha.1
1.0.0-0.3.7
1.0.0-x.7.z.92

以下是一些常见的先行版本号名称:

  • alpha :是内部测试版,一般不向外部发布,会有很多Bug.一般只有测试人员使用。
  • beta :也是测试版,这个阶段的版本会一直加入新的功能。在Alpha版之后推出
  • rc :Release Candidate,系统平台上就是发行候选版本。RC版不会再加入新的功能了,主要着重于除错。

4、定义依赖版本号

在 npm 的依赖的规则中,还有 ~、>、<、=、>=、<=、-、||、x、X、* 等符号;当使用 npm install XX 时,被安装的依赖的版本号前会默认加上 ^ 符号。

  • ^ :表示不会修改版本号中的第一个非零数字,如同一主版本号中,不小于指定版本号的版本号。
    ^2.2.1 对应主版本号为 2,不小于 2.2.1 的版本号,比如 2.2.12.2.22.3.0 ,主版本号固定。当该依赖有最新版本时(eg:2.3.3),npm install 会安装最新的依赖
表示 涵义
^2.2.1 >= 2.2.1 <3.0.0
^0.4.2 >= 0.4.2 <0.5.0
^0.0.3 >= 0.0.3 <0.0.4
  • ~ :字符范围,同时使用字符~和次版本号,表明允许修订号变更。同时使用字符~和主版本号,表明允许次版本号变更。
    ~2.2.1 对应主版本号为 2,次版本号为 2,不小于 2.2.1 的版本号,比如 2.2.1、2.2.2,主版本号和次版本号固定。
表示 涵义
~3.2.1 >= 3.2.1 <3.3.0
~3.2 >= 3.2.x 或 >= 3.2.0 <3.3.0
~3 >= 3.x 或 >= 3.0.0 <4.0.0
  • >、<、=、>=、<=、-:用来指定一个版本号范围(注意使用 - 的时候,必须两边都有空格),||表示或。
    >2.1
    1.0.0 - 1.2.0
    <2.2 || > 2.3
表示 涵义
<1.0.0 任何小于1.0.0的版本
<3.2.3 任何小于或等于3.2.3的版本
>0.2.3 任何大于0.2.3的版本
>=2.2.3 任何大于或等于2.2.3的版本
=2.2.3 等于2.2.3的版本
>=2.0.0<3.2.5 交集,大于或等于2.0.0并小于3.2.5的版本
2.0.0 - 3.2.1 >= 2.0.0 <=3.2.1
0.5 - 2 >= 0.5.0 <=2.0.0
`<2.0.0
  • x、X、*:表示通配符,* 对应所有版本号,用于填充部分或全部版本号。被省略的那部分版本号默认为 x 范围。
    3.x 对应所有主版本号为 3 的版本号
表示 涵义
* >= 0.0.0(任意版本)
3 或 3.x >= 3.0.0 <4.0.0(匹配主要版本)
3.1 或 3.1.x >= 3.1.0 <3.2.0(匹配主要和次要版本)
‘’(空字符串) * 或 >= 0.0.0(任意版本)

5、npm 中 package-lock.json 的一些坑

在 npm install 后,会生成一个 package-lock.json 文件用于保存当前安装依赖的各种来源及版本号。在 npm 5.4.2版本后,package-lock.json 的变动规则:

  1. 当在 install dependency 的指定版本时,会自动更新 package-lock.json 文件中该 dependency 的 version 到指定的 version
  2. 当在 install dependency 的范围版本时,当前的 version 低于or等于 package-lock.json 文件中对应的 dependency 的 version 时,会安装 package-lock.json 中的 version;
package.json
"antd": "^3.6.1", // eg:最新版本是 3.9.4

package-lock.json
"antd": "3.7.1",

# 执行npm install 会安装 3.7.1 版本
  • 如果高于 package-lock.json 中对应的 dependency 的 version 时,会安装当前范围版本号中最高的版本,同时更新 package-lock.json 文件中对应的版本号;

6、为什么要使用语义化的版本控制?

这并不是一个新的或者革命性的想法。实际上,你可能已经在做一些近似的事情了。问题在于只是“近似”还不够。如果没有某个正式的规范可循,版本号对于依赖的管理并无实质意义。将上述的想法命名并给予清楚的定义,让你对软件使用者传达意向变得容易。一旦这些意向变得清楚,弹性(但又不会太弹性)的依赖规范就能达成。

  • 举个简单的例子就可以展示语义化的版本控制如何让依赖地狱成为过去。假设有个名为“救火车”的函数库,它需要另一个名为“梯子”并已经有使用语义化版本控制的包。当救火车创建时,梯子的版本号为 3.1.0。因为救火车使用了一些版本 3.1.0 所新增的功能,你可以放心地指定依赖于梯子的版本号大于等于 3.1.0 但小于 4.0.0。这样,当梯子版本 3.1.1 和 3.2.0 发布时,你可以将直接它们纳入你的包管理系统,因为它们能与原有依赖的软件兼容。

  • 作为一位负责任的开发者,你理当确保每次包升级的运作与版本号的表述一致。现实世界是复杂的,我们除了提高警觉外能做的不多。你所能做的就是让语义化的版本控制为你提供一个健全的方式来发行以及升级包,而无需推出新的依赖包,节省你的时间及烦恼。

7、FAQ

在 0.y.z 初始开发阶段,我该如何进行版本控制?

  • 最简单的做法是以 0.1.0 作为你的初始化开发版本,并在后续的每次发行时递增次版本号。

如何判断发布 1.0.0 版本的时机?

  • 当你的软件被用于正式环境,它应该已经达到了 1.0.0 版。如果你已经有个稳定的 API 被使用者依赖,也会是 1.0.0 版。如果你很担心向下兼容的问题,也应该算是 1.0.0 版了。

这不会阻碍快速开发和迭代吗?

  • 主版本号为零的时候就是为了做快速开发。如果你每天都在改变 API,那么你应该仍在主版本号为零的阶段(0.y.z),或是正在下个主版本的独立开发分支中。

对于公共 API,若即使是最小但不向下兼容的改变都需要产生新的主版本号,岂不是很快就达到 42.0.0 版?

  • 这是开发的责任感和前瞻性的问题。不兼容的改变不应该轻易被加入到有许多依赖代码的软件中。升级所付出的代价可能是巨大的。要递增主版本号来发行不兼容的改版,意味着你必须为这些改变所带来的影响深思熟虑,并且评估所涉及的成本及效益比。

为整个公共 API 写文件太费事了!

  • 为供他人使用的软件编写适当的文件,是你作为一名专业开发者应尽的职责。保持专案高效一个非常重要的部份是掌控软件的复杂度,如果没有人知道如何使用你的软件或不知道哪些函数的调用是可靠的,要掌控复杂度会是困难的。长远来看,使用语义化版本控制以及对于公共 API 有良好规范的坚持,可以让每个人及每件事都运行顺畅。

万一不小心把一个不兼容的改版当成了次版本号发行了该怎么办?

  • 一旦发现自己破坏了语义化版本控制的规范,就要修正这个问题,并发行一个新的次版本号来更正这个问题并且恢复向下兼容。即使是这种情况,也不能去修改已发行的版本。可以的话,将有问题的版本号记录到文件中,告诉使用者问题所在,让他们能够意识到这是有问题的版本。

如果我更新了自己的依赖但没有改变公共 API 该怎么办?

  • 由于没有影响到公共 API,这可以被认定是兼容的。若某个软件和你的包有共同依赖,则它会有自己的依赖规范,作者也会告知可能的冲突。要判断改版是属于修订等级或是次版等级,是依据你更新的依赖关系是为了修复问题或是加入新功能。对于后者,我经常会预期伴随着更多的代码,这显然会是一个次版本号级别的递增。

如果我变更了公共 API 但无意中未遵循版本号的改动怎么办呢?(意即在修订等级的发布中,误将重大且不兼容的改变加到代码之中)

  • 自行做最佳的判断。如果你有庞大的使用者群在依照公共 API 的意图而变更行为后会大受影响,那么最好做一次主版本的发布,即使严格来说这个修复仅是修订等级的发布。记住, 语义化的版本控制就是透过版本号的改变来传达意义。若这些改变对你的使用者是重要的,那就透过版本号来向他们说明。

我该如何处理即将弃用的功能?

  • 弃用现存的功能是软件开发中的家常便饭,也通常是向前发展所必须的。当你弃用部份公共 API 时,你应该做两件事:(1)更新你的文件让使用者知道这个改变,(2)在适当的时机将弃用的功能透过新的次版本号发布。在新的主版本完全移除弃用功能前,至少要有一个次版本包含这个弃用信息,这样使用者才能平顺地转移到新版 API。

语义化版本对于版本的字串长度是否有限制呢?

  • 没有,请自行做适当的判断。举例来说,长到 255 个字元的版本已过度夸张。再者,特定的系统对于字串长度可能会有他们自己的限制。

语义化版本2.0.0官网https://semver.org/lang/zh-CN/

0

评论区