[譯] Go 語言命令概覽

我偶爾會被人問到:「你爲何喜歡使用 Go 語言?」 我常常會提到的就是 go 工具命令,它是與語言一同存在的一部分。有一些命令 —— 好比 go fmtgo build —— 我天天都會用到,還有一些命令 —— 就像 go tool pprof —— 我用它們解決特定的問題。但在全部的場景下,我都很感謝 go 命令讓個人項目管理和維護變得更加容易。html

在這篇文章中,我但願提供一些關於我認爲最有用的命令的背景和上下文,更重要的是,解釋它們如何適應典型項目的工做流程。若是你剛剛接觸 Go 語言,我但願這是一個良好的開端。前端

若是你使用 Go 語言已經有一段時間,這篇文章可能不適合你,但一樣但願你能在這裏發現以前不瞭解的命令和參數😀node

本文中的信息是針對 Go 1.12 編寫的,並假設你正在開發一個 module-enabled 的項目。linux

安裝命令

這篇文章中,我將主要關注 go 命令這部分。但這裏也將提到一些不屬於 Go 1.12 標準發行版的內容。android

當你在 Go 1.12 版本下安裝命令時,你首先須要確保當前在 module-enabled 的目錄以外(我一般跳轉到 /tmp 目錄下)。以後你可使用 GO111MODULE=on go get 命令來安裝。例如:ios

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

這條命令將會下載相關的包和依賴項、構建可執行文件,並將它添加到你設置的 GOBIN 目錄下。若是你沒有顯式設定 GOBIN 目錄,可執行文件將會被添加到 GOPATH/bin 目錄下。但不管哪一種方式,你都應當確保系統路徑上有對應的目錄。git

注意:這個過程有些笨拙,但願能在未來的 Go 版本中有所改進。你能夠在 Issue 30515 跟蹤有關此問題的討論。github

查看環境信息

你可使用 go env 命令顯示當前 Go 工做環境。若是你在不熟悉的計算機上工做,這可能頗有用。golang

$ 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。例如:web

$ 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 1.11 版本,當你執行 go run 命令時,你能夠傳入包的路徑,就像咱們上面提到的那樣。這意味着再也不須要使用像 go run *.go 這樣包含通配符擴展的變通方法運行多個文件。我很是喜歡這個改進。

獲取依賴關係

假設你已經啓用了模塊,那當你運行 go rungo test 或者 go build 相似的命令時,全部外部依賴項將會自動(或遞歸)下載,以實現代碼中的 import 語句。默認狀況下,將下載依賴項的最新 tag,若是沒有可用的 tag,則使用最新提交的依賴項。

若是你事先知道須要特定版本的依賴項(而不是 Go 默認獲取的依賴項),則能夠在使用 go get 同時帶上相關版本號或 commit hash。例如:

$ 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 命令將返回大多數(但不是全部依賴項)的應答。你能夠在 Issue 27900 跟蹤這個問題。

若是你對分析應用程序的依賴關係或將其可視化感興趣,你可能還想查看 go mod graph 工具。在這裏有一個很棒的生成可視化依賴關係的教程和示例代碼。

最後,下載的依賴項存儲在位於 GOPATH/pkg/mod模塊緩存中。若是你須要清除模塊緩存,可使用 go clean 工具。但請注意:這將刪除計算機上全部項目的已下載依賴項。

$ go clean -modcache
複製代碼

重構代碼

你可能熟悉使用 gofmt 工具。它能夠自動格式化代碼,可是它也支持去重寫規則。你可使用它來幫助重構代碼。我將在下面證實這一點。

假設你有如下代碼,你但願將 foo 變量更改成 Foo,以便將其導出。

var foo int

func bar() {
    foo = 1
	fmt.Println("foo")
}
複製代碼

要實現這一點,你可使用 gofmt-r 參數實現重寫規則,-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 參數運行重寫規則,並先檢查差別,以確保代碼的更改如你所願。

讓咱們來看一個稍複雜的例子。假設你要更新代碼,以使用新的 Go 1.12 版本中攜帶的 strings.ReplaceAll() 方法替換掉以前的 strings.Replace() 方法。要進行此更改,你能夠運行:

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

在重寫規則中,單個小寫字符用做匹配任意表達式的通配符,這些被匹配到的表達式將會被替換。

查看 Go 文檔

你可使用 go doc 工具,在終端中查看標準庫的文檔。我常常在開發過程當中使用它來快速查詢某些東西 —— 好比特定功能的名稱或簽名。我以爲這比瀏覽網頁文檔更快,並且它能夠離線查閱。

