跳到主要内容
  1. Skills/
  2. 后端编程/

如何使用go mod的方式来编写第一个 go helloworld

··字数 586
go howto

如果你在互联网上查找 go 语言的 helloworld 示例程序时,还看到别人的例子,让你如何配置 GOPATH 的话,基本可以说明那篇文章大概率是比较老的。

从 golang 1.11 版本以上, 就开始支持 go modules。当前使用 go modules 来管理工程的项目也越来越多。

那到底有什么好处呢?

不知你是否有过那种经历,当你想学习 go 语言,在互联网上搜索到一篇教程,按照其中的例子写了个 helloworld.go,结果使用 go buildgo run 怎么弄都跑不起来,编译器告诉你找不到相应文件。

是的,的确如此,go 程序的代码工程是需要 GOPATH 环境变量,并且要按照如下的目录结构来组织:

path of $GOPATH:
├─bin
├─pkg
├─src
│  └─helloworld
│      └─helloworld.go

是不是相当麻烦(因为你在网上找到的helloworld例子,不一定会告诉你需要这么做,或者假定你已经这么做)。

当有了 go modules 之后,你的目录可以这么组织:

path of everywhere:
├─go.mod
└─helloworld.go

😱😱😱WHAT??? 是的,就是这么简单。有没有一种 Deja vu 的感觉! go.modnpmpackage.json 作用类似,前端工程师一定非常熟悉。

下面将介绍如何使用编写一个 go mod 的 helloworld 程序。

假设你还没有安装go的编译环境 #

  • windows安装, scoop install go
  • macos 安装, brew install go

使用 windows 操作系统的同学,不知道什么叫scoop的,可以看本站相关文章。

你的第一个helloworld例子 #

进入到一个目录,比如 ~/Projects/go(你可以自己选择,这里只是为了示意方便)。

~/Projects/go
➜ mkdir hello

~/Projects/go
cd hello

~/Projects/go/hello
➜  go mod init world
go: creating new go.mod: module world

创建一个 go 文件 hellohello.go ,写上如下内容,并保存:

插播一下,这里使用 hellohello 作为文件名不是手误,而是故意的,为了说明一个问题,你也可以使用其它名称一做文件名,感兴趣请看 go build

package main

import "fmt"

func main() {
    fmt.Println("hello world 😀😀😀")
}

如果你机器上所安装的 go 环境是正常的,按照下面执行,结果是这样的:

~/Projects/go/hello
➜  ls
go.mod        hellohello.go

~/Projects/go/hello
➜  go run hellohello.go
hello world 😀😀😀

如果想把它编译成可执行程序,按照如下步骤,结果是:

~/Projects/go/hello
➜  go build hellohello.go

~/Projects/go/hello
➜  ls
go.mod        hellohello    hellohello.go

~/Projects/go/hello
➜  ./hellohello
hello world 😀😀😀

~/Projects/go/hello
➜  go build

~/Projects/go/hello
➜  ls
go.mod        hellohello    hellohello.go world

~/Projects/go/hello
➜  ./world
hello world 😀😀😀

这就是一个最简单的 go 程序。以上过程不小心把 go buildgo build hellohello.go 的差异也透露出来了,想知道这两者的差别,请看。

拆解 #

go mod init #

在上面例子,go mod init world,其中的 world 表示所定义的模块名称,模块名称也可以是一个路径,比如 world/youwu.today。这个名称将决定了后续包引用时的前缀,若此时你还未涉及包依赖引用的,请忽略。

如果你使用 go build 来生成可执行程序,那么这个模块名称就是可执行程序的名称。如果是带有路径的,如使用 world/youwu.today,那么生成的可执行程序名称为 youwu.today。 具体可看文章 go build

go.mod #

执行 go mod init world 命令,go 会生成一个依赖声明文件,文件名为 go.mod,内容如下:

module world

go 1.16

如果你执行 go mod init world/youwu.today,那么生成的 go.mod 内容长这样:

module world/youwu.today

go 1.16

规律尽在不言中。

如果你的程序引用了外部的go 程序库,使用 go mod tidy 或者 go get 包url路径 会帮助你将引用库的路径以及对应版本号记录到这个文件,不需要手动修改。

进阶 #

以上章节,都假设你是一位想学习 golang 编程的新手。 关于 go modules,只使用到了 go mod init 命令。若想看看 go mod 还有哪些功能,使用 go mod help 查看。

