Go语言工具简介 - Honeybadger


在本文中,Ayooluwa Isaiah总结了Go for Rubyists系列,并介绍了go工具。
工具通常被认为是Go生态系统中最强大的方面之一。go命令本身是本文将要讨论的许多工具的门户。通过学习此处讨论的每个工具,您在Go项目上工作时将变得更加高效,并快速,可靠地执行常见任务。
 
查看环境变量
go env命令用于显示有关当前Go环境的信息。这是此命令输出的示例:

GO111MODULE="on"
GOARCH=
"amd64"
GOBIN=
"/home/ayo/go/bin"
GOCACHE=
"/home/ayo/.cache/go-build"
GOENV=
"/home/ayo/.config/go/env"
GOEXE=
""
GOFLAGS=
""
GOHOSTARCH=
"amd64"
GOHOSTOS=
"linux"
GOINSECURE=
""
GONOPROXY=
""
GONOSUMDB=
""
GOOS=
"linux"
GOPATH=
"/home/ayo/go"
GOPRIVATE=
""
GOPROXY=
"https://proxy.golang.org,direct"
GOROOT=
"/usr/lib/go"
GOSUMDB=
"sum.golang.org"
GOTMPDIR=
""
GOTOOLDIR=
"/usr/lib/go/pkg/tool/linux_amd64"
GCCGO=
"gccgo"
AR=
"ar"
CC=
"gcc"
CXX=
"g++"
CGO_ENABLED=
"1"
GOMOD=
"/dev/null"
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-build145204762=/tmp/go-build -gno-record-gcc-switches"

如果要查看特定变量的值,可以将它们作为参数传递给go env命令:

$ go env GOPATH GOROOT GOBIN
/home/ayo/go
/usr/lib/go
/home/ayo/go/bin

可以使用以下命令访问每个变量的文档:

go help environmental

 
使用go run运行代码
假设您有一个main.go包含以下代码的文件,

package main
import "fmt"
func main() { fmt.Println(
"Welcome to Go!") }

您可以使用go run命令来运行它,正如我们在本系列中已经多次看到的那样:

$ go run main.go
Welcome to Go!

该go run命令将编译程序,在/tmp 目录中创建可执行文件,然后一步执行该二进制文件。如果要一次执行多个文件,可以将它们全部作为参数传递给go run:

$ go run main.go time.go input.go

或者,您可以使用通配符:

$ go run *.go

从Go v1.11开始,您还可以一次运行整个程序包:
$ go run ./foo # Run the package in the `foo` directory
$ go run .     # Run the package in the current directory
 

使用gofmt格式化代码
如果您一直在编写Go代码一段时间,就会知道对于如何格式化代码有严格的约定。gofmt命令对所有现有的Go代码强制执行这些约定。
上一节中显示的代码段格式不正确,因此让我们使用对其进行格式设置gofmt,如下所示:
$ gofmt main.go
package main

import "fmt"

func main() { fmt.Println(
"Welcome to Go!") }

这将格式化源文件中的代码,并将结果打印到标准输出中。如果要用格式化的输出覆盖源文件,则需要添加-w标志。

$ gofmt -w main.go

要递归格式化Go源文件(当前目录和子目录),请指定a.作为参数gofmt。

gofmt .

 
修复导入语句
必须先导入包,然后才能在代码中使用包。否则将失败,则代码将无法编译,并且将显示错误。给定main.go文件中的以下代码,
package main

func main() {
    fmt.Println("Welcome to Go!")
}

如果尝试执行该程序,应该看到以下错误:

$ go run main.go
# command-line-arguments
./main.go:4:2: undefined: fmt

在编译代码之前,fmt必须先导入软件包。您可以手动添加必要的代码,也可以使用goimports命令为您添加必要的import语句。

$ goimports main.go
package main

import "fmt"

func main() {
    fmt.Println(
"Welcome to Go!")
}

该命令还会删除不再引用的所有导入软件包,并以与gofmt相同的样式设置代码格式。因此,您也可以将其go imports 视为的替代品gofmt。