$ go doc strings # 查看 string 包的簡略版文檔 
$ go doc -all strings # 查看 string 包的完整版文檔 
$ go doc strings.Replace # 查看 strings.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 函數的源碼
複製代碼

測試

運行測試

你可使用 go test 工具測試項目中的代碼,像這樣:

$ go test . # 運行當前目錄下的所有測試
$ go test ./... # 運行當前目錄和子目錄下的所有測試
$ go test ./foo/bar # 運行 ./foo/bar 目錄下的所有測試
複製代碼

我會在運行測試時啓用 Go 的 競爭檢測器,這能夠幫助我找到在實際使用中可能出現的一些數據競爭狀況。就像這樣:

$ go test -race ./...
複製代碼

這裏有很重要的一點要特別注意,啓用競爭檢測將增長測試的整體運行時間。所以,若是你常常在 TDD(測試驅動開發)工做流中運行測試,你可能會使用此方法進行預提交測試運行。

從 1.10 版本起,Go 在包級別 緩存測試結果。若是一個包在測試運行期間沒有發生改變,而且你正在使用相同的、可緩存的 go test 工具,那麼將會展現緩存的測試結果,並用 "(cached)" 標記註明。這對於加速大型代碼庫的測試運行很是有用。若是要強制測試徹底運行(並避免緩存),可使用 -count=1 參數,或使用 go clean 工具清除全部緩存的測試結果。

$ go test -count=1 ./... # 運行測試時繞過測試緩存
$ go clean -testcache # 刪除全部的測試結果緩存
複製代碼

注意:緩存的測試結果與構建結果被一同存儲在你的 GOCACHE 目錄中。若是你不肯定 GOCACHE 目錄在機器上的位置,請輸入 go env GOCACHE 檢查。

你可使用 -run 參數將 go test 限制爲只運行特定測試(和子測試)。-run 參數接受正則表達式,而且只運行具備與正則表達式匹配的名稱的測試。我喜歡將它與 -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
複製代碼

這將爲你提供全部測試文件的可導航列表,其中綠色代碼是被測試覆蓋到的,紅色代碼未被測試覆蓋。

若是你願意的話,能夠再進一步。設置 -covermode=count 參數,使覆蓋率配置文件記錄測試期間每條語句執行的確切次數

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

在瀏覽器中查看時,更頻繁執行的語句以更飽和的綠色陰影顯示,相似於:

注意:若是你在測試中使用了 t.Parallel() 命令,你應該用 -covermode=atomic 替換掉 -covermode=count 以確保計數準確。

最後,若是你沒有可用於查看覆蓋率配置文件的 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
複製代碼

要使用 stress 工具,首先須要爲要測試的特定包編譯測試二進制文件。你可使用 go test -c 命令。例如,爲當前目錄中的包建立測試二進制文件:

$ go test -c -o=/tmp/foo.test .
複製代碼

在這個例子中,測試二進制文件將輸出到 /tmp/foo.test。以後你可使用 stress 工具在該文件中執行特定測試,以下所示:

$ 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() 方法執行結果相同數量的進程(當前系統的 CPU 核數量的進程數)。

測試所有依賴關係

在爲發佈或部署構建可執行文件或公開發布代碼以前,你可能但願運行 go test all 命令:

$ go test all
複製代碼

這將對模塊中的全部包和依賴項運行測試 —— 包括對測試依賴項和必要的標準庫包的測試 —— 它能夠幫助驗證所使用的依賴項的確切版本是否互相兼容。可能須要至關長的時間才能運行,但測試結果能夠很好地緩存,所以任何未來的後續測試都會更快。若是你願意,你也可使用 go test -short all 跳過任何須要長時間運行的測試。

預提交檢查

格式化代碼

Go 提供了兩個工具 gofmtgo fmt 來根據 Go 約定自動格式化代碼。使用這些有助於保持代碼在文件和項目中保持一致,而且 —— 在提交代碼以前使用它們 —— 有助於在檢查文件版本之間的差別時減小干擾項。

我喜歡使用帶有如下參數的 gofmt 工具:

$ gofmt -w -s -d foo.go # 格式化 foo.go 文件
$ gofmt -w -s -d . # 遞歸格式化當前目錄和子目錄中的全部文件
複製代碼

