简介
在软件管理的领域里存在着被称作"依赖地狱"的死亡之谷,系统规模越大,加入的包越多,你就越有可能在未来的某一天发现自己已深陷绝望之中。
在依赖高的系统中发布新版本包可能很快会成为噩梦。如果依赖关系过高,可能面临版本控制被锁死的风险(必须对每一个依赖包改版才能完成某次升级)。而如果依赖关系过于松散,又将无法避免版本的混乱(假设兼容于未来的多个版本已超出了合理数量)。当你专案的进展因为版本依赖被锁死或版本混乱变得不够简便和可靠,就意味着你正处于依赖地狱之中。
为什么需要版本
> 1. 对于软件工程,简单的说,因为有依赖。
现代软件工程,是模块和依赖构建,越大的规模,涉及的包越多,而依赖包会有如下更新情况:
在软件开发过程中,会有三种常见升级情况:
- 修复问题,没有加新功能
- 加入新功能,不影响当前功能
- 大的变动,和现有版本不兼容
如果没有一些规则,对每个依赖库的升级,就会变成一种灾难。
所以我们需要一种约定、规范,来实现依赖之间的合作,升级,及让开发者能清晰的识别。
> 2. 另一种情况是,
信息确认
通常人们说升级了一个新版本,用户需要来通过这个值确定是不是已经升到了最新。 其实,当用户在遇到问题时,客服、开发者需要知道用户的问题是出现在哪个版本下的。
从上可见,这种需求更多的适用于没有软件工程中库的依赖的顶级 APP。
常用版本格式
- 官网:SemVer(中文) : 语义化版本
- 官网:ChronVer :时间版本
SemVer:语义化版本控制
对于简介中提到的问题,语义化版本控制的解决方案是:
我提议用一组简单的规则及条件来约束版本号的配置和增长。这些规则是根据(但不局限于)已经被各种封闭、开放源码软件所广泛使用的惯例所设计。为了让这套理论运作,你必须先有定义好的 公共 API 。这可以透过文件定义或代码强制要求来实现。无论如何,这套 API 的清楚明了是十分重要的。一旦你定义了公共 API,你就可以透过修改相应的版本号来向大家说明你的修改。考虑使用这样的版本号格式:X.Y.Z (主版本号.次版本号.修订号)
- 修复问题但不影响API 时,递增修订号;
- API 保持向下兼容的新增及修改时,递增次版本号;
- 进行不向下兼容的修改时,递增主版本号。
我称这套系统为"语义化的版本控制",在这套约定下,版本号及其更新方式包含了相邻版本间的底层代码和修改内容的信息。
版本格式:主版本号.次版本号.修订号
(英文为:MAJOR.MINOR.PATCH
),版本号递增规则如下:
- 主版本号(MAJOR):当你做了不兼容的 API 修改,(breaking change)
- 次版本号(MINOR):当你做了向下兼容的功能性新增,(new feature)
- 修订号(PATCH):当你做了向下兼容的问题修正。(bug fix)
先行版本号 及 版本编译元数据 可以加到"主版本号.次版本号.修订号
" 的后面,作为延伸。
语义化版本控制规范
-
使用语义化版本控制的软件必须(MUST)定义公共 API。
-
标准的版本号 必须 (MUST)采用
X.Y.Z
的格式,其中X
、Y
和Z
为非负的整数,且禁止(MUST NOT)在数字前方补零。X
是主版本号、Y
是次版本号、而Z
为修订号。每个元素必须(MUST)以数值来递增。例如:1.9.1 -> 1.10.0 -> 1.11.0
。 -
标记版本号的软件发行后,禁止(MUST NOT)改变该版本软件的内容 。任何修改都必须(MUST)以新版本发行。⭐
-
主版本号为零(
0.y.z
)的软件处于开发初始阶段,一切都可能随时被改变。这样的公共 API 不应该被视为稳定版。 -
修订号
Z
(x.y.Z | x > 0
)必须(MUST)在只做了向下兼容的修正时才递增。这里的修正指的是针对不正确结果而进行的内部修改。 -
次版本号
Y
(x.Y.z | x > 0
)必须(MUST)在有向下兼容的新功能出现时递增。在任何公共 API 的功能被标记为弃用时也必须(MUST)递增。也可以(MAY)在内部程序有大量新功能或改进被加入时递增,其中可以(MAY)包括修订级别的改变。每当次版本号递增时,修订号必须(MUST)归零。 -
主版本号
X
(X.y.z | X > 0
)必须(MUST)在有任何不兼容的修改被加入公共 API 时递增。其中可以(MAY)包括次版本号及修订级别的改变。每当主版本号递增时,次版本号和修订号必须(MUST)归零。 -
先行版本号 可以(MAY)被标注在修订版之后,先加上一个
连接号
再加上一连串以句点
分隔的标识符
来修饰。标识符必须(MUST)由 ASCII 字母数字和连接号[0-9A-Za-z-]
组成,且禁止(MUST NOT)留白。数字型的标识符禁止(MUST NOT)在前方补零。先行版的优先级低于相关联的标准版本。被标上先行版本号则表示这个版本并非稳定而且可能无法满足预期的兼容性需求。先行版本号,范例:
1.0.0-alpha
1.0.0-alpha.1
1.0.0-0.3.7
1.0.0-x.7.z.92
-
版本编译元数据可以(MAY)被标注在修订版或先行版本号之后,先加上一个
加号
再加上一连串以句点
分隔的标识符
来修饰。标识符必须(MUST)由 ASCII 字母数字和连接号[0-9A-Za-z-]
组成,且禁止(MUST NOT)留白。当判断版本的优先层级时,版本编译元数据可(SHOULD)被忽略。因此当两个版本只有在版本编译元数据有差别时,属于相同的优先层级。版本编译元数据,范例:
1.0.0-alpha+001
1.0.0+20130313144700
1.0.0-beta+exp.sha.5114f85
-
版本的优先层级指的是不同版本在排序时如何比较。判断优先层级时,必须(MUST)把版本依序拆分为主版本号、次版本号、修订号及先行版本号后进行比较(版本编译元数据不在这份比较的列表中)。
版本的优先层级,范例:
1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0
最佳实践
0.1.0
- Init 项目初始化0.2.0
- 加入了新的功能1.0.0
- 版本正式上线1.0.1
- Bug 修复2.0.0
- 重构了新的 API,与1.x
不兼容- 先行版本号: 一般可以使用
dev
,alpha
,beta
,rc1
等
常见问题
-
在
0.y.z
初始开发阶段,我该如何进行版本控制?最简单的做法是以
0.1.0
作为你的初始化开发版本,并在后续的每次发行时递增次版本号。 -
如何判断发布
1.0.0
版本的时机?当你的软件被用于正式环境,它应该已经达到了
1.0.0
版。如果你已经有个稳定的 API 被使用者依赖,也会是1.0.0
版。如果你很担心向下兼容的问题,也应该算是1.0.0
版了。
ChronVer:时间版本控制
版本格式: YEAR.MONTH.DAY.CHANGESET_IDENTIFIER
,版本号递增规则:
YEAR
年份更改MONTH
月份更改DAY
每日更改CHANGESET_IDENTIFIER
每次您对 包/项目 进行了提交更改时。
对于简介中提到的问题,时间版本控制的解决方案是:
作为此问题的解决方案,我提出了一组简单的规则和要求,这些规则和要求规定了如何分配和递增版本号。 很简单,这些规则是基于时间的。 您可以使用版本号的特定增量传达对项目的更改。 考虑
A.B.C.D
(Year.Month.Day.Change
)的版本格式。 如果没有新的更改,则不必包含D
。 例如:有一天你开始做一个新项目,版本号是
2006.04.01
;当天晚些时候,您将向项目提交一个新的更改,版本号变为2006.04.01.1
。第二天,您的第一次提交将使版本号变为:2006.04.02
。我称此系统为 “时间版本控制 ( Chronologic Versioning )” 。 在此方案下,版本号及其更改方式不仅比其他版本控制方案更容易理解,而且更有可能 “stick” 。 不再需要任意版本更新和回归来更正版本错误。
时间版本控制的规范
- 版本号必须采用
A.B.C.D
格式,其中A
,B
,C
和D
是非负整数。A
和D
不得包含前导零。B
和C
均为一位时,必须包含前导零。A
是年度版本,B
是每月版本,C
是每日版本,D
是变更集版本。 每个元素必须按数字增加。 例如:2006.04.01
>2006.04.02
>2006.04.03
。 - 在前述形式为
A.B.C.D
的版本号示例中,如果D
为零,则D
是可选的。 例如:2006.04.01
等于2006.04.01.0
。 - 如果引入了向后不兼容的更改,则必须添加连字符和保留的标签"
break
"。 例如:2006.04.03.12
>2006.04.03.12-break
。 - 标记版本号的软件发行后,禁止(MUST NOT)改变该版本软件的内容 。任何修改都必须(MUST)以新版本发行。
为什么使用时间版本控制?
与其他版本控制方案相比,遵从性充其量是不一致和松散的。顺从?拜托。以语义版本控制为例,当应用程序一年发布几次或更少时,它是有意义的。我们现在正处于持续交付/集成的时代,v3.5.27
的概念在web应用程序或任何看到快速开发和发布的东西上都丢失了。您的用户几乎肯定对您的版本号没有兴趣,也无法理解您在版本号后面附加的内容。
使用时间顺序版本控制的一个令人愉快的副作用是能够一目了然地查看项目中的哪些依赖项在一段时间内没有更新(如果这些依赖项也正在使用时间顺序版本控制)。按时间顺序的版本控制为您提供了一种理智的方式来考虑依赖关系管理,从而节省了时间和麻烦。
常见问题
-
这样的生态是在促进快速开发和快速迭代吗?
是的!ChronVer 致力于快速开发。
-
这听起来很适合个人项目,但我怎样才能把它用于工作呢?
标记功能版本是许多团队的通用工作流程。 以下是ChronVer如何在多开发者环境中工作的示例。
想象一下,您有团队在研究某个功能,他们将任务分为 UI分支 和 implementation (实现)分支:
2019.05.08.14 (base release) ├─ 2019.05.08.14-super-ui-enhance (UI fork) │ └─ 2019.05.08.14-super-ui-enhance.13 (UI fork, later in the day) └─ 2019.05.08.14-super-ui-please-work (implementation fork) └─ 2019.05.08.14-super-ui-please-work.57 (implementation fork, later in the day)
如您所见,
super-ui-enhance
和super-ui-please-work
是从2019.05.08
创建的发行版的第14个更新中派生的特性版本。第二天的某个时候,这些分支可能是这样的:
2019.05.09-super-ui-enhance
2019.05.09-super-ui-please-work.9
将你的工作合并到主分支后,可以安全地从版本号中省略
-FEATURE_NAME.CHANGESET_IDENTIFIER
。 -
我应该如何处理过时的功能?
弃用现有功能是软件开发的正常部分。 弃用 公共API 的一部分(如果存在)时,应该做两件事:
- (1)更新文档以使用户知道更改;
- (2)发行具有弃用特性(带有
break
)的最新版本(或者翻译为:发行具有弃用功能的临时发行版)。
这里有一篇文章,介绍日期版本的一些现有实例。
软件版本阶段说明
在软件开发周期中一般包含下面几个阶段:
- Propose(Requirement) : 提案(需求)
- Development : 开发
- Integration + Testing : 集成和测试
- Release : 发布
在软件版本命名时,一般也会有对应软件开发周期中各阶段的软件版本阶段说明。
而对于测试我们还可以使用:Alpha(α,阿尔法)、Beta(β,贝塔)和Gamma(γ,伽玛),用来标识测试的阶段与范围。
- Alpha 指的是内测,即现在说的 CB,即开发团队内部测试的版本或者有限用户的体验测试版本。
- Beta 指的是公测,即针对所有用户公开的测试版本。
- 而做过一些修改,成为正式发布的候选版本时(现在叫做 RC - Release Candidate),叫做 Gamma。
最终软件版本阶段说明就有下面几种:
- Alpha版: 此版本表示该软件在此阶段主要是以实现软件功能为主,通常只在软件开发者内部交流,一般而言,该版本软件的Bug较多,需要继续修改。
- Beta版: 该版本相对于α版已有了很大的改进,消除了严重的错误,但还是存在着一些缺陷,需要经过多次测试来进一步消除,此版本主要的修改对像是软件的UI。
- RC版: 该版本已经相当成熟了,基本上不存在导致错误的BUG,与即将发行的正式版相差无几。
- Release版: 该版本意味"最终版本",在前面版本的一系列测试版之后,终归会有一个正式版本,是最终交付用户使用的一个版本。该版本有时也称为标准版。一般情况下,Release 不会以单词形式出现在软件封面上,取而代之的是符号(R)。
在 语义化版本控制规范 中:
dev
、alpha
、beta
、rc
等希腊字母可以用在 先行版本号 之中;- 对于 Release版(标准版本)只存在
主版本号.次版本号.修订号
,即Release
或R
等任何希腊字母并不会出现在标准版本中。
Android 应用中的 版本号 与 版本名
- VersionCode ( 版本号),这是一个
Integer类型
的值。所以大家在设置的时候,不要将versionCode设置的太大,最好不要超过Integer的取值范围(当然一般也是不会超过的),一般大家在发布自己的第一个应用到市场的时候,版本取值为1(versionCode=1),这也是目前典型和普遍的做法。然后,每次发布更新版本时可以递增versionCode的值。上面描述版本控制重要性时也描述过,一个新版本的应用的versionCode不能小于之前旧版本的versionCode值,否则进行替换更新升级时会出错,系统提示无法安装。当然,这也不是强制的,只是正式发布应用时,建议必须考虑的问题。- VersionName (版本名),这是一个值为
String类型
的属性,一般和VersionCode成对出现。VersionCode是方便程序开发者运行和维护Application而设置的一个有效的值。versionName是一个版本的描述,给用户看的,也是用户放在各个第3方平台上提供给使用者看的一个版本名,可以说是对 VersionCode 的解释和描述。一般格式可以为:1.1.2(major.minor.point)的形式。
当第一次看到时,我确实有点懵,当仔细观察后感觉关键点应该是类型,一个是 Integer类型
一个是 String类型
;个人理解是这里的 VersionName (版本名) 命名规则应该可以使用上文介绍的规则。如有错误欢迎指正。