本文會演示簡單的 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
src/
github.com/golang/example/
.git/
hello/
hello.go
stringutil/
reverse.go
reverse_test.go
github.com/user/
hello/
hello.go
stringutil/
reverse.go
reverse_test.go
託管在 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 編程 Effective Go》 提供編寫清晰高效、地道的 Go 代碼的技巧。