程序员社区

Go tools概述

偶尔有人问我:“你为什么喜欢Go?”而我经常提到的一件事是:作为go命令的一部分,与语言一起存在的实用工具。有一些我每天使用的工具,比如go fmt和go build,还有一些工具,比如go tool pprof,我只使用它们来解决特定的问题。但总的来说,我很欣赏这样一个事实,即它们使管理和维护我的项目变得更容易。

在这篇文章中,我希望提供一些我认为最有用的工具,更重要的是,解释它们如何适合典型项目的工作流程。如果你是新手,我希望它能给你一个好的开始。或者,如果你已经使用Go一段时间了,有些东西并不适用于你,希望你仍然会发现一个命令是你以前不知道的。

1、安装工具

在这篇文章中,我将主要关注go命令的一部分工具。要在使用Go module的同时安装模块,首先需要确保您在启用模块的目录之外(我通常只需要更改到/tmp)。然后可以使用GO111MODULE=on go get命令安装工具。例如:

$ cd /tmp
$ GO111MODULE=on go get golang.org/x/tools/cmd/stress

这将下载相关的包和依赖项,构建可执行文件并将其添加到GOBIN目录中。如果您没有明确设置GOBIN目录,那么可执行文件将被添加到您的GOPATH/bin文件夹中。无论采用哪种方式,您都应该确保有适当的目录在系统路径上。

2、查看环境信息

您可以使用go env工具来显示关于当前go运行环境的信息。如果您使用的是不熟悉的机器,那么这一点特别有用。

$ go env
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/alex/.cache/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/Alex/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/go"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build245740092=/tmp/go-build -gno-record-gcc-switches"

如果您对某个环境变量感兴趣,可以将它们作为参数传递给go env。例如:

$ go env GOPATH GOOS GOARCH
/home/Alex/go
linux
amd64

要显示所有go env变量和值的文档可以运行:

go help environment

开发工具

运行代码

在开发过程中,go run工具是测试代码的一种方便的方法。它本质上是一个编译代码的快捷方式,在/tmp目录中创建一个可执行的二进制文件,然后运行这个二进制文件。

$ go run .          #  在当前目录下运行包
$ go run ./cmd/foo  # 在“./cmd/foo”目录下运行
获取依赖

假设您已经启用了go module,当您使用go run(或go test或go build)时,任何外部依赖项都将自动(并递归地)下载以完成代码中的导入语句。默认情况下,将下载该依赖项的最新标记版本,如果没有可用的标记版本,则下载最新提交的依赖项。

如果您提前知道需要某个依赖项的特定版本(而不是Go默认获取的版本),您可以使用带有相关版本号的Go get或commit哈希值。例如:

$ go get github.com/foo/bar@v1.2.3
$ go get github.com/foo/bar@8e1b8d3

如果被获取的依赖项有一个go.mod文件,那么它的依赖项将不会在你的go.mod文件中。相反,如果您正在下载的依赖项没有go.mod文件,然后它的依赖将被列在你的go.mod文件里,旁边有一个//indirect注释。

这意味着你的go.mod文件不一定显示项目的所有依赖项。相反,你可以使用go list工具查看它们,如下所示:

$ go list -m all

有时你可能想知道为什么需要这个依赖?可以用go mod why命令来查看答案,它会显示从主模块中的包到给定依赖项的最短路径。例如:

$ go mod why -m golang.org/x/sys
# golang.org/x/sys
github.com/alexedwards/argon2id
golang.org/x/crypto/argon2
golang.org/x/sys/cpu

注意:go mod why命令将返回大多数(而不是全部)依赖。
如果您对应用程序依赖关系的分析或可视化感兴趣,那么您可能想要查看go mod graph工具。这里有一个生成可视化的教程和示例代码。
最后,下载的依赖项存储在位于GOPATH/pkg/mod的模块缓存中。如果您需要清除模块缓存,可以使用go clean工具。但是要注意:这将删除您机器上所有项目下载的依赖项。

$ go clean -modcache
代码重构

您可能很熟悉使用gofmt工具来自动格式化代码。但它也支持重写规则,您可以使用这些规则来帮助重构代码。我将演示。假设你有以下代码,你想把foo变量改为foo,这样它就可导出了。

var foo int

func bar() {
    foo = 1
    fmt.Println("foo")
}

要做到这一点,你可以使用带有-r标志的gofmt来实现重写规则,使用-d标志来显示更改的差异,使用-w标志来原地进行更改,如下所示:

$ gofmt -d -w -r 'foo -> Foo' .
-var foo int
+var Foo int

 func bar() {
-   foo = 1
+   Foo = 1
    fmt.Println("foo")
 }

注意比较这如何比查找和替换更智能?foo变量被改变了,但是fmt.Println()语句中的"foo"字符串没有改变。另一件需要注意的事情是,gofmt命令是递归工作的,因此上面的命令将作用在当前目录和子目录中的所有*.go文件。

