Familiar with Go Modules and Packages

熟悉使用 Go 模块与包

前言

从 JS 转写 Go 我其实还是不太熟悉 Go 如何模块化处理代码,虽然它们有大致相似的地方,但使用体验感觉非常简单甚至到简陋的程度,当然简单并不意味着「容易」或「没用」,Go 简单且固执己见的哲学在各方面都感受得到。

初始化一個 Go Module

Terminal window
# 初始化一個 Go Module:Module Path 必須獨特,通常使用 GitHub Repo 路徑
go mod init github.com/riceball-tw/project
# 安裝一個 Go Module
go get github.com/fatih/color

會得到以下的 go.modgo.sum 純文字檔用於紀錄模組的依賴與 Go 版本,沒有被引用過的套件就會被註解為 indirect

go.mod
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 mainmain() 沒有例外,多個 main package 可以存在於同個 module。

package main
func main()

創建不同的 package

習慣上通常資料夾名稱會是 package 名稱,非強制。

greetings/
└── greetings.go
go.mod
go.sum
main.go
greetings.go
package 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.go

Package 測試

Go 的測試工具內建於語言當中,測試檔案必須以 _test.go 結尾,測試函式必須以 Test 開頭並接受 *testing.T 參數。

greetings.go
package greetings
// Hello 是公開函式
func Hello(name string) string {
return formatMessage(name)
}
// formatMessage 是私有函式
func formatMessage(name string) string {
return "Hello " + name + "!"
}
greetings_test.go - 白盒測試
package greetings // 注意:沒有 _test
func TestHello(t *testing.T) {
Hello("World") // ✅ 可以調用
}
func TestFormatMessage(t *testing.T) {
formatMessage("World") // ✅ 可以調用私有函式
}
greetings_test.go - 黑盒測試
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 packagemain() 函式
  • go.mod 管理依賴關係,go.sum管理套件的校驗和
  • internal 目錄提供了模組內部的私有 package 機制

延伸閱讀