关于golang:学习-go-mod-的心得

2次阅读

共计 5207 个字符,预计需要花费 14 分钟才能阅读完成。

学习 go mod 的心得

@Spike


参考

  • 官网 Guide
  • Russ Cox 的 Go & Versioning 11 篇

请留神:这篇文章次要是学习 go mod 的应用和对 Go & Versioning 这个系列文章的学习心得。

如果你看完且了解了这个系列的文章,那么接下来的内容应该对你没有任何帮忙。


蕴含内容

  • 什么包治理,有哪些内容?
  • Go 的包治理是怎么的。
  • 怎么在 Go 外面实现最好的实际。

大部分新一点的语言都有本人的包管理系统,例如 CPAN(perl), pip(Python), gem(Ruby)。

一般来说,大部分语言的包管理系统次要都集中在拉取包和依赖。然而本文介绍的 go mod 还有更加丰盛的性能和设计指标。这些个性和下面说的那些包管理系统或多或少有一些不同,至多能够叫做「版本化的、有依赖的模块治理」。


Go 版本治理的次要指标

  • 晋升应用内部 module 的体验。
  • 更加准确的治理咱们的代码和模块。
  • 稳固的构建 reproducible builds
  • 标记应用的其余模块(甚至能够记录在最终的二进制中)。

背景:模块和包 module and package

  • 模块 (module) 是一堆有雷同前缀的包 (package) 的汇合。

    • module 有版本号(并且是有逻辑上的意义的)。
    • package 没有版本号。
    • go mod 是针对的 module
  • 模块的命名,一般来说是 organization/modue_name 的形式。

    • 例如 google.golang.org/grpc

背景:Go 程序员已经的问题

  1. 没有官网的依赖模块管理工具。
  2. 无奈做到 reproducible builds
  3. 次要工具 go get 忽视了用 git tag 的版本号。
  4. GOPATH 解决不了同一模块的多个版本需要。
  5. 无奈在 GOPATH 之外写代码。

背景:go get 的问题

  • 用该工具拉取代码,太新 太旧

    • 如果本地没有这个库,会去拉取最新的。
    • 如果本地有这个库,则不会更新,太旧。
  • Go 程序员偏向于动不动就 go get -u
  • 每次拉取最新的模块导致的不稳固构建(low-fidelity builds)。
  • 被墙且不不便用全局代理(这个是被 go env proxy 解决的,与 module 无关)。

没有 go mod 期间的操作

Makefile 外面间接写 go get (甚至 go get -u)。

bin:
  go get ...
  go get ...@...
    go build project_path/app_name

这意味着编译的时候须要有网络环境,同时,明天可能编译的代码,今天就有可能失败。

同时,如果批改了某一个内部模块,非得本人 folk 一个,并且放在 git 仓库中。


畏惧依赖模块变动

  • 放心通过 go get 获取的内部模块改变影响到本地工程的构建。
  • 所以我拷贝到本人的目录下了(不须要感到耻辱,因为 Google 有段时间也是这么干的)。
  • 拷贝代码中的门路怎么解决?

    • 在最长远期间,须要用脚本来解决被拷贝到工程中的内部模块代码。
    • 例如 xx/pkg -> local_dir/xx/pkg

从 2012 年到 2015 年,vendor 计划出炉。

  1. 主动改代码依赖 Goven
  2. 新工具,解决模块依赖 godep glide govendor
  3. 新版本(go 1.5)对 vendor 类计划的官网反对 go vender dep

各种 vendor 实现

  • 官网对 GOPATH 打补丁(约定 vendor 作为内部包的文件)。
  • 实际中,都把 vendor 一起放入了代码库中。
  • 须要有一个版本形容文件。

大部分 vendor 类工具做的事件:
下载指定版本的模块,实现拷贝。


govendor 计划的问题

  • 打补丁和降级不方面。
  • 代码仓库冗余且俊俏。
  • 计算依赖关系的时候,基于 GOPATH 和旧的 go get 算法(对于 +external 的模块)。