如果您想使用这个功能,我建议首先运行不带-w标志的重写规则,先检查差异,以确保对代码的更改是您所期望的。让我们看一个稍微复杂一点的例子。假设您想要更新代码,使用新的strings.ReplaceAll()函数而不是strings.Replace()。要进行此更改,可以运行以下命令:

$ gofmt -w -r 'strings.Replace(a, b, c, -1) -> strings.ReplaceAll(a, b, c)' .

查看文档

您可以使用go doc工具通过终端查看标准库包的文档。我经常在开发过程中使用它来快速检查某些东西——比如特定函数的名称或签名。我发现它比浏览网络文档更快,而且它总是离线可用。

$ go doc strings            # 查看字符串包的简化文档
$ go doc -all strings       # 查看strings包的完整文档
$ go doc strings.Replace    # 查看字符串文档中Replace函数
$ go doc sql.DB             # 查看database/sql.DB 类型文档
$ go doc sql.DB.Query       # 查看database/sql.DB.Query方法的文档

还可以包含-src标志以显示相关的Go源代码。例如:

$ go doc -src strings.Replace   # 查看strings.Replace函数源码

测试Testing

执行测试

可以使用go test工具在项目中运行测试,如下所示:

$ go test .          # 运行当前目录中的所有测试
$ go test ./...      # 运行当前目录和子目录中的所有测试
$ go test ./foo/bar  # 运行./foo/bar目录中的所有测试

通常情况下,我在运行测试时启用了Go的竞争检测,它可以帮助收集在实际使用中可能发生的一些数据竞争。像这样:

$ go test -race ./...

值得注意的是,启用竞争检测将增加测试的总体运行时间。因此,如果您在TDD工作流中非常频繁地运行测试,您可能更喜欢将其保存下来,只用于预提交测试运行。

从1.10开始,Go在包级别缓存测试结果。如果一个包在测试运行时没有更新——并且您正在使用相同的、可缓存的go测试标志——那么将显示缓存的结果并在旁边标注(cached)。这对于加快大型代码库的测试运行时非常有帮助。如果您希望强制完整运行测试(并避免缓存),您可以使用-count=1标志,或使用go clean工具清除所有缓存的测试结果。

$ go test -count=1 ./...    # 运行测试时绕过测试缓存
$ go clean -testcache       # 删除所有缓存的测试结果

注意:缓存的测试结果与缓存的构建结果一起存储在GOCACHE目录中。如果不确定它在机器上的位置,请检查go env GOCACHE。

可以使用-run标志将go test限制为运行特定的测试(和子测试)。它接受一个正则表达式,并且只运行名称与正则表达式匹配的测试。我喜欢将其与-v标志结合使用以启用详细模式,这样运行的测试和子测试的名称就会显示出来。这是一个有用的方法,可以确保我没有用错正则表达式,运行的是正确的测试用例。

$ go test -v -run=^TestFooBar$ .          # 使用确切的名称TestFooBar运行测试
$ go test -v -run=^TestFoo .              # 运行名称以TestFoo开头的测试
$ go test -v -run=^TestFooBar$/^Baz$ .    #只运行TestFooBar测试的Baz子测试

需注意的两个标志是-short(可以用来跳过长时间运行的测试)和-failfast(在第一次失败后将停止运行剩下的测试)。注意-failfast将阻止缓存测试结果。

$ go test -short ./...      #跳过长时间运行的测试
$ go test -failfast ./...   # 在失败后不要运行后面的测试。
分析测试覆盖率

您可以在运行测试时使用-cover标志启用覆盖率分析。这将在每个包的输出中显示测试覆盖的代码的百分比,类似如下所示:

$ go test -cover ./...
ok      github.com/alexedwards/argon2id 0.467s  coverage: 78.6% of statements

你也可以使用-coverprofile标志生成覆盖配置文件,并通过go tool cover -html命令在你的浏览器中查看它,如下所示:

$ go test -coverprofile=/tmp/profile.out ./...
$ go tool cover -html=/tmp/profile.out
Go tools概述插图

这将为您提供所有测试文件的导航列表,其中测试覆盖的代码显示为绿色,未覆盖的代码显示为红色。

如果愿意,可以进一步设置-covermode=count标志,使覆盖率配置文件记录测试期间每个语句执行的确切次数。

$ go test -covermode=count -coverprofile=/tmp/profile.out ./...
$ go tool cover -html=/tmp/profile.out

当在浏览器中查看时,执行频率更高的语句会显示为更饱和的绿色阴影,类似如下:

Go tools概述插图1

最后,如果你没有可用的web浏览器来查看覆盖率配置文件,你可以在终端中通过命令查看按功能/方法划分的测试覆盖率:

