最近看了 Alex 写的 An Overview of Go's Tooling,对我很有帮助
加上立了一个翻译一篇技术文章的flag,所以尝试翻译一下
希望对正在用go的小伙伴有所帮助
原文地址:An Overview of Go’s Tooling - Alex Edwards


有时我会被问到“您为什么喜欢使用Go?” 我基本上会回答到这一点:“因为go语言有一些伴生的牛逼的好用的命令行工具!” 其中有一些我每天都在用(例如go fmt 和 go build) 而其他一些工具(例如go工具pprof)仅用于帮助解决特定问题。 它们都能让我的项目管理和维护变得更加容易。

在这篇文章中,我会提供一些有关我发现最有用的工具的背景知识和上下文,更重要的是,我将会说明如何将它们用在典型Go语言项目的开发流程中 如果您是Go的新手,希望这能对您有所帮助。

当然,如果您使用Go已有一段时间,觉得这些入门工具的使用介绍没啥卵用的话,您可以尝试快速浏览,可能会发现一些能帮你打开新天地的命令呦 :)

这篇文章的内容使用go语言的版本是1.12,使用的依赖管理工具是GO Modules

1. 工具的安装

在本文中,我将主要关注go命令中包含的工具。但是我也会提到一些不是标准Go 1.12版本的命令。

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

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

虽然这个过程有点笨拙,但有望在以后的Go版本中改进。你可以在这个 issue 下看到有关的讨论 。

2. 看一下当前的环境变量

go env
$ 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 help environment

3. 开发相关

  • 运行代码
go run
$ go run .          # Run the package in the current directory
$ go run ./cmd/foo  # Run the package in the ./cmd/foo directory
go run * .go
  • 拉取依赖

假设您已启用go moudle,则在使用go run(或进行测试或构建)时,将自动(递归)下载任何外部依赖项,以实现代码中的import语句。默认情况下,将下载依赖项的最新标记版本,或者如果没有可用的标记版本,则会拉该项目最新的commit。