在這些命令中,-w 參數指示工具重寫文件,-s 參數指示工具儘量的簡化代碼,-d 參數指示工具輸出變化的差別(由於我很想知道改變了什麼)。若是你只想顯示已更改文件的名稱而不是差別,則能夠將其替換爲 -l 參數。

注意:gofmt 命令以遞歸方式工做。若是你傳遞一個相似 ../cmd/foo的目錄,它將格式化目錄下的全部 .go 文件。

另外一種格式化工具 go fmt 是一個包裝器,它在指定的文件或目錄上調用 gofmt -l -w。你能夠像這樣使用它:

$ go fmt ./...
複製代碼

執行靜態分析

go vet 工具對你的代碼進行靜態分析,並對你可能是代碼錯誤但不被編譯器指出(語法正確)的東西提出警告。諸如沒法訪問的代碼,沒必要要的分配和格式錯誤的構建標記等問題。你能夠像這樣使用它:

$ go vet foo.go # 對 foo.go 文件進行靜態分析 
$ go vet . # 對當前目錄下的全部文件進行靜態分析
$ go vet ./... # 對當前目錄以及子目錄下的全部文件進行靜態分析
$ go vet ./foo/bar # 對 ./foo/bar 目錄下的全部文件進行靜態分析
複製代碼

go vet 在背後運行了許多不一樣的分析器,你能夠根據具體狀況禁用特定的分析器。例如,要禁用 composite 分析器,你可使用:

$ go vet -composites=false ./...
複製代碼

golang.org/x/tools 中有幾個實驗性的分析器,你可能想嘗試一下:

  • nilness:檢查多餘或不可能的零比較
  • shadow: 檢查可能的非預期變量陰影

若是要使用這些,則須要單獨安裝和運行它們。例如,若是安裝 nilness,你須要運行:

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

以後你能夠這樣使用:

$ go vet -vettool=$(which nilness) ./...
複製代碼

注:自 Go 1.10 版本起,go test 工具會在運行任何測試以前自動運行 go vet 檢查的一個小的、高可信度的子集。你能夠在運行測試時像這樣關閉此行爲:

$ go test -vet=off ./...
複製代碼

Linting 代碼

你可使用 golint 工具識別代碼中的樣式錯誤。與 go vet 不一樣,這與代碼的正確性無關,但能夠幫助你將代碼與 Effective Go 和 Go CodeReviewComments 中的樣式約定對齊。

它不是標準庫的一部分,你須要執行以下命令安裝:

$ cd /tmp
$ GO111MODULE=on go get golang.org/x/lint/golint
複製代碼

以後你能夠這樣運行:

$ golint foo.go # Lint foo.go 文件
$ golint . # Lint 當前目錄下的全部文件
$ golint ./... # Lint 當前目錄及其子目錄下的全部文件
$ golint ./foo/bar # Lint ./foo/bar 目錄下的全部文件
複製代碼

整理和驗證依賴關係

在你對代碼進行任何更改以前,我建議你運行如下兩個命令來整理和驗證你的依賴項:

$ go mod tidy
$ go mod verify
複製代碼

