在學完 Go 的語法之后猬仁,就可以開始寫代碼了帝璧,但一個項目中不可能只有幾個代碼文件,而是由很多代碼組成湿刽,下面這篇文章將告訴你如何組織這些 Go 代碼的烁。
原文地址:https://golang.org/doc/code
寫在前面
這篇文檔演示了基于 Go Modules(模塊) 的包的開發(fā)流程,并介紹了獲取诈闺、構(gòu)建和安裝 Go 模塊渴庆、包、命令時使用的 Go 工具雅镊。
注意:這篇文章會假設(shè)你在使用 Go1.13或更高版本襟雷,并且沒有設(shè)置 GO111MODULE 環(huán)境變量。如果需要是使用 Go Modules 的版本仁烹,看這里耸弄。
組織代碼
Go 代碼是通過 package(包) 來組織。包是同一目錄下所有源文件的集合卓缰,它們會被編譯在一起叙赚。一個源文件中定義的函數(shù)、類型僚饭、變量和常量對同一個包下的其他源文件都是可見的。
一個 repository(倉庫)中包含一個或多個 module(模塊)胧砰。模塊由多個相關(guān)的包組成鳍鸵,并且會一起發(fā)布。一個倉庫中通常只會包含一個模塊尉间,這個模塊位于倉庫的根目錄偿乖。go.mod 中會聲明模塊路徑,該模塊中的所有包再被導入的時候哲嘲,到會把這個路徑當做前綴加上贪薪。一個模塊的范圍是從包含 go.mod 的文件夾及文件夾中所有的文件夾(包括所有的子文件夾),到那些包含了 go.mod 的子文件夾(如果有的話)為止眠副。
在代碼可以編譯之前画切,你不需要把代碼推送到遠程的倉庫。模塊可以不屬于任何殘酷囱怕,僅僅在本地定義霍弹。把要發(fā)布的代碼組織好是一個好習慣。
每個模塊的路徑不僅僅用于作為導入包的前綴娃弓,在使用 go 命令下載包的時候也需要用到典格。比如,為了下載 golang.org/x/tools 模塊台丛,go 命令就會找到 https://golang.org/x/tools(詳情看這里)耍缴。
導入路徑是用來導入包的字符串。一個包的導入路徑就是模塊的路徑加上這個包在模塊中的子目錄路徑。比如模塊 github.com/google/go-cmp 包含一個包防嗡,在 cmp 目錄下变汪。那么這個包的導入路徑就是 github.com/google/go-cmp/cmp。標準庫中的包沒有模塊路徑前綴本鸣。
你的第一個程序
在編譯和運行一個簡單程序之前疫衩,要選擇一個模塊路徑(在這里用 example.com/user/hello)并且創(chuàng)建一個 go.mod 文件來描述這個模塊:
$ mkdir hello # 可選,如果已經(jīng)存在荣德,直接把項目拉取到本地
$ cd hello
$ go mod init example.com/user/hello
go: creating new go.mod: module example.com/user/hello
$ cat go.mod
module example.com/user/hello
go 1.14
Go 源文件的第一個語句必須是包名闷煤。可執(zhí)行的文件必須使用 main 作為包名涮瞻。
接下來鲤拿,在當前目錄下創(chuàng)建一個 hello.go 文件,寫入以下代碼:
package main
import "fmt"
func main() {
fmt.Println("Hello, world.")
}
現(xiàn)在你可以使用 go 命令來編譯并安裝這個程序:
$ go install example.com/user/hello
這個命令將會編譯 hello 程序署咽,產(chǎn)生一個可執(zhí)行文件近顷。這個命令會被安裝為 $HOME/go/bin/hello
(在 Windows 系統(tǒng)中,路徑是 %USERPROFILE%\go\bin\hello.exe
)宁否。
安裝的目錄通過 GOPATH 和 GOBIN 環(huán)境變量來控制窒升。如果 GOBIN 設(shè)置了,就會安裝到 GOBIN 指定的目錄慕匠。如果 GOPATH 設(shè)置了饱须,這個就會安裝到 GOPATH 目錄下的子目錄 bin 中。否則就會被安裝到默認的 GOPATH 子目錄下 bin 下($HOME/go 或者 %USERPROFILE%\go)台谊。
你可以使用 go env
命令來設(shè)置環(huán)境變量的默認值:
$ go env -w GOBIN=/somewhere/else/bin
使用 go env -u
刪除 go env -w 設(shè)置的變量值:
$ go env -u GOBIN
像 go install
這樣的命令可以在模塊的根目錄及其子目錄中使用蓉媳。如果當前的工作目錄不在 example.com/user/hello 模塊中,go install 會失敗锅铅。
為了方便起見酪呻,go 命令接受工作目錄的相對路徑,如果沒有給出其他路徑盐须,則默認為當前工作目錄下的包玩荠。因此,在我們的工作目錄中贼邓,以下命令都是等價的:
$ go install example.com/user/hello
$ go install .
$ go install
接下來姨蟋,我們來運行一下這個程序,確保功能正常立帖。為了使用方便眼溶,我們把安裝目錄增加到系統(tǒng) PATH 路徑下,這樣就可以直接在命令行使用程序:
# windows 用戶看這里: https://github.com/golang/go/wiki/SettingGOPATH
# 設(shè)置 %PATH%.
$ export PATH=$PATH:$(dirname $(go list -f '{{.Target}}' .))
$ hello
Hello, world.
如果你在用版本管理系統(tǒng)晓勇,現(xiàn)在就可以初始化倉庫堂飞,添加文件灌旧,并提交第一次修改。這個步驟是可選的绰筛,在寫代碼的時候枢泰,不一定要使用版本管理系統(tǒng):
$ git init
Initialized empty Git repository in /home/user/hello/.git/
$ git add go.mod hello.go
$ git commit -m "initial commit"
[master (root-commit) 0b4507d] initial commit
1 file changed, 7 insertion(+)
create mode 100644 go.mod hello.go
go 命令通過請求相應的 HTTPS URL 并讀取 HTML 響應中的元數(shù)據(jù)來定位給定模塊的存儲庫(參見 go help importpath)。許多代碼托管服務已經(jīng)為包含 Go 代碼的存儲庫提供了元數(shù)據(jù)铝噩,因此衡蚂,為了讓你的模塊可以順利的被人找到,直接使用存儲庫的 URL 作為你的模塊路徑骏庸。
導入當前模塊內(nèi)的包
讓我們寫一個 morestrings
包并在 hello 程序中使用它毛甲。首先,為這個包創(chuàng)建一個目錄:$HOME/hello/morestrings具被,并創(chuàng)建一個 reverse.go 文件玻募,填充下面的內(nèi)容:
// Package morestrings implements additional functions to manipulate UTF-8
// encoded strings, beyond what is provided in the standard "strings" package.
package morestrings
// ReverseRunes returns its argument string reversed rune-wise left to right.
func ReverseRunes(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)
}
因為我們的 ReverseRunes 方法以大寫字母開頭,所以它是導出的一姿,可以在其他的包中導入 morestrings 包來使用.
使用 go build 命令編譯測試一下這個包:
$ cd $HOME/hello/morestrings
$ go build
這不會產(chǎn)生輸出文件七咧,相反,它將已編譯的包保存在本地構(gòu)建緩存中叮叹。
在確認 morestrings 是可編譯的艾栋,就可以在 hello 程序中使用它。然后修改 /hello/hello.go 來使用 morestrings 包:
package main
import (
"fmt"
"example.com/user/hello/morestrings"
)
func main() {
fmt.Println(morestrings.ReverseRunes("!oG ,olleH"))
}
安裝 hello 程序:
$ go install example.com/user/hello
運行這個新版的程序蛉顽,你應該可以看見一個反轉(zhuǎn)的字符串:
$ hello
導入遠程模塊的包
導入路徑可以使用版本管理系統(tǒng)(如 Git 或 Mercurial)獲取源碼蝗砾,go 工具會自動從遠程倉庫獲取包。例如蜂林,在程序中使用 github.com/google/go-cmp/cmp:
package main
import (
"fmt"
"example.com/user/hello/morestrings"
"github.com/google/go-cmp/cmp"
)
func main() {
fmt.Println(morestrings.ReverseRunes("!oG ,olleH"))
fmt.Println(cmp.Diff("Hello World", "Hello Go"))
}
當你運行 go install,go build拇泣,或者 go run 等命令時噪叙,就會自動下載這些遠程的模塊并記錄到 go.mod 文件中:
$ go install example.com/user/hello
go: finding module for package github.com/google/go-cmp/cmp
go: downloading github.com/google/go-cmp v0.4.0
go: found github.com/google/go-cmp/cmp in github.com/google/go-cmp v0.4.0
$ hello
Hello, Go!
string(
- "Hello World",
+ "Hello Go",
)
$ cat go.mod
module example.com/user/hello
go 1.14
require github.com/google/go-cmp v0.4.0
模塊依賴關(guān)系會自動下載到 GOPATH 下的 pkg/mod 子目錄。某個模塊的給定版本的下載內(nèi)容在所有需要該版本的其他模塊之間共享霉翔,因此 go 命令將這些文件和目錄定義為只讀睁蕾。如果要刪除這些下載的模塊,可以使用:
$ go clean -modcache
測試
Go 有一個輕量級的測試框架债朵,包含 go test
命令和 testing
包子眶。
測試文件的結(jié)尾必須以 _test.go
結(jié)尾,并且測試函數(shù)的名稱都是 TestXXX序芦,方法簽名是 func(t *testing.T)。測試框架會運行每一個函數(shù),如果函數(shù)調(diào)用了t.Error 或者 t.Fail 函數(shù)斋日,那么這個測試就可以被認為失敗。
為 morestrings 添加一個測試 $HOME/hello/morestrings/reverse_test.go寥枝,寫入以下代碼:
package morestrings
import "testing"
func TestReverseRunes(t *testing.T) {
cases := []struct {
in, want string
}{
{"Hello, world", "dlrow ,olleH"},
{"Hello, 世界", "界世 ,olleH"},
{"", ""},
}
for _, c := range cases {
got := ReverseRunes(c.in)
if got != c.want {
t.Errorf("ReverseRunes(%q) == %q, want %q", c.in, got, c.want)
}
}
}
使用 go test 來運行測試:
$ go test
PASS
ok example.com/user/morestrings 0.165s
運行 go help test
或者查看文檔來看更多細節(jié)。