设为首页 加入收藏

TOP

再探go modules:使用与细节(一)
2019-01-20 16:08:25 】 浏览:531
Tags:再探 modules 使用 细节

还有半个月go1.12就要发布了。这是首个将go modules纳入正式支持的稳定版本。

距离go modules随着go1.11正式面向广大开发者进行体验也已经过去了半年,这段时间go modules也发生了一些变化,借此机会我想再次深入探讨go modules的使用,同时对这个新生包管理方案做一些思考。

本文索引

版本控制和语义化版本

包的版本控制总是一个包管理器绕不开的古老话题,自然对于我们的go modules也是这样。

我们将学习一种新的版本指定方式,然后深入地探讨一下golang官方推荐的semver即语义化版本。

控制包版本

在讨论go get进行包管理时我们曾经讨论过如何对包版本进行控制(文章在此),支持的格式如下:

vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdef
vX.0.0-yyyymmddhhmmss-abcdefabcdef
vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdefabcdef
vX.Y.Z

在go.mod文件中我们也需要这样指定,否则go mod无法正常工作,这带来了2个痛点:

  1. 目标库需要打上符合要求的tag,如果tag不符合要求不排除日后出现兼容问题(目前来说只要正确指定tag就行,唯一的特殊情况在下一节介绍)
  2. 如果目标库没有打上tag,那么就必须毫无差错的编写大串的版本信息,大大加重了使用者的负担

基于以上原因,现在可以直接使用commit的hash来指定版本,如下:

# 使用go get时
go get github.com/mqu/go-notify@ef6f6f49

# 在go.mod中指定
module my-module

require (
  // other packages
  github.com/mqu/go-notify ef6f6f49
)

随后我们运行go buildgo mod tidy,这两条命令会整理并更新go.mod文件,更新后的文件会是这样:

module my-module

require (
    github.com/mattn/go-gtk v0.0.0-20181205025739-e9a6766929f6 // indirect
    github.com/mqu/go-notify v0.0.0-20130719194048-ef6f6f49d093
)

可以看到hash信息自动扩充成了符合要求的版本信息,今后可以依赖这一特性简化包版本的指定。

对于hash信息只有两个要求:

  1. 指定hash信息时不要在前面加上v,只需要给出commit hash即可
  2. hash至少需要8位,与git等工具不同,少于8位会导致go mod无法找到包的对应版本,推荐与go mod保持一致给出12位长度的hash

然而这和我们理想中的版本控制方式似乎还是有些出入,是不是觉得。。。有点不直观?接下来介绍的语义化版本也许能带来一些改观。

语义化版本

golang官方推荐的最佳实践叫做semver,这是一个简称,写全了就是Semantic Versioning,也就是语义化版本。

何谓语义化

通俗地说,就是一种清晰可读的,明确反应版本信息的版本格式,更具体的规范在这里

如规范所言,形如vX.Y.Z的形式显然比一串hash更直观,所以golang的开发者才会把目光集中于此。

为何使用语义化版本

semver简化版本指定的作用是显而易见的,然而仅此一条理由显然有点缺乏说服力,毕竟改进后的版本指定其实也不是那么麻烦,对吧?

那么为何要引入一套新的规范呢?

我想这可能与golang一贯重视工程化的哲学有关:

不要删除导出的名称,鼓励标记的复合文字等等。如果需要不同的功能,添加 新名称而不是更改旧名称。如果需要完整中断,请创建一个带有新导入路径的新包。 -go modules wiki

通过semver对版本进行严格的约束,可以最大程度地保证向后兼容以及避免“breaking changes”,而这些都是golang所追求的。两者一拍即合,所以go modules提供了语义化版本的支持。

语义化版本带来的影响

如果你使用和发布的包没有版本tag或者处于1.x版本,那么你可能体会不到什么区别,因为go mod所支持的格式从始至终是遵循semver的,主要的区别体现在v2.0.0以及更高版本的包上。

“如果旧软件包和新软件包具有相同的导入路径,则新软件包必须向后兼容旧软件包。” - go modules wiki

正如这句话所说,相同名字的对象应该向后兼容,然而按照语义化版本的约定,当出现v2.0.0的时候一定表示发生了重大变化,很可能无法保证向后兼容,这时候应该如何处理呢?

答案很简单,我们为包的导入路径的末尾附加版本信息即可,例如:

module my-module/v2

require (
  some/pkg/v2 v2.0.0
  some/pkg/v2/mod1 v2.0.0
  my/pkg/v3 v3.0.1
)

格式总结为pkgpath/vN,其中N是大于1的主要版本号。在代码里导入时也需要附带上这个版本信息,如import "some/pkg/v2"。如此一来包的导入路径发生了变化,也不用担心名称相同的对象需要向后兼容的限制了,因为golang认为不同的导入路径意味着不同的包。

不过这里有几个例外可以不用参照这种写法:

  1. 当使用gopkg.in格式时可以使用等价的require gopkg.in/some/pkg.v2 v2.0.0
  2. 在版本信息后加上+incompatible就可以不需要指定/vN,例如:require some/pkg v2.0.0+incompatible
  3. 使用go1.11时设置GO111MODULE=off将取消这种限制,当然go1.12里就不能这么干了

除此以外的情况如果直接使用v2+版本将会导致go mod报错。

v2+版本的包允许和其他不同大版本的包同时存在(前提是添加了/vN),它们将被当做不同的包来处理。

另外/vN并不会影响你的仓库,不需要创建一个v2对应的仓库,这只是go modules添加的一种附加信息而已。

当然如果你不想遵循这一规范或者需要兼容现有代码,那么指定+incompatible会是一个合理的选择。不过如其字面意思,go modules不推荐这种行为。

一点思考

眼尖的读者可能已经发现了,semver很眼熟。

是的,REST api是它的最忠实用户,像xxx.com/api/v2/xxx的最佳实践我们恐怕都司空见惯了,所以golang才会要求v2+的包使用pkg/v2的形式。然而把REST api的最佳实践融合进包管理器设计,真的会是又一个最佳实践吗?

我觉得未必如此,一个显而易见的缺点就在于向后兼容上,主流的包管理器都只采用semver的子集,最大的原因在于如果只提供对版本的控制,而把先后兼容的责任交由开发者/用户相对于强行将无关的信

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Ubuntu 16.04 下简单安装使用gola.. 下一篇[Go] 使用go语言解决现代编程难题

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目