这是一篇关于GO module的介绍性文章。GO是由谷歌的三位工程师Robert Griesemer、Rob Pike和Ken Thompson设计开发的编程语言。它以易上手和其优异的性能,深受众多程序员的喜爱。但同时因为它不那么面向对象,也被深受面向对象影响的的其它程序员群众所嫌弃。

在一个GO项目里,我们通常会创建一些目录来组织代码,这些目录就被称作packages。package是可以通过“import”被引入使用的 。我们通过 go get 来安装某个package。这里我不展开对package的讨论。有兴趣的朋友可以读一下 “Understanding Golang Packages” 这篇文章( https://thenewstack.io/understanding-golang-packages/ )。

管理Package的版本和依赖是一件很头疼的事情,特别是当项目结构变得复杂,package引入量大的时候。比如,升级了一个package后,因为新版本不兼容当前项目代码导致项目没法正常build;又比如搞不清不同版本的产品中到底该使用哪个版本的package。在GO1.11之前,因为没有一个官方的package管理工具来解决这些问题。Gopher们(GO程序员的一个昵称) 自己开发了一些工具来解决这些问题。耳熟能详的有dep(https://golang.github.io/dep/ )以及vgo( https://github.com/golang/vgo/ )。

也许是因为看不下去了,GO1.11里出现了GO module这个东西。GO官方对module的解释是:

module是一个储存在一个文件树(a file free) 里的package的集合。这个文件树的根结点是一个叫 go.mod 的文件。这个文件定义了module的路径、根目录引入的package以及它们依赖的package的路径[1]。

这个文件树里还包含了一个重要信息,那就是版本。GO module的出现就是要悬壶济世,造福一方Gopher, 彻底解决package管理的各种问题。因为今天主要介绍如何快速上手,所以对module概念我在这里也就不展开了。感兴趣的朋友可以读一下 “A Gentle Introduction To Golang Modules“ 这篇文章( https://ukiahsmith.com/blog/a-gentle-introduction-to-golang-modules/ )。截止到这篇文章完成时,GO module还属于尝鲜版,等到GO1.13发布时,GO module就将正式成为GO的默认package管理工具。

那么Go module会给我们带来什么好的变化呢?

  • 我们不需要再依赖$GOPATH/src来创建项目,我们可以在此之外创建项目,构建module
  • 当我们运行 go build 或 go test 时,我们所需要的packages会以module的组织结构会被自动下载和安装
  • 我们可以通过 go mod 命令来方便的管理modules。比如:
    • 用 go mod init 来为项目创建一个module
    • 用 go mod download  将使用的modules进行本地化缓存
    • 用 go mod tidy 来自动添加缺失的modules或移除没有用的modules
    • 用 go mod vendor 在项目中做一个vendored 拷贝
    • 用 go mod edit 来编辑 module的定义
  • 我们还可以用 go list -m all 来查看所有已经安装的modules
  • 并且可以用 go list -m -version <module_name> 来查看特定module所有可用的版本信息

 

上手练习

那么接下来,我们就来花大概5分钟时间,掌握GO module的基本操作。掌握好这些操作对开启我们的GO module之旅至关重要,同时也能让我们在GO1.13发布时更从容。在开始之前,让我们先来了解一下要做的任务:

  1. 创建一个module
  2. 引入一个module
  3. 写一个单元测试
  4. 通过运行这个单元测试来安装引入的module
  5. 查看已安装的module及它们可用的所有版本信息
  6. 变更引入module的版本

让我们开始吧!

 

创建一个go module

首先,我们创建一个叫 hello.go 的文件,并编写以下代码:

package gogoCFB

func Hello() string {
    return "Hello, world."
}

执行 go mod init github.com/NBCFB/gogoCFB,创建module。这时,我们会看到 go.mod 文件被生成,打开该文件,我们会看到module的定义信息:

$ cat go.mod
module github.com/NBCFB/gogoCFB

go 1.12

 

引入一个module

我们引入 rsc.io/quote 这个module, 并改写 hello.go 中 Hello() 这个方法。quote.Hello()返回的也是“Hello, world.”这个字符串,所以Hello()在改写后返回的结果并无变化:

package gogoCFB

import (
    "rsc.io/quote"
)

func Hello() string {
    return quote.Hello()
}

 

写一个单元测试

接下来,我们为 hello.go 写一个叫 hello_test.go 的测试文件,并写一个TestHello的单元测试:

package gogoCFB

import (
    "testing"
)

func TestHello(t *testing.T) {
    expected := "Hello, world."
    if actual := Hello(); actual != expected {
        t.Errorf("expect %q, but got %q ", expected, actual)
    }
}

 

运行单元测试,安装 rsc.io/quote

接下来,我们执行 go test 来运行单元测试:

$ go test
PASS
ok github.com/NBCFB/gogoCFB 0.007s

单元测试通过了。与此同时,我们发现 go.mod 中多了 rsc.io/quote 这个module的定义,安装的版本是v1.5.2,其中 require表示我们的module github.com/NBCFB/gogoCFB 需要它:

$ cat go.mod
module github.com/NBCFB/gogoCFB

go 1.12

require rsc.io/quote v1.5.2

同时,在项目根目录,多了一个名为 go.sum 的文件。go.sum记录了我们安装的module的版本及对应的校验码(一种hash code)。go.sum的存在是为了确保对于我们下载的同一个版本的module内容的一致性。

 

查看module安装情况及 rsc.io/quote 可用版本信息

我们可以通过执行 go list -m all 来查看已经安装的modules。结果如下:

$ go list -m all
github.com/NBCFB/gogoCFB
golang.org/x/text v0.3.0
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0

我们可以看到,rsc.io/quote 依赖的modules golang.org/x/text 和 rsc.io/sampler 也被一并下载安装好了。我们接着执行 go list -m -versions rsc.io/quote 来查看所有 rsc.io/quote 的可用版本。结果如下:

$ go list -m -versions rsc.io/quote
rsc.io/quote v1.0.0 v1.1.0 v1.2.0 v1.2.1 v1.3.0 v1.4.0 v1.5.0 v1.5.1 v1.5.2 v1.5.3-pre1

结果显示最新版本是v1.5.2,不过还有一个pre-release版本 v1.5.3-pre1。

 

变更 rsc.io/quote 的版本

这时,我们将版本从v1.5.2变更到v1.5.3-pre1:

go get rsc.io/quote@v1.5.3-pre1

我们再此运行单元测试:

$ go test
PASS
ok github.com/NBCFB/gogoCFB 0.007s

单元测试依然OK。我们打开 go.mod 发现,rsc.io/quote 的版本已经更新:

$ cat go.mod
module github.com/NBCFB/gogoCFB

go 1.12

require (
    golang.org/x/text v0.3.0 // indirect
    rsc.io/quote v1.5.3-pre1
    rsc.io/sampler v1.3.0 // indirect
)

到这里,我们已经完成了GO module的基本技能练习。不过需要提醒的是,将项目从传统的$GOPATH/src下的package模式切换到Go module模式需要十分谨慎。如果项目规模比较大,结构比较复杂,最好采用愚公移山策略一点一点migrate。

 

Reference

[1] https://blog.golang.org/using-go-modules.

发表评论

电子邮件地址不会被公开。 必填项已用*标注