$ go tool cover -func=/tmp/profile.out
github.com/alexedwards/argon2id/argon2id.go:77:     CreateHash      87.5%
github.com/alexedwards/argon2id/argon2id.go:96:     ComparePasswordAndHash  85.7%
...
压测

可以使用go test -count命令连续多次运行测试,如果希望检查零星或间歇故障,这可能很有用。例如:

$ go test -run=^TestFooBar$ -count=500 .

在本例中,TestFooBar测试将重复500次。但是需要注意的是,测试将以串行方式重复执行——即使它包含t.Parallel()指令。因此,如果测试的速度相对较慢,比如访问数据库、硬盘或互联网,那么运行大量测试可能会花费相当长的时间。

在这种情况下,您可能希望使用压力工具并行地多次重复相同的测试。你可以像安装stress:

$ cd /tmp
$ GO111MODULE=on go get golang.org/x/tools/cmd/stress

要使用压测工具,首先需要为要测试的特定包编译一个测试二进制文件。你可以使用go test -c命令。例如,要为当前目录中的包创建一个测试二进制文件:

$ go test -c -o=/tmp/foo.test .

在本例中,测试二进制文件将输出到/tmp/foo.test。然后,你可以使用压力工具在测试二进制文件中执行特定的测试,如下所示:

$ stress -p=4 /tmp/foo.test -test.run=^TestFooBar$
60 runs so far, 0 failures
120 runs so far, 0 failures
...

注意:在上面的例子中,我使用了-p标志来限制stress所使用的并行进程的数量为4。如果没有这个标志,工具将默认使用runtime.NumCPU()个进程数。

测试所有的依赖

在为发布或部署构建可执行文件或公开发布代码之前,您可能需要运行go test all命令:

$ go test all

这将运行模块中的所有包和所有依赖项的测试——包括测试依赖项和必要的标准库包——它可以帮助验证所使用的依赖项的确切版本彼此兼容。这可能需要相当长的时间来运行,但使用测试结果缓存,后续测试都应该会很快。如果愿意,还可以使用go test -short all来跳过任何长时间运行的测试。

代码提交前的检查

格式化代码

Go提供了两个工具:gofmt和Go fmt来根据Go约定自动格式化代码。使用这些工具有助于保持您的代码在文件和项目之间的一致性,并且——如果您在提交代码之前使用它们——有助于在检查文件版本之间的差异时减少干扰。
我喜欢使用gofmt工具带有以下标志:

$ gofmt -w -s -d foo.go  # 格式化foo.go文件
$ gofmt -w -s -d .       # 递归格式化当前目录和子目录中的所有文件

在这些命令中,-w标志指示工具在适当的地方重写文件,-s指示工具在可能的情况下对代码进行简化,而-d标志指示工具输出更改的差异(因为我很想知道更改了什么)。如果您想只显示更改后的文件的名称,而不是显示差异,您可以将此替换为-l标志。

注意:gofmt命令是递归工作的。如果你传递给它一个目录如.或者./cmd/foo它会格式化目录下所有的.go文件。

另一个格式化工具gofmt - tool是一个包装器,它在指定的文件或目录上调用gofmt -l -w。你可以这样使用它:

$ go fmt ./...

静态分析工具

go vet工具对你的代码进行静态分析,并提醒你代码中可能存在的错误,但编译器不会发现这些错误。诸如执行不到的代码、不必要的分配和格式错误的构建标记等问题。用法如下:

$ go vet foo.go     # 检查单个文件
$ go vet .          # 检查当前目录中所有文件
$ go vet ./...      # 检查当前目录和子目录中的所有文件
$ go vet ./foo/bar  # 检查./foo/bar目录所有文件

go vet运行了很多不同的分析器点击查看,你可以根据具体情况禁用特定的分析器。例如,要禁用composite分析器:

$ go vet -composites=false ./...

在golang.org/x/tools中有几个您可能想要尝试的实验分析器:nilness(检查冗余或不可能的nil比较)和shadow(检查可能无意中隐藏的变量)。如果您想要使用这些分析器,您需要分别安装并运行。例如,要安装nilness,你可以运行:

$ cd /tmp
$ GO111MODULE=on go get golang.org/x/tools/go/analysis/passes/nilness/cmd/nilness

然后你可以这样使用:

$ go vet -vettool=$(which nilness) ./...

注意:当使用-vettool参数时,它将只运行指定的分析器,所有其他go vet分析器将不会运行。
顺便提一下,从Go1.10开始,go test工具会在运行任何测试之前自动执行部分可靠的Go vet检查。您可以在运行测试时关闭该功能,如下所示:

$ go test -vet=off ./...

整理和验证代码依赖

在您提交任何代码修改之前,我建议运行以下两个命令来整理和验证代码依赖关系:

