偶爾有人問我:“你為什么喜歡Go?”而我經常提到的一件事是:作為go命令的一部分拧略,與語言一起存在的實用工具川无。有一些我每天使用的工具,比如go fmt和go build笙隙,還有一些工具,比如go tool pprof,我只使用它們來解決特定的問題。但總的來說暇咆,我很欣賞這樣一個事實,即它們使管理和維護我的項目變得更容易崎坊。
在這篇文章中,我希望提供一些我認為最有用的工具纤泵,更重要的是捻浦,解釋它們如何適合典型項目的工作流程缕碎。如果你是新手,我希望它能給你一個好的開始毙沾」┨睿或者泳桦,如果你已經使用Go一段時間了惦蚊,有些東西并不適用于你,希望你仍然會發(fā)現(xiàn)一個命令是你以前不知道的。
1曲伊、安裝工具
在這篇文章中灭美,我將主要關注go命令的一部分工具。要在使用Go module的同時安裝模塊围详,首先需要確保您在啟用模塊的目錄之外(我通常只需要更改到/tmp)期丰。然后可以使用GO111MODULE=on go get命令安裝工具。例如:
$ cd /tmp
$ GO111MODULE=on go get golang.org/x/tools/cmd/stress
這將下載相關的包和依賴項鸡岗,構建可執(zhí)行文件并將其添加到GOBIN目錄中混槐。如果您沒有明確設置GOBIN目錄,那么可執(zhí)行文件將被添加到您的GOPATH/bin文件夾中轩性。無論采用哪種方式声登,您都應該確保有適當?shù)哪夸浽谙到y(tǒng)路徑上。
2揣苏、查看環(huán)境信息
您可以使用go env工具來顯示關于當前go運行環(huán)境的信息悯嗓。如果您使用的是不熟悉的機器,那么這一點特別有用卸察。
$ 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"
如果您對某個環(huán)境變量感興趣脯厨,可以將它們作為參數(shù)傳遞給go env。例如:
$ go env GOPATH GOOS GOARCH
/home/Alex/go
linux
amd64
要顯示所有go env變量和值的文檔可以運行:
go help environment
開發(fā)工具
運行代碼
在開發(fā)過程中坑质,go run工具是測試代碼的一種方便的方法合武。它本質上是一個編譯代碼的快捷方式个少,在/tmp目錄中創(chuàng)建一個可執(zhí)行的二進制文件,然后運行這個二進制文件眯杏。
$ 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命令將返回大多數(shù)(而不是全部)依賴敬飒。
如果您對應用程序依賴關系的分析或可視化感興趣,那么您可能想要查看go mod graph工具芬位。這里有一個生成可視化的教程和示例代碼无拗。
最后,下載的依賴項存儲在位于GOPATH/pkg/mod的模塊緩存中晶衷。如果您需要清除模塊緩存蓝纲,可以使用go clean工具。但是要注意:這將刪除您機器上所有項目下載的依賴項晌纫。
$ go clean -modcache
代碼重構
您可能很熟悉使用gofmt工具來自動格式化代碼。但它也支持重寫規(guī)則永丝,您可以使用這些規(guī)則來幫助重構代碼锹漱。我將演示。假設你有以下代碼慕嚷,你想把foo變量改為foo哥牍,這樣它就可導出了毕泌。
var foo int
func bar() {
foo = 1
fmt.Println("foo")
}
要做到這一點,你可以使用帶有-r標志的gofmt來實現(xiàn)重寫規(guī)則嗅辣,使用-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標志的重寫規(guī)則仔夺,先檢查差異,以確保對代碼的更改是您所期望的攒砖。讓我們看一個稍微復雜一點的例子缸兔。假設您想要更新代碼,使用新的strings.ReplaceAll()函數(shù)而不是strings.Replace()吹艇。要進行此更改灶体,可以運行以下命令:
$ gofmt -w -r 'strings.Replace(a, b, c, -1) -> strings.ReplaceAll(a, b, c)' .
查看文檔
您可以使用go doc工具通過終端查看標準庫包的文檔。我經常在開發(fā)過程中使用它來快速檢查某些東西——比如特定函數(shù)的名稱或簽名掐暮。我發(fā)現(xiàn)它比瀏覽網絡文檔更快蝎抽,而且它總是離線可用。
$ go doc strings # 查看字符串包的簡化文檔
$ go doc -all strings # 查看strings包的完整文檔
$ go doc strings.Replace # 查看字符串文檔中Replace函數(shù)
$ 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函數(shù)源碼
測試Testing
執(zhí)行測試
可以使用go test工具在項目中運行測試樟结,如下所示:
$ go test . # 運行當前目錄中的所有測試
$ go test ./... # 運行當前目錄和子目錄中的所有測試
$ go test ./foo/bar # 運行./foo/bar目錄中的所有測試
通常情況下,我在運行測試時啟用了Go的競爭檢測精算,它可以幫助收集在實際使用中可能發(fā)生的一些數(shù)據競爭瓢宦。像這樣:
$ 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
這將為您提供所有測試文件的導航列表辨萍,其中測試覆蓋的代碼顯示為綠色棋恼,未覆蓋的代碼顯示為紅色。
如果愿意锈玉,可以進一步設置-covermode=count標志爪飘,使覆蓋率配置文件記錄測試期間每個語句執(zhí)行的確切次數(shù)。
$ go test -covermode=count -coverprofile=/tmp/profile.out ./...
$ go tool cover -html=/tmp/profile.out
當在瀏覽器中查看時拉背,執(zhí)行頻率更高的語句會顯示為更飽和的綠色陰影师崎,類似如下:
最后,如果你沒有可用的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命令連續(xù)多次運行測試犁罩,如果希望檢查零星或間歇故障,這可能很有用。例如:
$ go test -run=^TestFooBar$ -count=500 .
在本例中,TestFooBar測試將重復500次赵颅。但是需要注意的是,測試將以串行方式重復執(zhí)行——即使它包含t.Parallel()指令顷窒。因此,如果測試的速度相對較慢源哩,比如訪問數(shù)據庫鞋吉、硬盤或互聯(lián)網,那么運行大量測試可能會花費相當長的時間励烦。
在這種情況下谓着,您可能希望使用壓力工具并行地多次重復相同的測試。你可以像安裝stress:
$ cd /tmp
$ GO111MODULE=on go get golang.org/x/tools/cmd/stress
要使用壓測工具坛掠,首先需要為要測試的特定包編譯一個測試二進制文件赊锚。你可以使用go test -c命令。例如屉栓,要為當前目錄中的包創(chuàng)建一個測試二進制文件:
$ go test -c -o=/tmp/foo.test .
在本例中舷蒲,測試二進制文件將輸出到/tmp/foo.test。然后友多,你可以使用壓力工具在測試二進制文件中執(zhí)行特定的測試牲平,如下所示:
$ stress -p=4 /tmp/foo.test -test.run=^TestFooBar$
60 runs so far, 0 failures
120 runs so far, 0 failures
...
注意:在上面的例子中,我使用了-p標志來限制stress所使用的并行進程的數(shù)量為4域滥。如果沒有這個標志纵柿,工具將默認使用runtime.NumCPU()個進程數(shù)。
測試所有的依賴
在為發(fā)布或部署構建可執(zhí)行文件或公開發(fā)布代碼之前启绰,您可能需要運行go test all命令:
$ go test all
這將運行模塊中的所有包和所有依賴項的測試——包括測試依賴項和必要的標準庫包——它可以幫助驗證所使用的依賴項的確切版本彼此兼容昂儒。這可能需要相當長的時間來運行,但使用測試結果緩存委可,后續(xù)測試都應該會很快渊跋。如果愿意,還可以使用go test -short all來跳過任何長時間運行的測試着倾。
代碼提交前的檢查
格式化代碼
Go提供了兩個工具:gofmt和Go fmt來根據Go約定自動格式化代碼拾酝。使用這些工具有助于保持您的代碼在文件和項目之間的一致性,并且——如果您在提交代碼之前使用它們——有助于在檢查文件版本之間的差異時減少干擾屈呕。
我喜歡使用gofmt工具帶有以下標志:
$ gofmt -w -s -d foo.go # 格式化foo.go文件
$ gofmt -w -s -d . # 遞歸格式化當前目錄和子目錄中的所有文件
在這些命令中微宝,-w標志指示工具在適當?shù)牡胤街貙懳募?s指示工具在可能的情況下對代碼進行簡化,而-d標志指示工具輸出更改的差異(因為我很想知道更改了什么)虎眨。如果您想只顯示更改后的文件的名稱蟋软,而不是顯示差異,您可以將此替換為-l標志嗽桩。
注意:gofmt命令是遞歸工作的岳守。如果你傳遞給它一個目錄如.或者./cmd/foo它會格式化目錄下所有的.go文件。
另一個格式化工具gofmt - tool是一個包裝器碌冶,它在指定的文件或目錄上調用gofmt -l -w湿痢。你可以這樣使用它:
$ go fmt ./...
靜態(tài)分析工具
go vet工具對你的代碼進行靜態(tài)分析,并提醒你代碼中可能存在的錯誤,但編譯器不會發(fā)現(xiàn)這些錯誤譬重。諸如執(zhí)行不到的代碼拒逮、不必要的分配和格式錯誤的構建標記等問題。用法如下:
$ 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參數(shù)時,它將只運行指定的分析器胶哲,所有其他go vet分析器將不會運行畔塔。
順便提一下,從Go1.10開始纪吮,go test工具會在運行任何測試之前自動執(zhí)行部分可靠的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等都是“惰性命令”,只會獲取當前構建環(huán)境所需的包)冰肴。每次提交代碼之前運行此命令屈藐,在查看版本控制歷史記錄時可以更容易地確定,哪些代碼導致添加或刪除了哪些依賴項熙尉。
我還建議使用go mod verify命令來檢查您的計算機上的依賴項是否在下載后意外(或有意)被更改联逻,并且它們與您的go.sum文件中的加密哈希值是否匹配。運行此命令有助于確保所使用的依賴項與您所期望的完全一致检痰。
構建和部署
要編譯一個main包并創(chuàng)建一個可執(zhí)行的二進制文件包归,你可以使用go build工具。通常铅歼,我將它與-o參數(shù)結合使用公壤,讓你顯式地設置輸出目錄和二進制文件的名稱:
$ 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 # 列出用于構建可執(zhí)行文件的所有包
$ go build -a -x -o=/tmp/foo . # 重新生成所有內容并顯示正在運行的命令
最后,如果您在一個非main包里運行go build慎皱,它將在一個臨時位置編譯老虫,結果將存儲在構建緩存中。不生成可執(zhí)行文件茫多。
跨平臺編譯
默認情況下祈匙,go build將輸出適合于當前操作系統(tǒng)和體系結構使用的二進制文件。但是它也支持交叉編譯天揖,因此您可以生成適合在不同機器上使用的二進制文件夺欲。如果您在一個操作系統(tǒng)上開發(fā),在另一個操作系統(tǒng)上部署今膊,那么這一點特別有用些阅。
通過分別設置GOOS和GOARCH環(huán)境變量,可以指定要為其創(chuàng)建二進制文件的操作系統(tǒng)和體系結構斑唬。例如:
$ GOOS=linux GOARCH=amd64 go build -o=/tmp/linux_amd64/foo .
$ GOOS=windows GOARCH=amd64 go build -o=/tmp/windows_amd64/foo.exe .
要查看所有支持的操作系統(tǒng)/架構組合的列表市埋,可以運行go tool dist list:
$ go tool dist list
aix/ppc64
android/386
android/amd64
android/arm
android/arm64
darwin/386
darwin/amd64
...
關于交叉編譯的更深入的信息,我推薦閱讀這篇文章恕刘。
編譯器和連接器參數(shù)
在構建可執(zhí)行文件時缤谎,您可以使用-gcflags標志來改變編譯器的行為,并查看關于執(zhí)行的更多信息褐着。你可以通過下面的命令查看可用編譯器參數(shù)的完整列表:
$ go tool compile -help
您可能會感興趣的一個參數(shù)是-m坷澡,它觸發(fā)打印關于在編譯期間做出的優(yōu)化信息。使用如下:
$ go build -gcflags="-m -m" -o=/tmp/foo . # 打印關于優(yōu)化決策的信息
在上面的示例中含蓉,我使用了兩次-m標志來表示想打印兩層深度的決策信息频敛。只使用一個就可以得到更簡單的輸出。
另外谴餐,從Go 1.10開始姻政,編譯器標志只適用于go build的特定包——在上面的例子中是當前目錄中的包(用.表示)。如果你想打印所有包的優(yōu)化決策岂嗓,包括依賴項汁展,可以使用下面的命令:
$ go build -gcflags="all=-m" -o=/tmp/foo .
從Go 1.11開始,您會發(fā)現(xiàn)調試優(yōu)化的二進制文件比以前更容易了。但是食绿,如果需要侈咕,您仍然可以使用標志-N來禁用優(yōu)化,使用標志-l來禁用內聯(lián)器紧。例如:
$ go build -gcflags="all=-N -l" -o=/tmp/foo . # 禁用優(yōu)化和內聯(lián)
您可能還對使用-s和-w參數(shù)耀销,從二進制文件中剝離調試信息感興趣。通常會使可執(zhí)行文件大小減少25%铲汪。例如:
$ go build -ldflags="-s -w" -o=/tmp/foo . # 從二進制文件中剝離調試信息
注意:如果二進制文件大小是你需要優(yōu)化的目標熊尉,你可能會使用upx來壓縮它。更多信息請看這篇文章掌腰。