注: govendor 一类的工具只会去下载最新的版本。并不会计算和抉择特定的版本,仅仅是递归的将
原始工程中的依赖库全副下载下来而已。


问题解决:外围规定

  • 模块版本要做向下兼容。
  • 不兼容的做大版本解决(不同的大版本不是一个模块)

If an old package and a new package have the same import path,
the new package must be backwards-compatible with the old package.

反过来:如果接口不一样,那么就是不同的包。


然而你不须要保护新的 Git 仓库

  • 应用 git tag 来标记和保护。

    import "github.com/go-yaml/yaml/v2"
  • 查看模块的 tags

    $ go list -m -versions github.com/go-yaml/yaml/v2
    github.com/go-yaml/yaml/v2 v2.0.0 v2.1.0 v2.1.1 
    v2.2.0 v2.2.1 v2.2.2 v2.2.3 v2.2.4 v2.2.5 v2.2.6 
    v2.2.7 v2.2.8 v2.3.0 v2.4.0

思考:如果没有大版本辨别的问题

问题解决办法:

  • 大版本用于整体接口降级和变更。
  • 两头版本能够减少新接口。
  • 小版本修 bug。

参考这一篇极为具体的介绍(具体到啰嗦了)。


问题解决:版本抉择策略

抉择最旧 (小) 的版本而不是最新的。

  • 兼容性。(稳固构建方面的思考reproducible builds)
  • Dependency hell. (复杂度和包管理系统的实现上思考 efficient)

须要留神的是:

  • 最小抉择的是「版本 」,而不是更新日期!如果包代码没有应用这套规定,那么被当成0.0.0 版本(pseudo version)+ 拉取最新的 git 提交,例如 v0.0.0-20170915032832-14c0d48ead0c
  • 对于本地的工程,默认开始创立 go.mod 的时候,依然会去拉取所依赖的模块的最新的版本,见后文「实际操作」。

毁灭 GOPATH

  • Go 的版本环境、代码本体的版本和一个隐含的 GOPATH 下的货色决定了编译进去的二进制。
  • GOPATH 就像是暗藏在 Go 前面的一个尿袋。
  • 如果代码应用 go mod 来形容所有的依赖模块,那么它位于什么中央就无关紧要了。

两种之前的垃圾版本治理 / 构建策略

[示意图]

  • 如果以后环境 (GOPATH) 有这个库,那么不去动他。go get
  • go get -u 间接降级到最新。

下面两种都是 low-fidelity builds


最小版本依赖 Minimal version selection

这是作者 Russ Cox 反复强调并且引以为傲的一个概念。
首先咱们要弄清楚,「最小」的意思,是针对曾经写了 go.mod 的模块来说的。

如果本地工程依赖了三个我的项目 a b c,他们都是用了另外一个模块 libx,别离的版本是 1.1 1.2 和 1.3,假如以后 libx 的最新版本曾经是 1.7 了。
如果本地工程不依赖 libx,那么构建时,go mod 会抉择 libx 1.3 作为构建的版本。

可是,如果此时本地工程应用了 libx,此时,创立一个新的 go.mod 并且计算出依赖的话,外面的版本将会是 libx 1.7。同时,在这种场景下,a b c 和本地程序最终都将应用 libx 的 1.7 版本(在一个 Go 程序中,一个大版本的模块只可能有惟一的实体)。

所以,这个「最小版本依赖」,针对的是 a(或者 b c 这种曾经公布的模块)。

应用命令 go version -m ~/go/bin/gopls 能够查看生成的二进制应用的模块版本号。该个性容许你找到适合的环境来从新 build 二进制(用于验证)。


最小版本依赖算法相干的 4 个操作。

  1. 从构建 build list。
  2. 降级所有模块。
  3. 降级指定模块。
  4. 降级指定模块。

这项特色依赖于「模块的向后兼容性」。如果某个依赖库不听从该规定,那么无奈达到目标。


操作构建 build list

  • 找到固定的版本,退出到列表外面,剔掉旧的。
  • 用递归的做法来,很慢且在有环的状况下会死循环。
  • 图的遍历算法解决。