$ go mod tidy
$ go mod verify

go mod tidy命令将从go.mod和go.sum文件中删除任何未使用的依赖项,并更新包含所有可能的构建标签/OS/架构组合的依赖(注意:go run, go test, go build等都是“惰性命令”,只会获取当前构建环境所需的包)。每次提交代码之前运行此命令,在查看版本控制历史记录时可以更容易地确定,哪些代码导致添加或删除了哪些依赖项。

我还建议使用go mod verify命令来检查您的计算机上的依赖项是否在下载后意外(或有意)被更改,并且它们与您的go.sum文件中的加密哈希值是否匹配。运行此命令有助于确保所使用的依赖项与您所期望的完全一致。

构建和部署

要编译一个main包并创建一个可执行的二进制文件,你可以使用go build工具。通常,我将它与-o参数结合使用,让你显式地设置输出目录和二进制文件的名称:

$ go build -o=/tmp/foo .            # 在当前目录中编译包
$ go build -o=/tmp/foo ./cmd/foo    # 在./cmd/foo目录下编译

值得注意的是,从Go 1.10开始,Go build工具将缓存构建结果。这个缓存将在以后的构建中使用,这可以加快构建时间。如果你不确定构建缓存在哪里,你可以通过运行go env GOCACHE命令来查看:

$ go env GOCACHE
/home/alex/.cache/go-build

使用构建缓存有一个重要的注意事项——它不检测用cgo导入的C库的更改。因此,如果您的代码通过cgo导入了一个C库,并且自上次构建以来对它进行了更改,那么您将需要使用-a标志来强制重新构建所有包。或者,你可以使用go clean来清除缓存:

$ go build -a -o=/tmp/foo .     #  强制重新生成所有包
$ go clean -cache               # 从构建缓存中删除所有内容

注意:运行go clean -cache也会删除缓存的测试结果。
如果你对go build具体做什么感兴趣,你可以使用以下命令:

$ go list -deps . | sort -u     # 列出用于构建可执行文件的所有包
$ go build -a -x -o=/tmp/foo .  # 重新生成所有内容并显示正在运行的命令

最后,如果您在一个非main包里运行go build,它将在一个临时位置编译,结果将存储在构建缓存中。不生成可执行文件。

跨平台编译

默认情况下,go build将输出适合于当前操作系统和体系结构使用的二进制文件。但是它也支持交叉编译,因此您可以生成适合在不同机器上使用的二进制文件。如果您在一个操作系统上开发,在另一个操作系统上部署,那么这一点特别有用。

通过分别设置GOOS和GOARCH环境变量,可以指定要为其创建二进制文件的操作系统和体系结构。例如:

$ GOOS=linux GOARCH=amd64 go build -o=/tmp/linux_amd64/foo .
$ GOOS=windows GOARCH=amd64 go build -o=/tmp/windows_amd64/foo.exe .

要查看所有支持的操作系统/架构组合的列表,可以运行go tool dist list:

$ go tool dist list
aix/ppc64
android/386
android/amd64
android/arm
android/arm64
darwin/386
darwin/amd64
...

关于交叉编译的更深入的信息,我推荐阅读这篇文章。

编译器和连接器参数

在构建可执行文件时,您可以使用-gcflags标志来改变编译器的行为,并查看关于执行的更多信息。你可以通过下面的命令查看可用编译器参数的完整列表:

$ go tool compile -help

您可能会感兴趣的一个参数是-m,它触发打印关于在编译期间做出的优化信息。使用如下:

$ go build -gcflags="-m -m" -o=/tmp/foo . # 打印关于优化决策的信息

在上面的示例中,我使用了两次-m标志来表示想打印两层深度的决策信息。只使用一个就可以得到更简单的输出。

另外,从Go 1.10开始,编译器标志只适用于go build的特定包——在上面的例子中是当前目录中的包(用.表示)。如果你想打印所有包的优化决策,包括依赖项,可以使用下面的命令:

$ go build -gcflags="all=-m" -o=/tmp/foo .

从Go 1.11开始,您会发现调试优化的二进制文件比以前更容易了。但是,如果需要,您仍然可以使用标志-N来禁用优化,使用标志-l来禁用内联。例如:

$ go build -gcflags="all=-N -l" -o=/tmp/foo .  # 禁用优化和内联

您可能还对使用-s和-w参数,从二进制文件中剥离调试信息感兴趣。通常会使可执行文件大小减少25%。例如:

$ go build -ldflags="-s -w" -o=/tmp/foo .  # 从二进制文件中剥离调试信息

注意:如果二进制文件大小是你需要优化的目标,你可能会使用upx来压缩它。更多信息请看这篇文章。

赞(0) 打赏
未经允许不得转载:IDEA激活码 » Go tools概述

一个分享Java & Python知识的社区