云原生系列Go语言篇-Go环境配置

本文来自正在规划的Go语言&云原生自我提升系列,欢迎关注后续文章。

每种编程语言都需要有开发环境,Go自然也不例外。如果读者之前已经写过Go程序,那么一定已经有环境了,但可能会漏掉一些最新技术和工具。如果是第一次在电脑上配置Go,也不必担心,Go及其支持工具非常简单。在配置好环境并验证后,我们会构建一个简单程序,学习几种构建和运行Go的方式,然后涉足一些简化Go开发的工具和技巧。

安装Go工具

要编写Go代码,首先要下载、安装Go开发工具。工具的最新版请见Go官方网站的下载页面。选择所用平台的下载文件并执行安装。Mac的安装包 .pkg及Windows的安装包 .msi可自动在相应的位置安装Go、移除旧的安装版本,并将Go二进制文件放到默认的执行路径下。

小贴士:如果读者是Mac开发者,可以通过brew install go命令来使用Homebrew安装Go。使用Chocolatey的Windows开发者可以通过choco install golang命令安装Go。

各类Linux和FreeBSD安装包是gzip压缩的tar文件,解压为名为go的目录。将该目录拷贝到 /usr/local ,再将 /usr/local/go/bin添加到$PATH,这样就可以访问go命令:

1
2
3
$ tar -C /usr/local -xzf go1.18.4.linux-amd64.tar.gz
$ echo 'export PATH=$PATH:/usr/local/go/bin' >> $HOME/.profile
$ source $HOME/.profile

注:Go程序编译为单个原生二进制,无需安装其它软件即可直接运行。这与Java、Python和JavaScript这样要求安装虚拟机才能运行程序的语言不同。使用单原生二进制利于Go编写语言的分发。

可以打开命令行输入如下命令验证环境配置是否正确:

1
$ go version

如果配置正确应该会打印出如下内容:

1
go version go1.18.4 darwin/arm64

这表明它是在macOS上Go版本为1.18.4。(Darwin是macOS内部的操作系统,arm64是基于ARM的64位芯片。)

排查 Go 安装的问题

如果显示的不是版本信息而是报错了,很可能是go不在执行路径下,或者是路径中有另一个名为go的程序。在macOS或其它类Unix系统中,可使用which go来查看所执行的go命令。如果未返回任何内容,则需要处理执行路径。

如果使用Linux或FreeBSD,则可能是在32位系统中安装了64位开发工具,或者是使用了错误的芯片架构的开发工具。

Go的工具

所有的Go开发工具都可使用go命令来访问。除了go version,还有编译器(go build)、代码格式化工具(go fmt)、依赖管理工具(go mod)、测试运行工具(go test)以及扫描常见代码错误的工具(go vet)等。在后面的文章中会进行详细讲解。现在我们通过第一个程序Hello World来快速查看最常用的一些工具。

注:自Go语言2009年发布以来,代码组织和依赖管理的方式发生过多次变化。因此读者可能在网上看到一些过时的建议,请注意甄别。

对于现在而言,Go语言开发的规则很简单:读者可以自由组织项目并放在任何位置。

注:在中国大陆通常在安装Go之后还应配置代理进行加速,比如使用七牛云的代理:

1
2
$ go env -w GO111MODULE=on
$ go env -w GOPROXY=https://goproxy.cn,direct

第一个Go程序

我们学习一些使用Go编写程序的基础。在编写的过程中会不断了解Go程序的基本组成。读者可能还不能掌握所有知识,但不必担心,后续的文章会有进一步的讲解。

创建Go模块

首先需要创建放程序的目录。命名为ch1。在命令行中进行创建:

1
2
$ mkdir ch1 
$ cd ch1

在该目录中,运行go mod init命令将该目录标记为一个Go模块:

1
2
$ go mod init hello_world
go: creating new go.mod: module hello_world

在第10章中会更深入地讨论模块,但现在读者只需知道Go项目被称为模块,每个模块在根目录下都有一个go.mod文件。运行go mod init会为我们创建该文件。go.mod中内容如下:

1
2
3
module hello_world

go 1.19

go.mod文件声明了模块的名称以及该模块所支持的最小Go版本,还有模块依赖的其它模块。可将其类比Python中的requirements.txt或Ruby中的Gemfile

不应直接编辑go.mod文件。而是使用go getgo mod tidy命令来管理对该文件的修改。

go build

下面开始打码!打开文本编辑器,键入如下内容,保存到ch1目录的hello.go文件中:

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
fmt.Println("Hello, world!")
}