如果将编辑器设置为在保存Go源文件时运行,则goimports价值将变得显而易见。这样,您就不必担心在使用软件包之前导入软件包或导入不再需要的语句。保存文件后,它将自动为您完成。大多数代码编辑器都有某种插件或设置可对此提供帮助。
 
构建你的项目
要为您的程序生成可执行二进制文件,请使用go build命令。这将在当前目录中输出一个二进制文件:

$ go build
$ ./demo
Welcome to Go!

生成的二进制文件go build特定于您的操作系统体系结构,并且包含运行程序所需的所有内容。因此,您可以将其转移到具有相同体系结构的另一台计算机上,即使未安装Go,它也可以相同的方式运行。

如果要交叉编译非您自己的体系结构的二进制文件,您所需要做的就是 在运行命令之前更改GOOS和GOARCH环境变量的值go build。

例如,以下命令可用于为64位Windows计算机生成二进制文件:

$ GOOS=windows GOARCH=amd64 go build

要针对Linux,macOS,ARM,Web Assembly或其他目标进行编译,请参考Go文档 以查看GOOS和GOARCH可用的组合。
 
安装Go二进制文件
如果您希望能够从源目录外部运行,go install可替代go build。
假设您的main.go文件位于一个名为的目录中demo,以下命令将demo在您的$GOPATH/bin目录中创建一个二进制文件。
$ go install

在大多数计算机上$GOPATH应是$HOME/go。您可以使用以下go env命令进行检查 :
$ go env GOPATH /home/ayo/go

如果要列出 $GOPATH/bin目录下内,你会看到一个demo二进制文件
$ ls $GOPATH/bin
demo

这个二进制文件可以在你文件系统任何位置通过运行demo命令。但是仅当$GOPATH/bin目录已添加到您$PATH中时,此方法才起作用。
$ demo
Welcome to Go!

 
列出包的信息
默认调用会go list返回您当前所在目录或提供的包路径的导入路径的名称:
$ go list
github.com/ayoisaiah/demo
$ go list github.com/joho/godotenv
github.com/joho/godotenv

我们可以使用-f标志来自定义go list命令的输出,该标志使您可以执行Go模板,模板可以访问go工具的内部数据结构。例如,您可以使用以下命令列出的fmt名称:

$ go list -f "{{ .Name }}" fmt
fmt

它本身并不是很有趣,但是还有:您可以使用{{ .Imports }}模板打印软件包的所有依赖项。这是fmt包的输出:

$ go list -f "{{ .Imports }}" fmt
[errors internal/fmtsort io math os reflect strconv sync unicode/utf8]

或者,您可以列出软件包的完整传递依赖项集:

$ go list -f "{{ .Deps  }}" fmt
[errors internal/bytealg internal/cpu internal/fmtsort internal/oserror internal/poll internal/race internal/reflectlite internal/syscall/execenv internal/syscall/unix internal/testlog io math math/bits os reflect runtime runtime/internal/atomic runtime/internal/math runtime/internal/sys sort strconv sync sync/atomic syscall time unicode unicode/utf8 unsafe]

您还可以使用以下go list命令检查对依赖项和子依赖项的更新:

$ go list -m -u all

或者,检查对特定依赖项的更新:

$ go list -m -u go.mongodb.org/mongo-driver
go.mongodb.org/mongo-driver v1.1.1 [v1.4.0]

您可以使用该go list命令执行更多操作。可以在documentation查阅可能与该命令一起使用的标志和其他模板变量。
 
显示包或符号的文档
go doc命令将打印与由其参数标识的项目关联的文档注释。它接受零,一或两个参数。

要在当前目录中显示软件包的软件包文档,请使用不带任何参数的命令:

$ go doc

如果程序包是命令(main程序包),则除非-cmd提供了标志,否则输出中将省略导出符号的文档。

go doc通过将软件包的导入路径作为命令的参数传递,我们可以使用该命令查看任何软件包的文档:

$ go doc encoding/json
package json // import "encoding/json"

