公眾號首發:https://mp.weixin.qq.com/s/JwEPt3oZ3aY8ZzKddwnpiA
剛開始接觸 Go 的開發者大概都會遇到一個問題:我該如何組織我的 Go 項目?這種問題當然沒有標準答案,不過 Go 官方下場,給了廣大 Gopher 一個推薦模板。本文就來帶大家一起來學習一下 Go 官方對於 Go 項目佈局的指導原則。
本文以 Go 官方博客「Organizing a Go module」為基石進行講解。
基礎 Go 包
對於最基礎的 Go 包,我們可以按照如下方式組織:
project-root-directory/
go.mod
modname.go
modname_test.go
所有代碼都位於項目的根目錄下,這個項目由一個模塊(module)組成,而這個模塊又由一個包(package)組成。
假設這個項目會上傳到 GitHub 倉庫 github.com/someuser/modname 中,那麼 go.mod 文件中的第一行(module line)應寫為:
module github.com/someuser/modname
modname.go 文件中的代碼使用如下命令聲明一個 modname 包:
package modname
// ... package code here
在其他 Go 項目中,可以通過如下方式導入這個 Go 包:
import "github.com/someuser/modname"
此外,一個 Go 包其實可以被拆分成多個文件:
project-root-directory/
go.mod
modname.go
modname_test.go
auth.go
auth_test.go
hash.go
hash_test.go
這裏所有 Go 文件都屬於同一個包,即都會聲明為 package modname。
NOTE:
不知道你有沒有注意,示例中的項目名稱
project-root-directory使用短橫線來連接多個單詞,而 Go 文件名則使用單詞直接連寫的形式modname.go(而非mod_name.go),這也是 Go 的一貫風格,雖然沒有強制約定,但看多了社區中優秀的 Go 代碼,你就會有所體會。關於這點更加深入的探討,你可以閲讀我曾寫過文章《Go 項目文件命名規範是什麼?》來進行學習。
可執行的 Go 程序
Go 可執行程序(或命令行程序)一般根據複雜程度來構建目錄,最小的 Go 程序可以僅定義一個包含 main 函數的 Go 文件,如果功能較多,可以將其拆分為多個文件:
project-root-directory/
go.mod
auth.go
auth_test.go
client.go
main.go
main.go 中存放了 main 函數,不過這只是一個約定俗成的做法,你可以將其放到任何合法的 xxx.go 中。
當拆分為多個文件,如果 main.go 引用了其他文件中的代碼,這個時候就不能簡單的使用 go run main.go 來執行 Go 程序了,可以按照如下方式執行:
$ go run .
並且,用户可以使用以下命令將 Go 程序安裝到自己的主機上:
$ go install github.com/someuser/modname@latest
更復雜的 Go 程序
如果一個 Go 包或者 Go 可執行程序更加複雜,則可以按照如下方式組織目錄結構:
project-root-directory/
internal/
auth/
auth.go
auth_test.go
hash/
hash.go
hash_test.go
go.mod
modname.go
modname_test.go
這裏還演示了使用 modname.go 作為程序的入口,而非命名為 main.go。
internal/ 目錄下的所有包都不會被公開,其他程序無法引用。這是 Go 語言層面限制,強制的,不僅僅是約定俗成。這樣,我們就可以隨時隨地的重構 internal/ 目錄中的代碼,而不必擔心影響其他程序。
當然,modname.go 中可以導入 internal/ 目錄下的包,因為它們本來就是一個程序。在 modname.go 中導入 auth 包:
import "github.com/someuser/modname/internal/auth"
包含多個包的 Go 程序
我們知道,一個 Go 模塊(module)可以由多個包(package)組成,而每個包都可以有自己的子目錄。
我們可以組織成一個層次結構更深的 Go 程序:
project-root-directory/
go.mod
modname.go
modname_test.go
auth/
auth.go
auth_test.go
token/
token.go
token_test.go
hash/
hash.go
internal/
trace/
trace.go
這個目錄看起來更符合一個真實的 Go 包。
假設 go.mod 中聲明 module 如下:
module github.com/someuser/modname
而 modname.go 位於項目根目錄,聲明包為 modname,用户在使用時則可以按照如下方式導入此包:
import "github.com/someuser/modname"
也可以按照如下方式導入子包:
import "github.com/someuser/modname/auth"
import "github.com/someuser/modname/auth/token"
import "github.com/someuser/modname/hash"
注意:internal/trace 無法被外部程序導入。
包含多個可執行的 Go 程序
有時候,我們可能會在一個 Git 倉庫中包含多個可執行的 Go 程序,可以按照如下方式組織目錄:
project-root-directory/
go.mod
internal/
... shared internal packages
prog1/
main.go
prog2/
main.go
prog1/ 和 prog2/ 中分別有自己的 main.go 文件,即兩個獨立的可執行 Go 程序。
prog1/ 和 prog2/ 可以共享根目錄中的其他包或子包。
我們可以按照如下方式分別安裝二者到主機上:
$ go install github.com/someuser/modname/prog1@latest
$ go install github.com/someuser/modname/prog2@latest
其實,針對一個 Git 倉庫中包含多個可執行的 Go 程序的情況,我們還有更好的做法來組織目錄:
project-root-directory/
go.mod
modname.go
modname_test.go
auth/
auth.go
auth_test.go
internal/
... internal packages
cmd/
prog1/
main.go
prog2/
main.go
將可執行文件都放到 cmd/ 目錄下也是約定俗成的做法。下次,當你見到 cmd/ 目錄,就應該想到這個目錄是幹什麼的。
現在可執行程序的安裝命令如下:
$ go install github.com/someuser/modname/cmd/prog1@latest
$ go install github.com/someuser/modname/cmd/prog2@latest
服務器項目
其實,我們在用 Go 編寫程序時,大多數是用來實現 Go Web Server 程序,這也是 Go 最常見的使用場景,Go 是實現服務器的通用語言選擇。
比如 Kubernetes 項目,其最核心的組件也是 Server 程序。
通常,我們可以按照如下方式來組織 Go 服務器項目:
project-root-directory/
go.mod
internal/
auth/
...
metrics/
...
model/
...
cmd/
api-server/
main.go
metrics-analyzer/
main.go
...
... the project's other directories with non-Go code
如果你有過 Go Web 開發經驗,是不是很熟悉這個目錄結構?
這已經到了廣大 Gopher 最熟悉的領域,接下來,我就不過多講解了,你可以繼續閲讀我寫的另一篇文章《如何設計一個優秀的 Go Web 項目目錄結構》,裏面介紹了開源項目 https://github.com/golang-standards/project-layout 對 Go Web 項目組織的最佳實踐。
總結
我們學習了 Go 官方推薦的項目佈局的指導原則,現在我們對於 Go 包和 Go 可執行程序的目錄組織方式,都有了較為清晰的認知。
此外,我們還順便學習瞭如何安裝 Go 可以執行程序,可以通過 go install xxx 來安裝。
對於廣大 Gopher,接觸最多的應該是 Go Web 服務器的開發。對於 Go Web 項目,不僅 Go 官方給出了指導原則,我們還可以參考 project-layout 項目學習一個大型的 Go 項目是如何佈局的。
希望此文能對你有所啓發。
聯繫我
- 公眾號:Go編程世界
- 微信:jianghushinian
- 郵箱:jianghushinian007@outlook.com
- 博客:https://jianghushinian.cn
- GitHub:https://github.com/jianghushinian