注:这里的缩进存在问题,但稍后读者就会明白为什么会这样了。

我们来快速看下创建的Go文件中的各部分。第一行是包声明。在Go模块中,代码由一个或多个包组成。模块中的main包含启动Go程序的代码。

接下来的是导入语句。import语句列出文件中所引用的包。我们这里使用的是标准库中的fmt(读作fumpt)包。不同于其它语言,Go中只导入整包。不能限定包中的具体类型、函数、常量或变量。

所有的Go程序由main包中的main函数启动。我们使用func main() {声明该函数。和Java、JavaScript及C一样,Go使用花括号标记代码块的起始和结束。
函数体由一行代码组成。表明调用fmt包中的Println函数,参数为"Hello, world!"。有过编程经验的读者应该能猜到该函数的作用。

保存好文件后,回到命令行输入如下命令:

1
$ go build

它会在当前目录创建一个名为hello_world的可执行文件(Windows下为hello_world.exe)。运行该命令,会在屏幕中输出Hello, world!

1
2
$ ./hello_world
Hello, world!

二进制文件的名称与模块中声明一致。若想使用其它名称或是存储在其它位置,使用-o参数。例如,我们希望将代码编译为二进制文件hello,可使用:

1
$ go build -o hello

格式化代码

Go的一大主要设计目录是创建一种高效编写代码的语言。这表示语法要简洁、编译要快速。同时也导致Go的作者们重新考虑了代码格式化。大部分语言在代码的格式上留有巨大的灵活性。但Go不是。强制标准的格式让编写规范源代码的工具相当容易。它简化了编译器并允许创建有一些生成代码的智能工具。

还有另一个好处。开发人员过去在格式之争上浪费了大量的时间。因为Go语言定义了代码格式化的标准方式。Go开发者就无需争论花括号样式Tab还是空格。例如,Go使用tab进行缩进,并在起始花括号与声明或代码块起始命令不在同一行时会报语法错误。

注:很多Go开发人员觉得Go团队定义了一套标准格式,是为了避免争论后来才发现了工具的优势。但Go语言开发负责人Russ Cox曾公开说过更好的工具化才是最初的动机。

Go开发工具包含有一个命令go fmt,它会自动修复代码空白匹配标准格式。但是,它无法修复错误行中的花括号。运行命令如下:

1
2
$ go fmt ./...
hello.go

使用./…告诉Go工具对当前目录及其子目录下的所有文件应用该命令。我们在进一步学习Go的工具时它还会出现。

此时如果打开hello.go,会看到fmt.Println已经采用单制表符进行了缩进。

小贴士:在编译代码前请记得运行go fmt,至少在将修改的代码提交到仓库前要运行该命令。如果忘记,单独提交一次只运行了**go fmt ./…**的修改。对使用 IDE 的读者更简单的方法是对 VSCode 和 GoLand 这些编辑器提前做好配置,这样在每次保存代码时都会自动执行该命令。

分号插入规则

go fmt命令不会修复错误行的花括号问题,这背后的原因是分号插入规则。类似C或Java,Go需要在每条语句后添加分号。但Go开发者不需要自己添加这一分号。Go编译器按照一套极简单的规则自动插入,参见《Effective Go》中的描述:

如果新行之前最后的符号(token )是以下中的一个,词法分析器(lexer)会在其后添加一个分号:

  • 标识符(包含int和float64这样的保留字)
  • 基本字面量,如数字或字符串常量
  • 以下符号之一:break、continue、fallthrough、return、++、–、)或}

有了这一简单规则,可以了解到为会花括号放到错误的位置时会出错。如果这样写代码:

1
2
3
4
func main()
{
fmt.Println("Hello, world!")
}

分号插入规则发现在func main()中以“)”结尾,会将其变成:

1
2
3
4
func main();
{
fmt.Println("Hello, world!");
};

这样就不是有效的Go代码了。

分号插入规则及其对花括号的限制让Go编译器变得更简单、快速,同时也限制定了代码风格。这是睿智的做法。

go vet

有一批bug在代码语法上没有问题,但却很可能是错误的。go工具含有一个go vet命令来检测这类错误。我们修改程序来看它是如何检测到的。修改hello.go中的fmt.Println代码行如下:

1
fmt.Printf("Hello, %s!\n")

fmt.Printf非常类似于C、Java、Ruby及其它语言中的printf 。如果之前没见过printf ,这个函数的第一个参数是一个模板,剩余的参数是模板中占位符的值。

