第 16 章 / 共 20 章
包与模块
导入、可见性、init、go mod、版本管理
包
每个目录是一个包;目录下所有 .go 文件 package 名必须一致。可执行程序的入口包必须叫 main,包含 func main()。库包名通常和目录名相同。
myapp/
├── go.mod
├── main.go // package main
└── internal/
└── store/
├── store.go // package store
└── store_test.go // package store可见性
标识符首字母大写则导出(包外可见),小写则只在包内可用——结构体字段也遵守这个规则。包是 Go 的最小封装单位,没有 public/private 关键字。
// store/user.go
package store
func parse(s string) {} // 包内可用
func Parse(s string) {} // 导出
type User struct {
ID int // 包外可读写
name string // 包内私有
}导入
import (
"fmt" // 标准库
"example.com/myapp/internal/db" // 同一模块内
"github.com/google/uuid" // 第三方
alias "encoding/json" // 起别名(含义冲突时)
. "math" // 点导入,把符号引到当前作用域(除测试外不推荐)
_ "github.com/lib/pq" // 空白导入,只触发 init
)init 函数与执行顺序
每个文件可定义任意个 init(),按文件名顺序执行;同一包的 init 在 main() 前跑。包之间的 init 顺序由依赖关系决定:被依赖的包先初始化。空白导入 `_ "github.com/lib/pq"` 就是用它的副作用——驱动注册。
package main
import "fmt"
type Driver interface {
Name() string
}
type MemoryDriver struct{}
func (MemoryDriver) Name() string { return "memory" }
var registry = map[string]Driver{}
func Register(name string, d Driver) { registry[name] = d }
func init() {
Register("memory", MemoryDriver{})
}
func main() {
fmt.Println(registry["memory"].Name())
}Go Modules
$ go mod init example.com/myapp # 新建模块(example.com/myapp 是 import 路径前缀)
$ go get github.com/google/uuid # 加最新依赖
$ go get github.com/google/uuid@v1.6.0 # 锁版本
$ go get -u ./... # 升级当前模块所有依赖
$ go mod tidy # 同步 go.mod / go.sum,删未用、加未列
$ go mod download # 拉到本地缓存
$ go mod why github.com/x/y # 解释为什么依赖了它
$ go mod graph # 依赖关系图go.mod 结构
module example.com/myapp
go 1.22
require (
github.com/google/uuid v1.6.0
golang.org/x/sync v0.7.0
)
replace example.com/lib => ../lib // 本地调试用
exclude github.com/bad/pkg v1.2.3 // 排除某版本语义化版本与 /v2
Go Modules 严格遵循 SemVer:vMAJOR.MINOR.PATCH,且 v2 及以上的大版本必须把模块路径改成 module example.com/lib/v2,并以 v2 子目录或主分支形式发布。导入时也写 example.com/lib/v2,否则就是不同模块。
internal 与目录布局
internal 目录下的包只能被同一模块内引入——这是 Go 编译器强制的访问控制。常见“标准布局”:
myapp/
├── cmd/
│ └── api/main.go // 可执行入口
├── internal/ // 仅本模块可用
│ ├── http/
│ ├── service/
│ └── store/
├── pkg/ // 对外可复用的工具库(可选)
├── api/ // proto / openapi
├── go.mod
└── go.sumGOPROXY 与私有模块
Go 默认通过 GOPROXY 代理下载依赖。国内常配置 goproxy.cn;公司私有仓库需要把对应路径加到 GOPRIVATE,让 go 命令跳过代理与校验和数据库。
$ go env -w GOPROXY=https://goproxy.cn,direct
$ go env -w GOSUMDB=sum.golang.google.cn
$ go env -w GOPRIVATE=*.corp.example.com,git.internal.ioworkspace(多模块开发)
Go 1.18+ 提供 go.work 让你同时开发多个本地模块而不用每个都改 replace。
$ go work init ./api ./lib
$ go work use ./tools # 再加一个