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
// main

init 函数的用途

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 包和模块的特点:

  1. :代码组织的基本单位
  2. 可见性:首字母大写导出,小写不导出
  3. Modules:Go 1.11+ 的依赖管理系统
  4. internal:内部包,外部无法导入
  5. pkg:公共包,可以被外部导入
  6. init:包初始化函数
  7. 工作区:本地多模块开发

掌握包和模块后,就可以组织大型项目了!


go 模块 依赖管理