go vet可以检测到的是格式化模板中的每个占位符是否有对应的值。对修改后的代码运行go vet,它会发现其中的错误:

1
2
3
$ go vet ./...
# hello_world
./hello.go:6:2: fmt.Printf format %s reads arg #1, but call has 0 args

这里go vet发现了我们的bug,可以很简单地进行修复。修改hello.go中的第6行如下:

1
fmt.Printf("Hello, %s!\n", "world")

虽然go vet可以捕获一些觉的编程错误,但有些问题无法监测到。所幸有一些第三方的Go代码智能工具弥补了这一缺陷。其中一些知名的质量工具会在第11章中进行讲解。

小贴士:就像我们应该运行go fmt来确保代码的格式正确,运行go vet可扫描到有效代码中的一些bug。它是只是保障高质量代码的第一步。除了本系列文章中的建议,所有的Go开发人员都应当读读Effective GoGo语言文档中的代码审核评论来理解地道的Go代码应该是什么样的。

工具选择

虽然我们在这个小程序时只用到了普通的文本编辑器和go命令,但在写更大的项目时最好使用更高级的工具。大部分编辑器和IDE都有优秀的Go开发工具。如果读者没有个人偏好的工具,最主流的两个Go开发环境是VS Code和GoLand。

Visual Studio Code

如果希望使用免费的开发环境,微软所出的Visual Studio Code 是最佳的选择。自2015年发布以来, VS Code已成为最流行开源代码编辑器。它并没有自带对Go的支持,但可以通过在插件库中下载Go插件来变成Go开发环境。

VS Code对Go的支持依赖于第三方工具。包含Go开发工具、Delve调试器gopls,后者是由Go团队开发的Go语言服务端。在安装Go开发工具包时,Go插件会安装Delve和gopls。

注:什么是语言的服务端?这是一种API标准规范,用于实现智能编辑行为,如代码补全、质量检查或查找代码中所有使用变量或函数之处。可以阅读语言服务端协议来了解语言服务端及其能力的详细知识。

一旦配置好的工具,就可以打开项目使用了。图1-1中展示了项目窗口的外观。Getting started with VS Code Go视频中演示了 VS Code的Go插件。

VS Code

图1-1 Visual Studio Code

GoLand

GoLand是JetBrains专门为Go推出的IDE。虽然JetBrains是出了名的以Java为中心的工具,但丝毫不影响GoLand是一款优秀的Go开发环境。参见图1-2中的GoLand用户界面,它和 IntelliJ、PyCharm、RubyMine、WebStorm、Android Studio或其它JetBrains IDE都很像。它对Go的支持有重合名、语法高亮、代码补全、代码导航、文档弹窗、调试器、代码覆盖率等。除了支持Go,GoLand还包含JavaScript/HTML/CSS和SQL数据库工具。不同于VS Code,GoLand不需要用户下载其它工具就可正常使用。

GoLand窗口

图1-2 GoLand
如果已订阅了IntelliJ Ultimate(或是符合免费证书申请),可通过插件来添加对Go的支持。不然的话就需要付费使用GoLand,并没有免费版。

The Go Playground

Go开发还有一个重要的工具,但无需安装。访问The Go Playground 就可以看到类似图1-3中的界面。如果使用过irbnodepython这些命令行环境,会发现The Go Playground的使用体验非常类似。它可用于测试和分享简单程序。在窗口中输入代码,点击Run按钮运行代码。Format按钮对程序运行go fmt同时更新导入包。Share按钮创建一个唯一URL,可发送给其他人查看该程序或是你自己在未来回来查看代码(这些URL验证下来可保存很长时间,但不要把它用成代码仓库)。

Go Playground

图1-3 The Go Playground

在图1-4中可以看到,可以通过在每个文件之间添加-- filename.go --这样的代码来模拟多文件。甚至可以通过在文件名中添加/来模拟子目录,如-- subdir/my_code.go --

注意The Go Playground实际上是别人的电脑(具体来说是Google的电脑),所有自由度受限。它提供了几种Go版本(通常是当前发行版、上一版和最新的开发版)。只能发起对localhost的网络连接,运行太长或占用过多内存时会停止掉进程。如果程序中用到时间的话,需要考虑到时钟设置为November 10, 2009, 23:00:00 UTC(Go首次发布的日期)。虽然有这些限制,Go Playground对于测试新想法很有用,而且不需要在本地新建项目。在本系列文章中,读者会看到很多The Go Playground的链接,可直接运行代码,无需拷贝到本地。

