前言
從 JS 轉寫 Go 我其實還是不太熟悉 Go 如何模組化處理代碼,雖然它們有大致相似的地方,但使用體驗感覺非常簡單甚至到簡陋的程度,當然簡單並不意味著「容易」或「沒用」,Go 簡單且固執己見的哲學在各方面都感受得到。
初始化一個 Go Module
# 初始化一個 Go Module:Module Path 必須獨特,通常使用 GitHub Repo 路徑go mod init github.com/riceball-tw/project
# 安裝一個 Go Modulego get github.com/fatih/color會得到以下的 go.mod 與 go.sum 純文字檔用於紀錄模組的依賴與 Go 版本,沒有被引用過的套件就會被註解為 indirect:
module github.com/riceball-tw/project
go 1.25.6
require ( github.com/fatih/color v1.18.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect golang.org/x/sys v0.25.0 // indirect)main 是一切的入口
所有被編譯的 Go 程式都一定會先執行 package main 的 main() 沒有例外,多個 main package 可以存在於同個 module。
package main
func main()創建不同的 package
習慣上通常資料夾名稱會是 package 名稱,非強制。
greetings/└── greetings.gogo.modgo.summain.gopackage greetings
import "fmt"
func Hello(name string) { fmt.Printf("Hello %s!", name)}大小寫控制變數是否公開
巧妙的是在 Go 當中變數或函式大小寫控制「是否其他 package 能否存取」,可以說是一種非常獨特的設計,沒有額外的關鍵字或慣例。
- 大寫開頭:表示 exported(公開),可以被其他 package 存取
- 小寫開頭:表示 unexported(私有),只能在同個 package 內使用
引入其他 package
透過 import 組合 「module 名稱」與 「package 路徑」 就能使用 package 當中的變數:
package main
import "github.com/riceball-tw/project/greetings"
func main() { greetings.Hi("test")}引入 package 別名
也可以使用別名來簡化長的 package 名稱:
import g "github.com/riceball-tw/project/greetings"func main() { g.Hello("test")}Internal package
Go 還有一個特殊的 internal 資料夾機制。放在 internal 目錄下的 package 只能被其父目錄及其子目錄的代碼引用:
myproject/├── internal/│ └── helper/│ └── helper.go # 只能被 myproject 內的代碼使用├── greetings/│ └── greetings.go└── main.goPackage 測試
Go 的測試工具內建於語言當中,測試檔案必須以 _test.go 結尾,測試函式必須以 Test 開頭並接受 *testing.T 參數。
package greetings
// Hello 是公開函式func Hello(name string) string { return formatMessage(name)}
// formatMessage 是私有函式func formatMessage(name string) string { return "Hello " + name + "!"}package greetings // 注意:沒有 _test
func TestHello(t *testing.T) { Hello("World") // ✅ 可以調用}
func TestFormatMessage(t *testing.T) { formatMessage("World") // ✅ 可以調用私有函式}package greetings_test // 注意:有 _test
import "github.com/riceball-tw/project/greetings"
func TestHello(t *testing.T) { greetings.Hello("World") // ✅ 可以調用}
func TestFormatMessage(t *testing.T) { greetings.formatMessage("World") // ❌ 編譯錯誤!無法訪問私有函式}而 Go 提供了一個獨特的測試方式,只要在測試檔案中使用 package packagename_test(後綴 _test),這樣測試代碼就只能訪問 package 的公開 API,模擬外部使用者的視角:
- 強制使用公開 API:確保測試從使用者角度出發,只測試公開介面
- 避免測試實作細節:無法訪問私有函式,防止測試與實作過度耦合
- 更好的封裝性:如果重構內部實作,只要公開 API 不變,測試就不需要修改
總結
- 一個 Module 可以有多個 package
- package 的存取控制透過大小寫來決定,大寫開頭為公開,小寫為私有
- 每個可執行的 Go 程式必須有一個
main package和main()函式 go.mod管理依賴關係,go.sum管理套件的校驗和internal目錄提供了模組內部的私有 package 機制