本文會演示簡單的Go軟體包的開發過程,並介紹了go
命令列工具,這是我們獲取,構建和安裝Go軟體包和命令的標準方法。
go工具要求你以特定方式組織程式碼。我們會介紹Go安裝啟動和執行的最簡單方法,一定要仔細閱讀啊。
組織程式碼結構
概要
- Go 程式設計師一般會將他們的原始碼存放在一個工作區中(多個專案放在一個工作區)
- 工作區中包含許多由 git 管理的程式碼倉庫(也可以是其他版本控制工具管理的)
- 每個程式碼倉庫包含一個或者多個 Go package
- 每個 package 由單一目錄下的一個或多個Go 原始碼檔案組成
- package 的目錄路徑決定了其匯入路徑
與其他程式語言不同的是,在其他程式語言裡每個專案都有自己的工作區,並且工作區都與版本控制系統緊密相關。
工作區
工作區是一個目錄層級,這個目錄層級在頂層有兩個目錄:
-
src
目錄,存放原始碼檔案。 -
bin
目錄,存放可執行二進位制檔案。
go
命令工具會把src
中的Go 檔案構建生成二進位制檔案放在bin
目錄中。
src
子目錄通常包含用 git 管理的多個程式碼倉庫,他們對應一個或多個Go 包的開發原始碼。
一個典型的工作區中會包含多個原始碼倉庫,對應多個可執行命令原始碼和包原始碼。大多數 Go 程式設計師會把他們的Go 原始碼和所有依賴的包都放在單一的工作區中。
下面的例子可以讓你更好的瞭解Go 的工作區大概的樣子:
bin/
hello # 可執行命令檔案
outyet # 可執行命令檔案
src/
github.com/golang/example/
.git/
hello/
hello.go # 命令檔案原始碼
outyet/
main.go # 命令檔案原始碼
main_test.go # 測試檔案
stringutil/
reverse.go # package原始碼
reverse_test.go # 測試檔案
golang.org/x/image/
.git/
bmp/
reader.go # package 原始碼
writer.go # package 原始碼
......
上面的目錄樹展示了工作區中的兩個程式碼倉庫(example 和 image)。example 倉庫中包含兩個命令hello 和 outyet(hello 和 outyet 目錄中存放的就是兩個命令的原始碼)一個被用作庫的 package - stirngutil
。image倉庫中包含一個bmp
包。
注意:不能使用符號連結(軟鏈 ln -s)將檔案連結到工作區中。
執行命令和庫是從不同類的原始碼包構建出來的,這個之後的部分會進行說明。
GOPATH 環境變數
GOPATH
環境變數指定工作區的位置。它預設為使用者目錄中名為go的目錄,因此在Linux上為$HOME/go
,在Windows上通常為C:\Users\YourName\Go
。
如果想在其他位置放置工作區,則需要將GOPATH
設定為該目錄的路徑。請注意,GOPATH不得與GO安裝路徑相同。
命令go env GOPATH
列印當前有效的GOPATH
;如果環境變數未設定,它將列印預設位置。為方便起見,可以請將工作區的bin
子目錄新增到系統環境變數$PATH
中
$ export PATH=$PATH:$(go env GOPATH)/bin
同時也把GOPATH
設定成系統的環境變數:
$ export GOPATH=$(go env GOPATH)
包的匯入路徑
一個匯入路徑是用來唯一標識包的字串,包的匯入路徑和他在工作區中的位置相對應。標準庫中的包具有較短的匯入路徑,如“fmt”和“net/http”。對於您自己的軟體包,你必須選擇一個不太可能與將來新增到標準庫或其他外部庫中的內容衝突的基本路徑。
如果你將程式碼儲存在某個原始碼庫中,那麼應該使用該原始碼庫的根目錄作為你的基本路徑。例如,如果你在github.com上有一個GitHub帳戶user,你建立的倉庫都會以 github.com/user 為字首,那麼github.com/user
這應該是你的基本路徑。
請注意,在構建程式碼之前,你不需要將程式碼釋出到遠端儲存庫。就像有一天會發布程式碼一樣來組織程式碼,這是一個好習慣。實際上,您可以選擇任意路徑名,只要它是唯一的。
我們將使用github.com/user
作為基本路徑。在工作區內建立一個儲存原始碼的目錄:
$ mkdir -p $GOPATH/src/github.com/user
你的第一個Go程式
要編譯並執行一個簡單的程式,首先選擇一個軟體包路徑(我們將使用github.com/user/hello),並在您的工作區內建立一個相應的軟體包目錄:
$ mkdir $GOPATH/src/github.com/user/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
go install
構建hello命令,生成一個可執行的二進位制檔案。然後,它將該二進位制檔案作為hello(在Windows下為hello.exe)安裝到工作區的bin目錄中,hello 可執行命令的位置為 $GOPATH/bin/hello
。
Go工具僅在發生錯誤時列印輸出,因此如果這些命令沒有產生輸出,則代表它們已成功執行。
現在,你可以通過在命令列中鍵入程式的完整路徑來執行該程式:
$ $GOPATH/bin/hello
Hello, world.
由於您已將$GOPATH/bin
新增到路徑中,因此只需鍵入二進位制檔案的名字:
$ hello
Hello, world.
你的第一個 library
讓我們編寫一個庫並在上面寫的hello程式中使用它。
同樣,第一步是選擇軟體包路徑(我們將使用github.com/user/stringutil)並建立軟體包目錄:
$ mkdir $GOPATH/src/github.com/user/stringutil
接下來在目錄中建立reverse.go
檔案並新增如下程式碼:
// stringutil包 存放關於字串的工具函式
package stringutil
// Reverse 將引數中的字串反轉後的字串
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
go build
不會產生輸出檔案。相反,它將編譯後的包儲存在本地構建快取中。
在確認stringutil
包構建可以正確之後,修改原始的hello.go
(位於$GOPATH/src/github.com/user/hello中)以使用它:
package main
import (
"fmt"
"github.com/user/stringutil"
)
func main() {
fmt.Println(stringutil.Reverse("!oG ,olleH"))
}
再次編譯安裝 hello 程式後執行他,可以看到輸出中的字串已經被反轉了。
$ hello
Hello, Go!
經過上面幾步後你的工作區現在應該看起來像下面這樣:
bin/
hello
src/
github.com/user/
hello/
hello.go
stringutil/
reverse.go
包名
go 原始碼檔案中的第一行語句必須是:
package name
其中,name是用於匯入的包的預設名稱。(包中的所有檔案必須使用相同的名稱)
go的慣例是包名是匯入路徑的最後一個元素:作為“crypto/rot13”匯入的包它的包名為rot13
。
生成可執行命令的原始碼檔案必須以main
作為包名。
go 中不要求連結到單個二進位制檔案的所有包的包名都是唯一的,只要求匯入路徑(它們的完整檔名)是唯一的。
測試
go有一個由go測試命令和測試包組成的輕量級測試框架。你可以通過建立一個名字以_test.go
結尾的檔案來編寫測試,該檔案包含名為TestXXX的函式,簽名函式為func(t*testing.T)
。測試框架執行每個這樣的函式;如果函式呼叫失敗函式,如t.Error或t.Fail,則認為測試失敗。
通過建立包含以下go程式碼的檔案$GOPATH/src/github.com/user/stringutil/reverse_test.go
,將測試新增到strangutil
包。
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
ok github.com/user/stringutil 0.165s
匯入路徑可以描述如何從版本控制系統(如Git)獲取包原始碼。Go工具使用此屬性自動從遠端倉庫中獲取包。例如,本文件中描述的示例也儲存在GitHub 以github.com/golang/example託管的Git儲存庫中。如果將程式碼倉庫的URL包含在軟體包的匯入路徑中,go將會使用go get`自動獲取、構建和安裝它:
$ 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"
What's Next
- 《Go語言之旅》https://tour.go-zh.org/list 瞭解 Go 語言的基礎語法
- 《Go 入門指南》《Go 入門指南》 通過了200 多個完整的程式碼示例和書中的解釋說明來對所有涉及到的概念和技巧進行徹底的講解。
- 《Go語言程式設計》https://yar999.gitbooks.io/gopl-zh/content... 通過學習這本書會對 Go 有更全面的認識,強化自己的Go語言底層基礎知識。
- 《Effective Go 中文版》《高效的 Go 程式設計》 提供編寫清晰高效、地道的 Go 程式碼的技巧。