警告:不要在其中使用敏感信息(如个人身份信息、密码或私钥)!如果点击Share按钮,这些信息会保存到Google的服务器,其他有分享链接的人都可以访问到。如果不慎这么干了,写一封邮件把URL和需要删除的原因发送给security@golang.org

Go Playground多文件

图1-4 The Go Playground支持多文件

Makefile

IDE很好使用,不易于自动化。现代软件开发依赖于反复的自动化构建,可在任何地方、任意时间由任何运行。这类工具是很好的软件工程化实践。它避免了一个历史问题,开发者不对构建问题负责并抛出那句经典台词:“在我的电脑上是好的!”实现的方式是通过脚本指定构建步骤。Go开发者使用make 进行解决。开发者通过它指定一系列构建程序所需要的操作以及它们执行的顺序。读者可能不熟悉make ,它1976年就在Unix系统上用于构建程序了。

ch1目录下创建一个文件Makefile ,添加如下内容:

1
2
3
4
5
6
7
8
9
10
11
.DEFAULT_GOAL := build

.PHONY:fmt vet build
fmt:
go fmt ./...

vet: fmt
go vet ./...

build: vet
go build

即使读者之前没用过Makefile ,也不难get到其作用。每个操作被称为一个target.DEFAULT_GOAL定义了在没有运行目标时所运行的操作。上例中运行的是build目标命令。接下来就是目标命令的定义,每个冒号(:)前为目标名。目标名后的单词 (build: vet中的vet ) 是在运行该目标之前所需要运行的目标命令。该目标所执行的任务放在之后的缩进行。.PHONYmake 命令在项目中有同名目录时不至于混淆。

运行make 命令后会输出:

1
2
3
4
$ make
go fmt ./...
go vet ./...
go build

通过这条命令,我们格式化了代码、检查了隐藏的错误并执行了编译。我们可以通过make vet来检查代码,或是只运行make fmt来完成格式化。看起来并没有多大改进 ,但构建前代码及检查代码(或是执行持续集成构建服务中的脚本)可以保障不丢失任何步骤。

Makefile的一个缺点是要求有些严格。必须使用制表符来缩进这些步骤。并且在Windows系统中没有原生支持。如果在Windows电脑上进行Go开发,需要先安装make。最简单的安装方式是先安装包管理工具如 Chocolatey,然后使用它来安装make (对于Chocolatey,命令为choco install make)。

如果想要学习更多有关Makefile的知识,Chase Lambert有一个很好的教程,但使用了一小部分C讲解了相关概念。

保持更新

像其它编程语言一样,Go开发工具会做定期更新。Go程序编译为单独的原生二进制文件,无需担心开发环境的升级引起已部署程序的崩溃。可以在同一台电脑或虚拟机上同时运行多个Go版本编译的程序。

从Go 1.2开始,大约每6个月会发布一个大版本。还有一些按需发布的bug和安全问题修复的小版本。因快速的开发周期以及Go团队对于向后兼容的承诺,Go的发行版倾向于递增而不是扩散的做法。Go兼容性承诺详细描述了Go团队如何规划避免引起崩溃的Go代码。其中说到不会从1开始Go版本不会出现任何语言和标准库层面的向后不兼容,除非涉及到bug或安全问题修复。这一承诺不适用于go命令。go命令的参数和功能有过向后不兼容的修改,且很有可能同样在未来出现。

准备好在电脑上更新Go开发工具时,Mac和Windows用户拥有快速的方法。安装了brewchocolatey的用户可以使用该工具升级。使用安装包安装的用户可在https://go.dev/dl/下载最新版本,它会删除掉老版本并安装新版本。

Linux和BSD用户需要下载最新版本,将老版本移至备份目录,解压新版本,然后再删除老版本:

1
2
3
$ mv /usr/local/go /usr/local/old-go
$ tar -C /usr/local -xzf go1.18.4.linux-amd64.tar.gz
$ rm -rf /usr/local/old-go

注:从技术上来说,无需移动已有安装,只需要删除它再安装新版本即可。但这不能保证“万无一失”。如果在安装新版本时出现问题,最好还有旧版本可以使用。

小结

本文中,我们学习了如何安装、配置Go开发环境。同时讨论了构建Go程序及保障代码质量的一些工具。至此我们已准备好了环境,下一篇文章中我们就开始探讨Go语言的内置类型以及如何声明变量。