版本规则介绍

▎认识版本号

这是 Windows 10 的版本号,相信在每日的使用与升级中,大家都有所目睹。


▎为什么需要版本

> 1. 对于软件工程,简单的说,因为有依赖。

现代软件工程,是模块和依赖构建,越大的规模,涉及的包越多,而依赖包会有如下更新情况:

在软件开发过程中,会有三种常见升级情况:

  • 修复问题,没有加新功能
  • 加入新功能,不影响当前功能
  • 大的变动,和现有版本不兼容

如果没有一些规则,对每个依赖库的升级,就会变成一种灾难。

所以我们需要一种约定、规范,来实现依赖之间的合作,升级,及让开发者能清晰的识别。

> 2. 另一种情况是,信息确认

通常人们说升级了一个新版本,用户需要来通过这个值确定是不是已经升到了最新。
其实,当用户在遇到问题时,客服、开发者需要知道用户的问题是出现在哪个版本下的。

从上可见,这种需求更多的适用于没有软件工程中库的依赖的顶级 APP。


▎常用版本格式

以上是当下最常见的两种通用版本格式,适用于主流应用,库及一些模块。


> SemVer | 语义化版本 2.0.0

版本格式:主版本号.次版本号.修订号,例如 1.9.15

版本号递增规则如下:

  • 主版本号:当你做了不兼容的 API 修改,
  • 次版本号:当你做了向下兼容的功能性新增,
  • 修订号:当你做了向下兼容的问题修正。

先行版本号及版本编译元数据可以加到“主版本号.次版本号.修订号”的后面,作为延伸。

该规范对于依赖库,非常实用。它可以通过通用的规范来做 Bug Fix 级别升级,或进行 Feature 级别的更新,事实也是如此。

- Bundler 实例

这里用 Ruby Gemfile 的管理器 Bundler 来举例:

1
2
3
4
gem 'nokogiri'
gem 'rails', '3.0.0.beta3'
gem 'rack', '>=1.0'
gem 'thin', '~>1.1'
  • = 指定版本 3.0.0,表示仅限制于该版本
  • >= 如字面意思,大于等于该版本都可升级
  • ~> 2.0 是指可以升级所有的功能更新 [‘>= 2.0’, ‘< 3.0’]
  • ~> 2.2.0 是指问题修复升级 [‘>= 2.2.0’, ‘< 2.3.0’]

- 最佳实践

在项目

  • 0.1.0 - Init 项目初始化
  • 0.2.0 - 加入了新的功能
  • 1.0.0 - 版本正式上线
  • 1.0.1 - Bug 修复
  • 2.0.0 - 重构了新的 API,与 1.x 不兼容

> ChronVer | 日期式版本 2019.05.19

Chronologic Versioning “日期版本”,软件版本按照年月日编排。

版本格式:A.B.C.D (Year.Month.Day.Change)

因为用户在使用场景上更在意的是版本的发布时间,语义化版本号对用户认知有一定的门槛。

如果是同一天发布多次,版本号如何定呢?

虽然官网讲了很多案例,但这种版本方式非常不适用在软件依赖包中,因为日期是没有功能的规则的。


> 对比

综上,SemVer 语义化版本适用于所有的情况。包括各种开源代码库,二进制包,终端应用产品。

在上家公司,让人印象最深的一个解释,PR 在给客户介绍产品时,指着下面的版本号,讲道,知道这个 6 代表着什么吗,代表着公司人的努力印迹,在几年的时间不断迭代,这已经是第六代产品了,那种自豪感,非常强烈。

SemVer 用在终端应用上的一个常见问题就是,在很长一个时间内,版本会一直维护在 1.xx.xx 上。

ChronVer 日期版本,更多适用于和用户产品相关的末端应用,像各种客户端,在场景上,使用者都是各行业者,在技术上它不被其它产品依赖。


▎错误示范

> 对 Docker 的吐槽

  • Docker Desktop 2.2.0.0
  • Engine 19.03.5
  • Compose v1.25.2
  • Notary 0.6.1
  • Kubernetes v1.15.5

天那,这都是什么样的版本命名呀。

Desktop 是一款面向用户的 UI APP,比较适合的方式是 ChronVer,用 SemVer 也合适,但这里用了四个字段来表示,不知最后一个代表什么。

而 Engine 更像是一个库,它被上面的 Desktop 依赖,这就表示它需要被知道是哪种类别的更新,更适合的版本方式是 SemVer。

Compose 和 Kubernetes 前面又加了个 v。

> 对 Golang 的吐槽

Golang 在 2018 2019 年最大的变化就是加入了 Go Module,那么来看看它是如何使用的吧:

1
2
3
4
5
6
7
require (
github.com/BurntSushi/toml v0.3.1
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-gonic/gin v1.5.0
}

Go Module 起源于 8012 年,在此之前有各种语言作为参考,但很遗憾,依赖设计出这么丑陋的模块依赖。

  • 依赖配置不是由开发者指定,而是代码分析生成
  • 依赖中大量的库没有版本号,Go 默认使用 Git 日期和 Commit Sha1
  • 间接依赖也在配置中?这就不懂了,不是有 go.sum 吗。

做好一门语言,最难的部分就在于依赖库管理,但这不应该是 Go 的借口,毕竟不是单片机时代。


▎ REF::