随着你的项目越来越复杂,会引用别人写好的库,这时你会需要 go mod tidy 来帮助管理。

GOPATH 真的就不产生作用了吗? #

不是的。原有显式设置 GOPATH 方式与当前 go module 是两种 go 代码工程管理方式。如果你选择了 go modules,那么那个讨厌的 GOPATH 就无须你时刻关注了,同时在多个 go module 工程目录来回切换,go toolchains 工具链就以 go.mod 这个文件来实现依赖管理。

GOPATH 在背后还是一直在起作用。

还是本文中这个例子

package main

import (
    "log"
)

func main() {
    log.Println("hello world 😀😀😀")
}

编译并运行:

~/Projects/go/examples/hello
➜  ./world
2021/06/17 05:47:05 hello world 😀😀😀

尝试把 GOPATH 改为当前目录:

~/Projects/go/examples/hello
➜  go env GOPATH
/Users/youwu.today/go # 原来默认的目录

~/Projects/go/examples/hello
GOPATH=$(PWD) # 改到当前目录

~/Projects/go/examples/hello
➜  go env GOPATH
$GOPATH/go.mod exists but should not # 提示 GOPATH 与 go.mod 不要在同一目录

~/Projects/go/examples/hello
cd .. && GOPATH=$(PWD) && go env GOPATH 
/Users/youwu.today/Projects/go/examples # 那么把 GOPATH 设置到上一级目录

~/Projects/go/examples
cd hello

~/Projects/go/examples/hello
➜  go build . && ./world
2021/06/17 05:54:48 hello world 😀😀😀

上述过程,是尝试把 GOPATH 指向 hello 目录,看会产生什么化学反应,结果是$GOPATH/go.mod exists but should not。为了两者都可以同时工作,那么把 GOPATH 指到上一级的 examples,暂时通过。回到 hello 目录重新编译看能否正常运行。答案是可以的。

接下来,重点来了。

package main

import (
    log "github.com/sirupsen/logrus"
)

func main() {
    log.Println("hello world 😀😀😀")
}

使用 golang 比较有人气的日志框架 logrus ,替换 go 标准 log 库(logrus 可以无逢替换 log ,也就是 API 兼容),然后编译运行看看。

~/Projects/go/examples/hello
➜  go mod tidy
go: downloading github.com/sirupsen/logrus v1.8.1
go: downloading golang.org/x/sys v0.0.0-20191026070338-33540a1f6037
go: downloading github.com/stretchr/testify v1.2.2
go: downloading github.com/davecgh/go-spew v1.1.1
go: downloading github.com/pmezard/go-difflib v1.0.0

~/Projects/go/examples/hello
➜  go build . && ./world
INFO[0000] hello world 😀😀😀

~/Projects/go/examples/hello
➜  go build . && ./world
INFO[0000] hello world 😀😀😀

~/Projects/go/examples/hello

记得使用 go mod tidy 下载依赖库,编译可以正常运行。go mod tidy时,终端提示下载了若干个库。返回到上级 examples ,多了一个 pkg 目录,GOPATH 起作用了。

~/Projects/go/examples/pkg 目录内容如下:
└─┬ .
  └─┬ mod
    ├─┬ golang.org
    │ └─┬ x
    │   └─┬ sys@v0.0.0-20191026070338-33540a1f6037
    │     ├── unix
    │     └─┬ windows
    ├─┬ cache
    │ └─┬ download
    │   └─┬ golang.org
    │     └─┬ x
    │       └─┬ sys
    │         └─┬ @v
    │           └── v0.0.0-20191026070338-33540a1f6037.zip
    └─┬ github.com
      ├─┬ stretchr
      │ └── testify@v1.2.2
      └─┬ sirupsen
        └── logrus@v1.8.1                                                  

因此可见,go mod tidy 会把当前工程所须的依赖包下载到 GOPATHpkg 目录缓存起来,这与之前纯 GOPATH 的方式没差别。(go 会为当前用户设置默认的GOPATH,即使你不显式去设置它。)

这一节花了这么长的篇幅说 GOPATH,主要还是为了提醒大家,因为不去主动管理,就容易把它忘记。当你在本地机器上开发了很长时间的 go 程序,说不定默认的 GOPATH pkg 下缓存了很多库,占用了比较大的空间,定期清理即可。当磁盘空间比较紧张时,说不定还可以挤点空间出来。