Package json implements encoding and decoding of JSON as defined in RFC
7159. The mapping between JSON and Go values is described in the
documentation for the Marshal and Unmarshal functions.
[truncated for brevity]

如果要查看包中特定方法的文档,只需将其作为第二个参数传递给go doc:

$ go doc encoding/json Marshal

下面是第二种命令参数,godoc将所有Go软件包(包括您下载的任何第三方依赖项)的文档显示为网页。运行该命令将默认在端口6060上启动Web服务器,但是您可以使用该-http标志更改地址。

$ godoc -http=:6060

 
执行静态分析
go vet命令可以帮助检测代码中可能未被编译器捕获的可疑结构。这些可能未必会阻止您的代码编译,但会影响代码质量,例如无法访问的代码,不必要的分配以及格式字符串参数格式错误。
$ go vet main.go # Run go vet on the `main.go` file
$ go vet .       # Run go vet on all the files in the current directory
$ go vet ./...   # Run go vet recursively on all the files in the current directory

该命令由几个分析器工具组成,这些工具在此处列出,每个工具都对文件执行不同的检查。
默认情况下,go vet执行命令时将执行所有检查。如果您只想执行特定的检查(忽略所有其他检查),则将分析器的名称作为标记包括在内,并将其设置为true。这是一个printf仅运行检查的示例 :

$ go vet -printf=true ./...

但是,将-printf=false标志传递给go vet将运行除printf以外的所有检查。

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

 
向项目添加依赖项
假设您启用了Go模块go run,则go build,或go install将下载实现程序中import语句所需的所有外部依赖项。默认情况下,如果没有可用的标记版本,则将下载最新的标记版本或最新的提交。
如果您需要下载依赖关系的特定版本,而不是默认情况下通过Go获取,则可以使用该go get命令。您可以指定特定版本或提交哈希:
$ go get github.com/joho/godotenv@v1.2.0 $ go get github.com/joho/godotenv@d6ee687

该方法可根据需要用于升级或降级依赖项。任何下载的依赖项都存储在位于的模块缓存中 $GOPATH/pkg/mod。您可以使用该go clean命令一次性清除所有项目的模块缓存:
$ go clean -modcache

 
使用Go模块
这是有效使用模块需要了解的命令的摘要:
  • go mod init 将初始化项目中的模块。
  • go mod tidy清理未使用的依赖项或添加缺失的依赖项。在确认对代码进行任何更改之前,请确保运行此命令。
  • go mod download 将所有模块下载到本地缓存。
  • go mod vendor将所有第三方依赖项复制到vendor项目根目录中的文件夹中。
  • go mod edit可用于将go.mod文件中的依赖项替换为本地或分支版本。例如,如果需要使用派生直到补丁在上游被合并,请使用以下代码:

go mod edit -replace=github.com/gizak/termui=github.com/ayoisaiah/termui

 

测试和基准测试代码
Go具有一个称为的内置测试命令go test和一个testing包,可以将其组合以提供简单但完整的单元测试体验。该test 工具还包括基准测试和代码覆盖率选项,可帮助您进一步分析代码。

让我们编写一个简单的测试来演示该test 工具的一些功能。修改main.go文件中的代码,如下所示:

package main

import "fmt"

func welcome() string {
    return
"Welcome!"
}

func main() {
    fmt.Println(welcome())
}

然后,将测试添加到同一目录中的单独文件main_test.go中:
package main

import "testing"

func TestWelcome(t *testing.T) {
    expected :=
"Welcome to Go!"
    str := welcome()
    if str != expected {
        t.Errorf(
"String was incorrect, got: %s, want: %s.", str, expected)
    }
}

如果在终端中运行go test,它将失败:

$ go test
--- FAIL: TestSum (0.00s)
    main_test.go:9: String was incorrect, got: Welcome!, want: Welcome to Go!.
FAIL
exit status 1
FAIL    github.com/ayoisaiah/demo   0.002s

我们可以通过修改文件中welcome 函数的返回值来使main.go通过测试。


func welcome() string {
    return "Welcome to Go!"
}

现在,测试应该成功通过:

$ go test
PASS
ok      github.com/ayoisaiah/demo   0.002s