再来一次 go get

  • GOPATH 的状况下,go get 体现和之前一样。
  • go mod 的状况下,go get 是用下面提到的办法构建和更新。

思考

  • 被本地工程和依赖模块依赖的同一个模块的版本的抉择?最终 build 的时候的后果?
  • 批改了第三方模块,然而并不想提交到独立的 git 仓库问题。
  • 内网开发的问题。

另一篇:What is Software Engineering?

  • Go 的设计思路和决策都来自于对软件工程的思考,会均衡工夫和真正编程中实际。
  • gofmt 的例子:

    1. 要让代码更洁净,对立格调。
    2. 终止无用的格局相干的探讨。
    3. 重要:任何中央 copy 来的代码,最终展现进去的样子都是统一的。
    4. 重要:因为3.,所以代码能够被后续的工具解决批改(goimports gorename go fix)。

总得来说,Go 自身就是一门为了工程而打造的语言,他并没有什么相似学术上希图。在最开始的时候,甚至只是为网络服务器程序而设计的。

所以,它肯定是让真正的写理论我的项目工程的人爽的一门语言。


实际

环境变量(GO111MODULE="auto")以及「当前目录下是否有 go.mod 文件」决定了 go get 的行为。

对于本地工程间接依赖的版本,go mod tidy操作,他会去拉取「最新」的版本(依照优先级):

  1. 最新的稳固 non-prerelease 版本。
  2. 没有的话,最新的 prerelease 版本。
  3. 没有的嗯话,最新的 untagged 版本。

go.mod 中的标记 indirect,示意不是本地工程所间接依赖的,通常是由未应用 go.mod 的模块引入静来的(给人一种「编制外」的感觉)的模块。
一个 Go 的模块,有两个规范:

  1. 是否应用了 semantic verion
  2. 是否有 go.mod

按理说一个有格调的 Go 模块,都应该依照这两个规范来组织。然而,作为鼎鼎大名的,Google 自家人的 etcd-io/etcd,却始终都没有应用 go mod

这里有一篇吐槽 Etcd 应用 go module 的劫难

如果我的项目外面应用了 etcd 和 go mod,那么你将面临一大片被拽入到 go.modgithub.com/coreos/xxx // indirect

这是最新的(2021 年 3 月)的 alpha 版本:

v3.5.0-alpha.0 is an experimental release in order to:

test the "modularized" release process
enable integration testing with the modularized code of 3.5.x.

我试了一下:

go get github.com/coreos/etcd@v3.5.0-alpha.0
go get: github.com/coreos/etcd@v3.5.0-alpha.0: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v3

看来他们还有很远的路要走,在此之前,应用了 etcd 的敌人们,只可能通过在 go.mod 中退出 replace 来给版本依赖关系打补丁。


实际总结

教程中的 cheat sheet:

go mod init creates a new module, initializing the go.mod file that describes it.
go build, go test, and other package-building commands add new dependencies to go.mod as needed.
go list -m all prints the current module’s dependencies.
go get changes the required version of a dependency (or adds a new dependency).
go mod tidy removes unused dependencies.

须要特地留神的是,在 go 1.16 版本外面,go build / go test 等操作 并不 再去批改 go.mod 文件(下面的第二条)!这个行为和之前的版本差别挺大的,不过坦白说,我认为这个改变不赖,防止了很多主动执行的,让人无语的 go.mod 批改。

In Go 1.16, module-aware commands report an error after discovering a problem in go.mod or go.sum instead of attempting to fix the problem automatically. In most cases, the error message recommends a command to fix the problem.

内网开发实际

如果应用了 go.mod,并且还须要应用一些本地的库,那么能够通过在 go.mod 中应用 replace 来指明:

require hx v0.1.0
replace hx v0.1.0 => /Users/chuanqin/code/go_work/src/hx

同理,咱们对于内网 gitlab 的库,也能够应用同样的办法:

require hx v0.1.0
replace hx v0.1.0 => your_repository/hx
正文完
 0