Go 包与模块
包(Package)
包的基本概念
// 每个 Go 文件都必须属于一个包
package main // 包声明
// main 包是可执行程序的入口
// 其他包是库包包的命名规则
// 1. 包名应该是小写字母
package mypackage // 正确
package MyPackage // 不推荐
// 2. 包名应该简短、有意义
package user // 好
package userinfo // 也可以
package u // 不好(太短)
// 3. 包名通常是单数形式
package user // 好
package users // 不推荐
// 4. 避免使用保留字和常用名称
// package main, package fmt 等包的导入
package main
// 标准导入
import "fmt"
import "os"
// 分组导入(推荐)
import (
"fmt"
"os"
"strings"
)
// 别名导入
import (
f "fmt" // 使用 f 代替 fmt
"database/sql"
_ "github.com/go-sql-driver/mysql" // 匿名导入(只执行 init)
)
// 点导入(不推荐,容易造成命名冲突)
import . "fmt" // 可以直接使用 Println,不需要 fmt.Println
func main() {
f.Println("使用别名")
Println("使用点导入")
}包的可见性
// 包内文件:user.go
package user
// 导出(公开):首字母大写
type User struct {
Name string // 导出字段
Email string // 导出字段
age int // 未导出字段(小写开头)
}
// 导出函数
func NewUser(name, email string) *User {
return &User{
Name: name,
Email: email,
}
}
// 未导出函数(小写开头)
func validateEmail(email string) bool {
return strings.Contains(email, "@")
}
// 包外文件:main.go
package main
import "yourmodule/user"
func main() {
// 可以使用导出的类型和函数
u := user.NewUser("Alice", "alice@example.com")
fmt.Println(u.Name) // 可以访问导出字段
// 不能使用未导出的
// u.age = 30 // 错误:age 未导出
// user.validateEmail("test") // 错误:validateEmail 未导出
}Go Modules
初始化 Module
# 在项目根目录执行
go mod init github.com/username/projectname
# 这会创建 go.mod 文件go.mod 文件
// go.mod
module github.com/username/projectname
go 1.21
require (
github.com/gin-gonic/gin v1.9.1
github.com/go-sql-driver/mysql v1.7.1
)
replace (
// 本地替换
example.com/old => example.com/new v1.0.0
)
exclude (
// 排除特定版本
github.com/some/package v1.0.0
)依赖管理
# 添加依赖
go get github.com/gin-gonic/gin
# 添加特定版本
go get github.com/gin-gonic/gin@v1.9.1
# 更新依赖
go get -u github.com/gin-gonic/gin
# 更新所有依赖
go get -u ./...
# 下载依赖
go mod download
# 整理依赖(移除未使用的)
go mod tidy
# 查看依赖
go mod graph
# 验证依赖
go mod verify版本管理
// Go Modules 使用语义化版本(Semantic Versioning)
// 格式:v主版本号.次版本号.修订号
// v1.2.3
// - 主版本号:不兼容的 API 变更
// - 次版本号:向后兼容的功能新增
// - 修订号:向后兼容的问题修复
// 版本标签
// v1.0.0 - 正式版本
// v1.0.0-alpha1 - 预发布版本
// v1.0.0-rc1 - 候选版本包的组织
标准项目结构
myproject/
├── go.mod
├── go.sum
├── main.go
├── cmd/ # 可执行文件
│ ├── server/
│ │ └── main.go
│ └── cli/
│ └── main.go
├── internal/ # 内部包(外部无法导入)
│ ├── config/
│ │ └── config.go
│ └── database/
│ └── db.go
├── pkg/ # 公共包(可以被外部导入)
│ ├── api/
│ │ └── client.go
│ └── utils/
│ └── helper.go
├── api/ # API 定义
│ └── v1/
│ └── user.go
├── config/ # 配置文件
│ └── config.yaml
├── scripts/ # 脚本
│ └── build.sh
└── README.md
internal 包
// internal 包只能被同一模块内的包导入
// 外部模块无法导入 internal 包
// 文件:internal/database/db.go
package database
// 这个包只能被 myproject 模块内的其他包导入
// 外部模块无法导入 github.com/username/myproject/internal/database
type DB struct {
// ...
}pkg 包
// pkg 包可以被外部模块导入
// 文件:pkg/api/client.go
package api
// 这个包可以被外部模块导入
// import "github.com/username/myproject/pkg/api"
type Client struct {
// ...
}init 函数
package main
import "fmt"
// init 函数在包被导入时自动执行
// 可以有多个 init 函数,按顺序执行
func init() {
fmt.Println("init 1")
}
func init() {
fmt.Println("init 2")
}
// init 函数在 main 函数之前执行
func main() {
fmt.Println("main")
}
// 输出:
// init 1
// init 2
// maininit 函数的用途
package database
import (
"database/sql"
_ "github.com/go-sql-driver/mysql" // 匿名导入,执行 init
)
var db *sql.DB
// 初始化数据库连接
func init() {
var err error
db, err = sql.Open("mysql", "user:password@/dbname")
if err != nil {
panic(err)
}
}
// 或者用于注册
package plugins
var plugins = make(map[string]Plugin)
func Register(name string, plugin Plugin) {
plugins[name] = plugin
}
// 某个插件文件
package main
import _ "myproject/plugins/mysql" // 导入时自动注册
// plugins/mysql/mysql.go
package mysql
import "myproject/plugins"
func init() {
plugins.Register("mysql", &MySQLPlugin{})
}包的文档
// Package user 提供用户相关的功能
// 这是包级别的文档注释
package user
// User 表示一个用户
// 这是类型级别的文档注释
type User struct {
Name string // 用户名
Email string // 邮箱地址
}
// NewUser 创建一个新用户
// 参数:
// name: 用户名
// email: 邮箱地址
// 返回:
// *User: 用户实例
func NewUser(name, email string) *User {
return &User{
Name: name,
Email: email,
}
}
// 生成文档:
// go doc user
// go doc user.User
// go doc user.NewUser
// 启动文档服务器:
// godoc -http=:6060工作区(Workspace)
Go 1.18+ 工作区
# 创建工作区
go work init ./module1 ./module2
# 这会创建 go.work 文件// go.work
go 1.21
use (
./module1
./module2
)工作区的用途
# 工作区允许在本地开发多个模块
# 不需要每次修改都 push 到远程仓库
# 场景:开发一个库和它的使用者
myworkspace/
├── go.work
├── mylibrary/
│ ├── go.mod
│ └── lib.go
└── myapp/
├── go.mod
└── main.go
# myapp 可以使用本地开发的 mylibrary
# 而不需要先发布 mylibrary最佳实践
// 1. 包名应该简短、有意义
package user // 好
package userinfo // 也可以
package u // 不好
// 2. 使用 internal 包保护内部实现
// internal/ 目录下的包只能被同一模块导入
// 3. 使用 pkg 包提供公共 API
// pkg/ 目录下的包可以被外部模块导入
// 4. 导出最小接口
// 只导出必要的类型和函数
// 5. 使用 init 函数进行初始化
// 但要避免在 init 中做复杂操作
// 6. 提供包级别的文档
// Package 注释应该描述包的用途
// 7. 使用 go mod tidy 保持依赖整洁
// 定期运行 go mod tidy 移除未使用的依赖总结
Go 包和模块的特点:
- 包:代码组织的基本单位
- 可见性:首字母大写导出,小写不导出
- Modules:Go 1.11+ 的依赖管理系统
- internal:内部包,外部无法导入
- pkg:公共包,可以被外部导入
- init:包初始化函数
- 工作区:本地多模块开发
掌握包和模块后,就可以组织大型项目了!