如何編寫Go程式碼
簡介
本文演示了一個簡單的Go語言包的開發,以及go tool
命令的使用,包含:獲取、構建、安裝Go包和命令的標準方法。
go tool
要求用特別的方式來組織你的Go程式碼。仔細閱讀本文,它解釋了啟動和執行Go安裝的最簡單方法。
程式碼組織
概述
- 程式員通常會將所有Go程式碼儲存在一個工作區中
- 工作空間包含許多版本控制的倉庫(repo)(例如,由Git管理的)
- 每個倉庫包含一個或多個包
- 每個包由一個目錄中的一個或多個Go原始檔組成
- 包目錄的路徑確定其匯入路徑
請注意,這與其他程式設計環境不同,在這些環境中,每個專案都有一個單獨的工作區,工作區與版本控制倉庫緊密相關。
工作區
工作空間是一個目錄層次結構,其根目錄有兩個目錄:
src bin
go工具構建二進位制檔案並將其安裝到bin
目錄。
src
子目錄通常包含多個版本控制的倉庫(例如Git),用於跟蹤一個或多個源包的開發。
在實踐中,工作區應該是什麼樣子呢? 下面給出一個例子:
bin/ hello# command executable outyet# command executable src/ github.com/golang/example/ .git/# Git repository metadata hello/ hello.go# command source outyet/ main.go# command source main_test.go# test source stringutil/ reverse.go# package source reverse_test.go# test source golang.org/x/image/ .git/# Git repository metadata bmp/ reader.go# package source writer.go# package source ... (many more repositories and packages omitted) ...
上面的這個樹型結構展示出一個工作區包含了2個倉庫(example
和image
)。example
倉庫包含了2個命令(hello
和outyet
)和一個庫(stringutil
)。image
倉庫包含了bmp
包和 一些其他的包。
通常工作區會包含很多的源倉庫(包含需要多和命令)。大多數的Go開發者都會把他們的原始碼和依賴存放在一個工作區。
命令和庫是從不同型別的源包構建的。我們稍後會討論這種區別。
GOPATH 環境變數
GOPATH
環境變數用來指定工作區的位置。預設是使用者主目錄的go
目錄,如在Linux和macOS上是$HOME/go
, 在Windows上是C:\Users\YourName\go
。
go env GOPATH
命令會打印出當前有效的GOPATH
; 如果環境變數沒有被設定會打印出預設的位置。
為方便起見,將工作空間的bin
子目錄新增到PATH
:
$ export PATH=$PATH:$(go env GOPATH)/bin
為簡潔起見,本文件其餘部分中的指令碼使用$GOPATH
而不是$(go env GOPATH)
。即
$ export PATH=$PATH:$GOPATH/bin
而如果還沒有設定$GOPATH
就執行寫好的指令碼,你需要替換為$HOME/go
, 否則需要執行:
$ export GOPATH=$(go env GOPATH)
要學習更多關於gopath環境變數,可以使用檢視幫助go help gopath
要使用自定義的工作區,可以檢視ofollow,noindex" target="_blank">https://golang.org/wiki/SettingGOPATH
匯入路徑
匯入路徑(import path)是唯一標識包的字串。包的匯入路徑對應於其在工作空間內或遠端倉庫中的位置(如下所述)。
標準庫中的包具有簡短的匯入路徑,例如fmt
和net/http
。對於我們自己開發的包您必須選擇一個基本路徑,該路徑不太可能與將來新增到標準庫或其他外部庫中發生衝突。
如果將程式碼儲存在某個源倉庫中,則應使用該源倉庫的根作為基本路徑。例如,如果你在github.com/user
上有一個GitHub帳戶,那麼這應該是你的基本路徑。
請注意,在構建程式碼之前,無需將程式碼釋出到遠端倉庫。組織程式碼只是一個好習慣,好像有一天你會發布它一樣。實際上,你可以選擇任意路徑名稱,只要它對標準庫和更大的Go生態系統是唯一的。
我們將使用github.com/user
作為我們的基本路徑。在工作區內建立一個目錄,用於儲存原始碼:
$ mkdir -p $GOPATH/src/github.com/user
你的第一個Go程式
要編譯和執行一個簡單的程式,首先要選擇一個包路徑(我們會使用github.com/user/hello
),然後在工作區裡建立一個相應的包目錄:
$ mkdir $GOPATH/src/github.com/user/hello
接下來,在hello目錄裡建立一個hello.go
檔案,寫入以下內容:
package main import "fmt" func main() { fmt.Println("Hello, world.") }
現在就可以使用go工具來構建和安裝該程式:
go install github.com/user/hello
注意,你可以在系統上的任何地方執行該命令。go工具通過在GOPATH
指定的工作空間內查詢github.com/user/hello
包來查詢原始碼。
如果是在這個包目錄內執行go install
也可以忽略包路徑:
$ cd $GOPATH/src/github.com/user/hello $ go install
該命令會生成一個hello
命令,生成一個可執行的二進位制檔案。同時安裝到工作區目錄下的bin
目錄,生成的可執行檔案是hello
(如果是windows則是 hello.exe)。
在本例子中是$GOPATH/bin/hello
, 也就是$HOME/go/bin/hello
。
當有錯誤發生的時候,go工具僅會列印除錯誤,所以如果沒有任何輸出的時候說明已經執行成功。
可以通過全路徑來執行:
$ $GOPATH/bin/hello Hello, world.
如果已經把$GOPATH/bin
加入到了PATH
, 可以直接輸入二進位制檔名:
$ hello Hello, world.
如果你正在使用一個版本控制系統,比如Git,現在是時候來初始化來生成一個倉庫(repository),然後新增檔案,做第一次提交。
當然這一步是可選的,不一定非要使用版本控制系統來寫Go程式碼
$ cd $GOPATH/src/github.com/user/hello $ git init Initialized empty Git repository in /home/user/work/src/github.com/user/hello/.git/ $ git add hello.go $ git commit -m "initial commit" [master (root-commit) 0b4507d] initial commit 1 file changed, 1 insertion(+) create mode 100644 hello.go $ git push origin master
你的第一個庫
我們再來寫一個庫,並在hello
程式中使用。
首先,第一步確定好包路徑,我們使用github.com/user/stringutil
, 建立包目錄
$ mkdir $GOPATH/src/github.com/user/stringutil
其次,建立一個名為reverse.go
的檔案,並寫入以下內容:
// Package stringutil contains utility functions for working with strings. package stringutil // Reverse returns its argument string reversed rune-wise left to right. func Reverse(s string) string { r := []rune(s) for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { r[i], r[j] = r[j], r[i] } return string(r) }
使用go build
編譯該包:
$ go build github.com/user/stringutil
如果已經在github.com/user/stringutil
目錄裡,則直接執行:
$ go build
當然該命令不會生成檔案,而是把編譯好的包放到了本地的構建(build)快取裡。
確實stringutil
包被編譯後, 修改hello.go
:
vim $GOPATH/src/github.com/user/hello
修改後的:
package main import ( "fmt" "github.com/user/stringutil" ) func main() { fmt.Println(stringutil.Reverse("!oG ,olleH")) }
再次安裝
$ go install github.com/user/hello
執行:
$ hello Hello, Go!
通過上面的一些步驟後,現在我們的結構是這樣子的:
bin/ hello# command executable src/ github.com/user/ hello/ hello.go# command source stringutil/ reverse.go# package source
包名
在Go原檔案中第一個使用的語句必須是
package name
其中name
就是包的預設名稱。一個包中的所有檔案必須使用相同的包名。
Go的約定是包名稱是匯入路徑的最後一個元素,例如匯入的包crypto/rot13
, 包名就是rot13
如果是可執行的檔案,包名必須使用main
。
不強制要求所有的包名都是唯一的,但是要求匯入的路徑必須是唯一的(全路徑檔名)。
更多關於go的命名規範可以檢視Effective Go
測試
Go提供了一個由go test
和testing
包組成的測試框架。
通過建立一個以_test.go
結尾的檔案,裡面寫有以TestXXX
開頭的函式。測試框架會執行每一個這樣的函式,如果函式呼叫了一個失敗的函式,如t.Error
或t.Error
, 那麼測試就算不通過。
通過新增一個測試檔案到stringutil
包中,
$ vim $GOPATH/src/github.com/user/stringutil/reverse_test.go
新增如下程式碼:
package stringutil import "testing" func TestReverse(t *testing.T) { cases := []struct { in, want string }{ {"Hello, world", "dlrow ,olleH"}, {"Hello, 世界", "界世 ,olleH"}, {"", ""}, } for _, c := range cases { got := Reverse(c.in) if got != c.want { t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want) } } }
然後執行測試go test
:
$ go test github.com/user/stringutil okgithub.com/user/stringutil 0.165s
如果當前是在go test github.com/user/stringutil
目錄中,則直接執行:
$ go test okgithub.com/user/stringutil 0.165s
更多細節可以執行go run test 和 檢視測試包文件
遠端包
匯入路徑可以描述如何使用諸如Git之類的版本控制系統來獲取包原始碼。go工具使用此屬性自動從遠端倉庫獲取包。例如,本文件中描述的示例也儲存在GitHubgithub.com/golang/example
上託管的Git倉庫中。如果你在包的匯入路徑中包含倉庫URL,那麼go將自動獲取,構建和安裝它:
$ go get github.com/golang/example/hello $ $GOPATH/bin/hello Hello, Go examples!
如果指定的包不在工作區,go get
將會通過GOPATH
把它放到指定的工作區,如果包已經存在,go get
會跳過遠端獲取,其行為與go install
相同。
上面go get
之後的目錄結構如下:
bin/ hello# command executable src/ github.com/golang/example/ .git/# Git repository metadata hello/ hello.go# command source stringutil/ reverse.go# package source reverse_test.go# test source github.com/user/ hello/ hello.go# command source stringutil/ reverse.go# package source reverse_test.go# test source
在GitHub上託管的hello
命令取決於同一倉庫中的stringutil
包。hello.go
檔案中的匯入使用相同的匯入路徑約定,因此go get
命令也能夠找到並安裝依賴包。
import "github.com/golang/example/stringutil"
此約定是使你的Go包可供其他人使用的最簡單方法。
Go Wiki 和godoc.org 提供了外部Go專案的列表。
有關使用go工具使用遠端倉庫的更多資訊, 可以檢視go help importpath