▎认识版本号
这是 Windows 10 的版本号,相信在每日的使用与升级中,大家都有所目睹。
▎为什么需要版本
> 1. 对于软件工程,简单的说,因为有依赖。
现代软件工程,是模块和依赖构建,越大的规模,涉及的包越多,而依赖包会有如下更新情况:
在软件开发过程中,会有三种常见升级情况:
- 修复问题,没有加新功能
- 加入新功能,不影响当前功能
- 大的变动,和现有版本不兼容
如果没有一些规则,对每个依赖库的升级,就会变成一种灾难。
所以我们需要一种约定、规范,来实现依赖之间的合作,升级,及让开发者能清晰的识别。
> 2. 另一种情况是,信息确认
通常人们说升级了一个新版本,用户需要来通过这个值确定是不是已经升到了最新。
其实,当用户在遇到问题时,客服、开发者需要知道用户的问题是出现在哪个版本下的。
从上可见,这种需求更多的适用于没有软件工程中库的依赖的顶级 APP。
▎常用版本格式
- SemVer https://semver.org
- ChronVer https://chronver.org
以上是当下最常见的两种通用版本格式,适用于主流应用,库及一些模块。
> SemVer | 语义化版本 2.0.0
版本格式:主版本号.次版本号.修订号
,例如 1.9.15
版本号递增规则如下:
- 主版本号:当你做了不兼容的 API 修改,
- 次版本号:当你做了向下兼容的功能性新增,
- 修订号:当你做了向下兼容的问题修正。
先行版本号及版本编译元数据可以加到“主版本号.次版本号.修订号”的后面,作为延伸。
该规范对于依赖库,非常实用。它可以通过通用的规范来做 Bug Fix 级别升级,或进行 Feature 级别的更新,事实也是如此。
- Bundler 实例
这里用 Ruby Gemfile 的管理器 Bundler 来举例:
- https://guides.rubygems.org/patterns/#pessimistic-version-constraint
- https://bundler.io/v1.5/gemfile.html
1 | gem 'nokogiri' |
- = 指定版本
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 | require ( |
Go Module 起源于 8012 年,在此之前有各种语言作为参考,但很遗憾,依赖设计出这么丑陋的模块依赖。
- 依赖配置不是由开发者指定,而是代码分析生成
- 依赖中大量的库没有版本号,Go 默认使用 Git 日期和 Commit Sha1
- 间接依赖也在配置中?这就不懂了,不是有 go.sum 吗。
做好一门语言,最难的部分就在于依赖库管理,但这不应该是 Go 的借口,毕竟不是单片机时代。