如果您事先知道需要特定版本的依赖项(而不是默认情况下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 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 / modgo clean
$ go clean -modcache
  • 重构代码

您可能对使用gofmt工具自动设置代码格式很熟悉。但它也可以用来重构你写的代码。我会给出一个例子

fooFoo
var foo int

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

为此,可以将gofmt与-r标志一起使用以实现重写规则,-d标志可以显示更改的差异,而-w标志可以对更改进行适当的更改,如下所示

$ go fmt -d -w -r ‘foo -> Foo’ .
-var foo int
+var Foo int

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

注意到这比寻找和替换要更聪明吗?foo变量已被更改,但fmt中的“foo”字符串保持不变。需要注意的另一点是go fmt命令默认是递归工作,所以上面的命令将在所有的文件里运行。

如果要使用此功能,建议您先运行不带-w标志的重写规则,并首先检查diff以确保对代码所做的更改符合您的期望。

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

在重写规则中,单个小写字符充当与任意表达式匹配的通配符,并且这些表达式将在替换中被替换。

  • 查看golang的文档

您可以使用go doc工具来通过终端查看标准库软件包的文档。在开发过程中,我经常使用它来快速检查某些内容,例如查看特定功能的名称或签名。我发现这比浏览基于Web的文档更快,并且它在离线状态下也是可以用的

$ go doc strings            # View simplified documentation for the strings package
$ go doc -all strings       # View full documentation for the strings package
$ go doc strings.Replace    # View documentation for the strings.Replace function
$ go doc sql.DB             # View documentation for the database/sql.DB type
$ go doc sql.DB.Query       # View documentation for the database/sql.DB.Query method

您还可以用-src的参数以显示相关的Go源代码。例如:

$ go doc -src strings.Replace   # View the source code for the strings.Replace function

4. 测试相关

  • 运行测试

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

$ go test .          # Run all tests in the current directory
$ go test ./…      # Run all tests in the current directory and sub-directories
$ go test ./foo/bar  # Run all tests in the ./foo/bar directory

通常,我会在并发条件下运行测试,这可以帮助我们找到可能发生的一些数据竞争。像这样:

$ go test -race ./…

需要注意的是,打开并发开关会增加测试的总体运行时间。因此,如果您是面向测试开发的话,则可能更愿意将只在pre-commit的时候打开这个选项。

自从1.10版本开始,Go语言默认会开启包基本的测试缓存,如果一个包在两次测试之间没有发生改动,并且你跑测试的命令没有变化的话,测试的结果会显示出“(cached)”这对加快大型项目的测试非常用帮助。如果要强制测试完全运行(并避免缓存),则可以使用-count = 1标志,或者使用go clean工具清除所有缓存的测试结果

$ go test -count=1 ./…    # Bypass the test cache when running tests
$ go clean -testcache       # Delete all cached test results

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

您可以使用-run标志将go test限制为运行特定的测试。接受一个正则表达式作为参数,并且仅运行名称与该正则表达式匹配的测试。我喜欢将其与-v参数结合使用以启用详细模式,这可以在跑测试的时候看见测试函数的名字。这是一个非常有用的技巧,可以确保我没有搞砸正则表达式,并且正确的运行我期望的测试!

$ go test -v -run=^TestFooBar$ .          # Run the test with the exact name TestFooBar
$ go test -v -run=^TestFoo .              # Run tests whose names start with TestFoo
$ go test -v -run=^TestFooBar$/^Baz$ .    # Run the Baz subtest of the TestFooBar test only

-short 参数(可以用来跳过长时间运行的测试)和-failfast(可以在第一次失败后停止运行进一步的测试)这两个标记是很不错的。请注意,如果用可-failfast参数,go将不会缓存测试结果

$ go test -short ./…      # Skip long running tests
$ go test -failfast ./…   # Don’t run further tests after a failure.
  • 生成测试覆盖率

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

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

您还可以使用-coverprofile参数生成coverage配置文件 并使用go工具cover -html命令在Web浏览器中查看它,如下所示:

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


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

如果需要,可以更进一的设置-covermode = count参数,以使coverage配置文件记录测试期间每个语句执行的确切次数。

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

在浏览器中查看时,执行频率更高的语句以更加饱和的绿色阴影显示,类似于:

注意:如果您在何测试中用t.Parallel命令(并发的跑测试),则应使用-covermode = atomic 参数而不是-covermode = count标志,以确保计数准确

$ 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(开启了并发)指令,测试也会以串行方式重复进行。因此,如果您的代码中有大量的访问外部IO代码,比如数据库,硬盘,网络等等,则运行大量测试可能会花费很长时间。

在这种情况下,您可能想使用压力工具(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参数将压力测试中的并行进程数限制为4。如果没有该标志,该工具将默认使用等于runtime.NumCPU()的进程数。

  • 测试所有的依赖项

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

$ go test all

这将对模块中的所有软件包以及所有依赖项(包括测试测试依赖项和必要的标准库软件包)运行测试,并且可以帮助验证所使用的依赖项的确切版本是否相互兼容。这可能需要很长时间才能运行,但是结果缓存良好,因此以后的任何后续测试都应该更快。如果需要,还可以使用go test -short all跳过所有长时间运行的测试。

提交之前的检查(Pre-Commit Checks)

  • 格式化代码

Go提供了两种根据Go约定自动设置代码格式的工具:gofmt和go fmt。使用这些有助于使代码在文件和项目之间保持一致,并且-如果在提交代码之前使用它们-有助于减少检查文件版本之间的差异

我喜欢将gofmt工具与以下参数一起使用:

$ gofmt -w -s -d foo.go  # Format the foo.go file
$ gofmt -w -s -d .       # Recursively format all files in the current directory and sub-directories

在这些命令中,-w参数指示工具在适当的位置重写文件,-s参数表示工具在可能的情况下对代码进行简化,-d参数表示工具输出更改的差异(因为I’我很好奇,看看有什么变化)。如果只想显示已更改文件的名称而不是diff,可以将其替换为-l标志。

注意:gofmt命令以递归方式工作。如果将其传递给目录,例如。或./cmd/foo,它将格式化目录下的所有.go文件。

另一个格式化工具-go fmt-工具是包装程序,它实际上在指定的文件或目录上调用gofmt -l -w。您可以像这样使用它:

$ go fmt ./…
  • 执行静态分析

go vet工具会对您的代码进行静态分析,并警告您某些可能与您的代码不符但不会被编译器接收的事情。诸如无法访问的代码,不必要的分配和格式错误的构建标签之类的问题。您可以这样使用它:

$ go vet foo.go     # Vet the foo.go file
$ go vet .          # Vet all files in the current directory
$ go vet ./…      # Vet all files in the current directory and sub-directories
$ go vet ./foo/bar  # Vet all files in the ./foo/bar directory

在运行的背后,go vet运行着许多不同的分析器,这里会列出所有的分析器。您可以根据情况禁用特定的分析器。例如,要禁用复合分析仪,可以使用

$ go vet -composites=false ./…

这里还有一些你可能想用的但正处于试验状态的静态检查工具。例如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 将_只会_运行指定的分析器,这意味着将不会运行所有其他 go vet 分析器。

作为旁注,从 Go 1.10 开始,在运行任何测试之前,go test 工具将自动执行一个小的高可信的 go vet 测试子集。你可以在运行测试的时候关掉这种行为,就像这样:

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

这个工具并不是标准库的一部分,因此,需要像这样安装它:

$ cd /tmp
$ GO111MODULE=on go get golang.org/x/lint/golint

然后,就可以像下面一样运行它了:

$ golint foo.go     # Lint the foo.go file
$ golint .          # Lint all files in the current directory
$ golint ./…      # Lint all files in the current directory and sub-directories
$ golint ./foo/bar  # Lint all files in the ./foo/bar directory
  • 整理和验证依赖项

在提交代码任意改动之前,我建议你运行以下两个命令来整理和验证依赖项:

$ go mod tidy
$ go mod verify

go mod tidy 命令将会修剪 go.mod 和 go.sum 文件中未使用的依赖项,然后更新文件,以包含用于所有可能的构建标签/操作系统/架构组合的依赖项(注意:go run、go test、go build 等都是“懒惰的”,它们只会获取当前构建标签/操作系统/架构所需的包)。在每次提交前运行这个命令,会让你更容易在查看版本控制历史时,确定哪些代码更改负责添加或者删除哪些依赖项。

我还推荐使用 go mod verify 命令来检查计算机上的依赖项自下载后没有被意外(或者故意)更改,并且它们匹配 go.sum 文件中的加密哈希值。运行此命令有助于确保所使用的依赖项确确实实是你所期望的,并且以后对该提交的任何构建都能重现。

5. 构建和部署

  • 构建可执行文件

你可以使用 go build 工具来编译 main 包,并创建一个可执行的二进制文件。通常,我会将其与 -o 标志结合使用,明确设置输出目录和二进制文件的名字,就像这样:你可以使用 go build 工具来编译 main 包,并创建一个可执行的二进制文件。通常,我会将其与 -o 标志结合使用,明确设置输出目录和二进制文件的名字,就像这样:

$ go build -o=/tmp/foo .            # Compile the package in the current directory
$ go build -o=/tmp/foo ./cmd/foo    # Compile the package in the ./cmd/foo directory

在这些示例中,go build 将会_编译特定的包(和任何依赖包),然后调用链接器_来生成一个可执行的二进制文件,并将其输出到 /tmp/foo 值得注意的是,从 Go 1.10 开始,go build 工具会将构建输出缓存到_ 构建缓存 _中。这缓存的输出将在未来的构建中的适当时机重新使用。这就可以显著加快整体的构建时间。这种新的缓存行为意味着,“相比 go build,宁可 go install” 的“ 古训 ”不再适用。 如果不确定构建缓存位于何处,那么可以运行 go env GOCACHE 命令来检查:

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

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

$ go build -a -o=/tmp/foo .     # Force all packages to be rebuilt
$ go clean -cache               # Remove everything from the build cache

注意:运行 go clean -cache 将也会删除缓存的测试结果。 如果你对 go build 的幕后操作感兴趣,那么可能会想要使用以下命令:

$ go list -deps . | sort -u     # List all packages that are used to build the executable
$ go build -a -x -o=/tmp/foo .  # Rebuild everything and show the commands that are run

最后,如果在非 main 包上运行 go build 命令,那么将会在临时位置编译这个包,并且结果将会被存储在构建缓存中。不会生成任何可执行文件。

  • 交叉编译 这是我最喜欢的 Go 功能之一。 默认情况下,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
…

提示:可以使用 Go 的交叉编译来 创建 WebAssembly 二进制文件 。 有关交叉编译的更深入的信息,推荐阅读 这篇优秀的博文 。

  • 使用编译器和链接器参数 在构建自己的可执行文件时,可以使用 -gcflags 标志来更改编译器的行为,并查看执行的详细信息。可以通过运行以下命令,查看可用编译器标志的完整列表:
$ go tool compile -help

其中一个你可能会感兴趣的标志是 -m,带上了这个标志会触发打印编译期间所进行的优化决策。可以这样使用它:

$ go build -gcflags=“-m -m” -o=/tmp/foo . # Print information about optimization decisions

在上面的例子中,我用了 -m 标志两次,表示我想要打印量级深度的决策信息。你可以通过使用一次来获得简单点的输出。 此外,从 Go 1.10 开始,编译器标志只适用于传给 go build 的指定包,在上面的例子中就是当前目录下的包(由 . 表示)。如果你想打印所有包的优化决策(包括依赖项),那么可以用这个命令替代:

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

$ go build -gcflags=“all=-N -l” -o=/tmp/foo .  # Disable optimizations and inlining

可以运行以下命令来查看可用的链接器标志列表:

$ go tool link -help

其中,可能最有名的就是 -X 标志了,它允许你将一个(字符串)值“烧入”应用里指定的变量。这通常用于 添加版本号或者提交哈希 。例如:

$ go build -ldflags=“-X main.version=1.2.3” -o=/tmp/foo .

更多关于 -X 标志和示例代码的信息,请参阅 这个 StackOverflow 问题 和本文以及 这篇文章 。 你可能还有兴趣使用 -s 和 -w 标志来删除二进制文件中的调试信息。这通常会让生成的二进制文件减少 25% 的大小。例如:

$ go build -ldflags=“-s -w” -o=/tmp/foo .  # Strip debug information from the binary

注意:如果需要优化二进制文件大小,那么或许会想要使用 upx 来压缩它。更多信息,请参阅 这篇文章 。

7. 诊断问题和优化

$ go test -bench=. ./…                        # Run all benchmarks and tests
$ go test -run=^$ -bench=. ./…                # Run all benchmarks (and no tests)
$ go test -run=^$ -bench=^BenchmarkFoo$ ./…   # Run only the BenchmarkFoo benchmark (and no tests)

我几乎总用 -benchmem 标志来运行基准测试,这会强制输出内存分配统计信息。

$  go test -bench=. -benchmem ./…

默认情况下,每个基准测试都会运行至少一秒,并且只运行一次。可以使用 -benchtime 和 -count 标志来更改此行为。

$ go test -bench=. -benchtime=5s ./…       # Run each benchmark test for at least 5 seconds
$ go test -bench=. -benchtime=500x ./…     # Run each benchmark test for exactly 500 iterations
$ go test -bench=. -count=3 ./…            # Repeat each benchmark test 3 times over

如果进行基准测试的代码使用了并发,那么,可以使用 -cpu 标志来查看更改 GOMAXPROCS 值对性能产生的影响(实际上,是可以同时执行 Go 代码的 OS 线程数)。例如,将 GOMAXPROCS 设置为 1,4 和 8,运行基准测试:

$ go test -bench=. -cpu=1,4,8 ./…

要比较基准测试之间的变动,你可能需要使用 benchcmp 工具。这个工具不属于标准的 go 命令,所以需要像这样安装它:

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

然后,可以像这样使用它:

$ go test -run=^$ -bench=. -benchmem ./... > /tmp/old.txt
# make changes
$ go test -run=^$ -bench=. -benchmem ./… > /tmp/new.txt
$ benchcmp /tmp/old.txt /tmp/new.txt
benchmark              old ns/op     new ns/op     delta
BenchmarkExample-8     21234         5510          -74.05%

benchmark              old allocs     new allocs     delta
BenchmarkExample-8     17             11             -35.29%

benchmark              old bytes     new bytes     delta
BenchmarkExample-8     8240          3808          -53.79%
  • 分析和跟踪 Go 可以让你创建关于 CPU 使用、内存使用、goroutine 阻塞和互斥锁争用的诊断性程序剖析文件。你可以使用它们来深入挖掘应用,以确切了解应用是如何使用(或者等待)资源的。 有三种生成程序剖析文件的方法:
  • 如果是 web 应用,那么可以导入 net/http/pprof 包。这个操作将向 http.DefaultServeMux 注册一些处理函数。这样,你就可以为运行中的应用生成并下载程序剖析文件了。 这篇文章 对其进行了很好的解释,并且提供了一些示例代码。
  • 对于其他类型的应用,则可以使用 pprof.StartCPUProfile() 和 pprof.WriteHeapProfile() 函数来生成运行中的程序剖析文件。 runtime/pprof 文档中有一些示例代码可供参考。
  • 或者,你可以在运行基准测试或者基础测试的时候生成程序剖析文件,只需像这样使用各种 -***profile 标志即可:
$ go test -run=^$ -bench=^BenchmarkFoo$ -cpuprofile=/tmp/cpuprofile.out .
$ go test -run=^$ -bench=^BenchmarkFoo$ -memprofile=/tmp/memprofile.out .
$ go test -run=^$ -bench=^BenchmarkFoo$ -blockprofile=/tmp/blockprofile.out .
$ go test -run=^$ -bench=^BenchmarkFoo$ -mutexprofile=/tmp/mutexprofile.out .

注意:运行基准测试或者基础测试的时候使用 -***profile 参数,当前目录下会生成一个测试二进制文件。如果你想将其输出到另一个地方,那么应该使用 -o 标志,就像这样:

1
$ go test -run=^$ -bench=^BenchmarkFoo$ -o=/tmp/foo.test -cpuprofile=/tmp/cpuprofile.out .

无论使用何种方式来生成程序剖析文件,当启用了程序剖析,Go 程序每秒将停止大约 100 次,并在停止的时刻创建程序快照。根据这些 样本 生成一份程序剖析文件,然后就可以使用 pprof 文件进行分析了。 我最喜欢的检查剖析文件的方式是,使用 go tool pprof -http 命令来将其在 web 浏览器中打开。例如:

$ go tool pprof -http=:5000 /tmp/cpuprofile.out


这个操作将会默认显示一个_图表_,展示了应用在采样情况下的执行树。这让你可以快速了解任何资源的使用“热点”。在上面的图中,我们可以看到,CPU 使用率方面的热点是来源于 ioutil.ReadFile() 的两个系统调用。 你还可以导航到剖析文件的其他_视图_,包括函数和源代码的最高使用情况。

如果信息量太大,那么,或许你会想要使用 —nodefraction 标志,忽略小于样本一定百分比的节点。例如,要忽略那些少于样本 10% 的节点,可以这样运行 pprof:

$ go tool pprof —nodefraction=0.1 -http=:5000 /tmp/cpuprofile.out


另一个可以用来协助诊断问题的工具是运行时执行跟踪器。它可以让你了解 Go 是如何创建和调度 goroutine 运行的,垃圾收集器何时运行,以及关于阻塞系统调用/网络/sync 操作的信息。 同样的,你也可以从基础测试和基准测试中生成跟踪文件,或者使用 net/http/pprof 来创建并下载应用的跟踪文件。然后,就可以使用 go tool trace,像这样在 web 浏览器中查看输出了:

$ go test -run=^$ -bench=^BenchmarkFoo$ -trace=/tmp/trace.out .
$ go tool trace /tmp/trace.out

重要提示:目前,只能在 Chrome 或者 Chromium 中查看。

关于 Go 的执行跟踪器以及如何解释输出的更多信息,请看 Rhys Hiltner 的 dotGo 2016 演讲 和这篇 优秀的博客 。

  • 检查竞争条件

我前面谈过使用 go test -race,在测试过程中启用 Go 的竞争检测器。但是,你也可以在构建可执行文件的过程中,为运行中的程序启用它,就像这样:

$ go build -race -o=/tmp/foo .

非常值得注意的是,启用了竞争检测器的二进制文件将比正常情况下使用更多的 CPU 和内存。因此,在正常情况下,构建用于生产环境的二进制文件时,不应该使用 -race 标志。 但是,你可能希望在服务器池中的某台服务器上面部署启用了竞争检测器的二进制文件。或者用它来跟踪可疑的竞争条件,方法是使用负载测试工具,对启用了竞争检测器的二进制文件并发施压。 默认情况下,在二进制文件运行过程中,如果检测到了竞争,那么会对 stderr 写入一条日志。如果必要,你可以通过使用 GORACE 环境变量来改变这种行为。例如,要运行位于 /tmp/foo 的二进制文件,并将竞争日志写到 /tmp/race.,则可以这样:

$ GORACE=“log_path=/tmp/race” /tmp/foo

8. 管理依赖关系

可以使用 go list 工具来检查指定的依赖项是否有更新版本,如下所示:

$ go list -m -u github.com/alecthomas/chroma
github.com/alecthomas/chroma v0.6.2 [v0.6.3]

这将输出当前使用的依赖项的名字和版本,如果存在较新的版本,那么后面会跟着方括号 [],里面是最新的版本。还可以使用 go list 来检查所有依赖项(和子依赖项)的更新。如下所示:

$ go list -m -u all

你可以升级(或者降级)一个依赖项至最新的版本,只需像这样在 go get 命令后指定发布标记或者提交哈希即可:

$ go get github.com/foo/bar@latest
$ go get github.com/foo/bar@v1.2.3
$ go get github.com/foo/bar@7e0369f

如果你正在更新的依赖项带有 go.mod 文件,那么,根据这份 go.mod 文件的信息,如果有需要的话,还将下载子依赖项的更新。如果使用 go get -u 标志,那么将忽略 go.mod 文件的内容,然后所有子依赖项将会被升级至最新的次要或者补丁版本(即使 go.mod 指定了不同的版本。)。 升级或者降级任何依赖项后,最好整理你的 mod 文件。可能你还会希望运行所有包的测试,从而检查兼容性。就像这样:

$ go mod tidy
$ go test all

有时,你可能想使用本地版本的依赖项(例如,在上游合并补丁之前,需要使用本地分支)。为此,可以使用 go mod edit 命令,将 go.mod 文件中的依赖项替代为本地版本。例如:

$ go mod edit -replace=github.com/alexedwards/argon2id=/home/alex/code/argon2id

这将像这样为你的 go.mod 文件添加一个替代规则。然后。未来的 go run 和 go build 等操作将会使用本地版本。

File: go.mod
module alexedwards.net/example
—
go 1.12
require github.com/alexedwards/argon2id v0.0.0-20190109181859-24206601af6c

replace github.com/alexedwards/argon2id => /home/Alex/Projects/playground/argon2id

一旦不需要本地版本了,就可以用下面这个命令来移除替换规则:

$ go mod edit -dropreplace=github.com/alexedwards/argon2id

可以使用 同样的技巧 来导入_只_存在于你自己文件系统上的包。如果你同时处理开发中的多个模块,其中一个模块依赖另一个模块,那么这将会很有用。 注意:如果不想使用 go mod edit 命令,可以手动编辑 go.mod 文件来进行修改。无论哪种方式都能行得通。

9. 升级到新版本

Go fix 文件最初于 2011 年发布(当时对于 Go 的 API 仍有定期修改),用以帮助用户自动更新旧代码至与 Go 的新版本兼容。从此,Go 的 兼容性承诺 意味着,如果你从一个 Go 1.x 版本升级至更新的 1.x 版本,那么将一切正常,故而通常没有必要使用 go fix。 但是,它确实处理了一些非常具体的问题。可以运行 go tool fix -help 来查看这些问题的摘要。如果你决定升级之后想要或者需要运行 go fix,那么应该运行以下命令,然后在提交前检查更改的差异。

$ go fix ./…

10. 报告错误

如果你确信找到了 Go 的标注库、工具或者文档中未报告的错误,那么可以使用 go bug 命令来创建一个新的 Github issue。

$ go bug

这个命令将会打开一个浏览器窗口,其中打开了 issue 页面,并且与填充了系统信息和报告模板。

11. 备忘

2019-04-19 更新: @FedirFR 根据这篇文章制作了一份备忘。你可以 在这里下载 .