go mod tidy 命令將刪除你的 go.modgo.sum 文件中任何未使用的依賴項,並更新文件以包含全部可能的構建標記/系統/體系結構組合的依賴項(注意:go rungo testgo 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 build編譯指定的包(以及任何依賴包),而後調用連接器以生成可執行二進制文件,並將其輸出到 /tmp/foo

值得注意的是,從 Go 1.10 開始,go build 工具在構建緩存中被緩存。此緩存將在未來的構建中適當時刻重用,這能夠顯著加快總體構建時間。這種新的緩存行爲意味着「使用 go install 替換 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 功能之一。

默認狀況下,go build 將輸出適合你當前操做系統和體系結構的二進制文件。但它也支持交叉編譯,所以你能夠生成適合在不一樣機器上使用的二進制文件。若是你在一個操做系統上進行開發並在另外一個操做系統上進行部署,這將特別有用。

你能夠經過分別設置 GOOSGOARCH 環境變量來指定要爲其建立二進制文件的操做系統和體系結構。例如:

$ 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 . # 打印優化決策信息
複製代碼

在上面的例子中,我兩次使用了 -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 . # Disable optimizations and inlining
複製代碼

經過運行如下命令,你能夠看到可用連接參數列表:

$ go tool link -help
複製代碼

其中最著名的多是 -X 參數,它容許你將(字符串)值「插入」應用程序中的特定變量。這一般用於添加版本號或提交 hash。例如:

$ 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 . # 從二進制文件中刪除調試信息
複製代碼

注意:若是你須要優化可執行文件的大小,可能須要使用 upx 來壓縮它。詳細信息請參閱 這篇文章

診斷問題和優化

運行和比較基準

Go 能夠輕鬆的對代碼進行基準測試,這是一個很好的功能。若是你不熟悉編寫基準測試的通常過程,你能夠在這裏這裏閱讀優秀指南。

要運行基準測試,你須要使用 go test 工具,將 -bench 參數設置爲與你要執行的基準匹配的正則表達式。例如:

$ go test -bench=. ./... # 進行基準檢查和測試
$ go test -run=^$ -bench=. ./... # 只進行基準檢查,不測試
$ go test -run=^$ -bench=^BenchmarkFoo$ ./... # 只進行 BenchmarkFoo 的基準檢查,不進行測試
複製代碼

我幾乎老是使用 -benchmem 參數運行基準測試,這會在輸出中強制包含內存分配統計信息。

$ go test -bench=. -benchmem ./...
複製代碼

默認狀況下,每一個基準測試一次運行最少一秒。你可使用 -benchtime-count 參數來更改它:

$ go test -bench=. -benchtime=5s ./... # 每一個基準測試運行最少 5 秒
$ go test -bench=. -benchtime=500x ./... # 運行每一個基準測試 500 次
$ go test -bench=. -count=3 ./... # 每一個基準測試重複三次以上
複製代碼

若是你併發執行基準測試的代碼,則可使用 -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
# 作出改變
$ 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 參數,以下所示:

$ 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
複製代碼

這讓圖形更加「嘈雜」,若是你放大這個截圖,就能夠更清楚的看到和了解 CPU 使用的熱點位置。

分析和優化資源使用是一個龐大且複雜的問題,我在這裏只涉及到一點皮毛。若是你有興趣瞭解更多信息,我建議你閱讀如下文章:

另外一個能夠用來幫助你診斷問題的工具是運行時執行跟蹤器。這使你能夠了解 Go 如何建立和安排運行垃圾收集器時運行的 goroutine,以及有關阻止系統調用/網絡/同步操做的信息。

一樣,你能夠從測試或基準測試中生成跟蹤,或使用 net/http/pprof 爲你的 Web 應用程序建立和下載跟蹤。而後,你可使用 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.<pid>,你可使用:

$ GORACE="log_path=/tmp/race" /tmp/foo
複製代碼

管理依賴

你可使用 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 命令將依賴項升級到最新版本、調整爲特定 tag 或 hash 的版本,以下所示:

$ 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 文件的內容將被忽略,全部子依賴項將升級到最新的 minor/patch 版本,即便已經在 go.mod 中指定了不一樣的版本。

在升級或降級任何依賴項後,最好整理你的 modfiles。你可能還但願爲全部程序包運行測試以幫助檢查不兼容性。像這樣:

$ 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 rungo 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
複製代碼

你可使用same general technique導入只在你本身的文件系統上存在的包。若是你同時處理開發中的多個模塊,其中一個模塊依賴於另外一個模塊,則此功能很是有用。

注意:若是你不想使用 go mod edit 命令,你也能夠能夠手動編輯 go.mod 文件以進行這些更改。兩種方式都是可行的。

升級到新版本

go fix 工具最初於 2011 年發佈(當時仍在對 Go 的 API 進行按期更改),以幫助用戶自動更新舊代碼以與最新版的 Go 兼容。從那之後,Go 的兼容性承諾意味着若是你從 Go 1.x 版本升級到更新的 Go 1.x 版本,一切都應該正常工做,而且一般沒有必要使用 go fix

可是,在某些具體的問題上,go fix 的確起到了做用。你能夠經過運行 go tool fix -help 來查看命令概述。若是你決定在升級後須要運行 go fix,則應該運行如下命令,而後在提交以前檢查更改的差別。

$ go fix ./...
複製代碼

報告問題

若是你確信在 Go 的標準庫、工具和文檔中找到了未報告的問題,則可使用 Go bug 命令提出新的 Github issue。

$ go bug
複製代碼

這將會打開一個包含了系統信息和報告模板的 issue 填寫頁面。

速查表

2019-04-19 更新:@FedirFR 基於這篇文章製做了一個速查表。你能夠點擊這裏下載

若是你喜歡這篇文章,請不要忘記查看個人新書《如何用 Go 構建專業的 Web 應用程序》。

你能夠在 Twitter 上關注我 @ajmedwards

文中的全部代碼片斷都可在 MIT 許可證下自由使用。

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索