如果您有许多具有测试功能的测试文件,但只想选择性地运行其中一些,则可以使用该-run标志。它接受一个正则表达式字符串以匹配要运行的测试函数:

$ go test -run=^TestWelcome$ . # Run top-level tests matching "TestWelcome"
$ go test -run= String .       # Run top-level tests matching
"String" such as "TestStringConcatenation"

您还应该知道以下测试标志,这些标志在测试Go程序时通常会派上用场:
  • -v标志启用详细模式,以便将测试的名称打印在输出中。
  • -short标志跳过长时间运行的测试。
  • -failfast一次失败测试后,该标志停止测试。
  • -count标志连续运行多次测试,这对于您要检查间歇性故障很有用。

 
代码覆盖率
要查看代码覆盖状态,请使用-cover标志,如下所示:

$ go test -cover
PASS
coverage: 50.0% of statements
ok      github.com/ayoisaiah/demo   0.002s

您还可以使用该-coverprofile标志生成coverage配置文件。这使您可以更详细地研究代码覆盖率结果:

$ go test -coverprofile=coverage.out

coverage.out运行上述命令后,您将在当前目录中找到一个文件。该文件中包含的信息可用于输出HTML文件,其中包含现有测试已覆盖的确切代码行。

$ go tool cover -html=coverage.out

运行此命令时,将弹出一个浏览器窗口,其中绿色的覆盖线和红色的覆盖线。
 
标杆管理
Go中的基准测试工具已被广泛认为是一种衡量Go代码性能的可靠方法。_test.go就像测试一样,基准放置在文件中。这是一个比较Go中不同字符串连接方法的性能的示例:

// main_test.go
package main

import (
   
"bytes"
   
"strings"
   
"testing"
)

var s1 =
"random"

const LIMIT = 1000

func BenchmarkConcatenationOperator(b *testing.B) {
    var q string
    for i := 0; i < b.N; i++ {
        for j := 0; j < LIMIT; j++ {
            q = q + s1
        }
        q =
""
    }
    b.ReportAllocs()
}

func BenchmarkStringBuilder(b *testing.B) {
    var q strings.Builder
    for i := 0; i < b.N; i++ {
        for j := 0; j < LIMIT; j++ {
            q.WriteString(s1)
        }
        q.Reset()
    }
    b.ReportAllocs()
}

func BenchmarkBytesBuffer(b *testing.B) {
    var q bytes.Buffer
    for i := 0; i < b.N; i++ {
        for j := 0; j < LIMIT; j++ {
            q.WriteString(s1)
        }
        q.Reset()
    }
    b.ReportAllocs()
}

func BenchmarkByteSlice(b *testing.B) {
    var q []byte
    for i := 0; i < b.N; i++ {
        for j := 0; j < LIMIT; j++ {
            q = append(q, s1...)
        }
        q = nil
    }
    b.ReportAllocs()
}

可以使用以下go test -bench=.命令调用此基准测试:

$ go test -bench=.
goos: linux
goarch: amd64
pkg: github.com/ayoisaiah/demo
BenchmarkConcatenationOperator-4        1718        655509 ns/op     3212609 B/op        999 allocs/op
BenchmarkStringBuilder-4              105122         11625 ns/op       21240 B/op         13 allocs/op
BenchmarkBytesBuffer-4                121896          9230 ns/op           0 B/op          0 allocs/op
BenchmarkByteSlice-4                  131947          9903 ns/op       21240 B/op         13 allocs/op
PASS
ok      github.com/ayoisaiah/demo   5.166s

如您所见,串联运算符是最慢的,每次操作为655509纳秒,而bytes.Buffer最快的则为9230纳秒。以这种方式编写基准测试是确定性能改进或可重复性下降的最佳方法。
 
检测竞争条件
go工具包含一个线程竞争探测器,可以通过-race选件激活 。这对于发现并发系统中的问题很有用,这可能导致崩溃和内存损坏。

$ go test -race ./...
$ go run -race main.go
$ go build -race ./...
$ go install -race ./...