跳到主要内容

Go 包和模块

本章将深入介绍 Go 的包(Package)和模块(Module)系统,这是 Go 语言组织代码和管理依赖的核心机制。理解这一章的内容对于编写可维护、可复用的 Go 代码至关重要。

包 (Package)

包的概念

包是 Go 组织代码的基本单位。每个 .go 文件(除了 main 包)都必须属于一个包,而每个包对应一个目录。包的主要作用包括:

  1. 代码组织:将相关功能放在一起,便于查找和维护
  2. 命名空间:避免不同代码之间的命名冲突
  3. 访问控制:通过首字母大小写控制导出/未导出

包命名规范

Go 的包命名有一些约定俗成的规则:

// 1. 包名通常使用小写字母
package utils // 正确
package Utils // 不推荐

// 2. 包名应该简短且有意义
package fmt // 标准库格式化包
package strings // 标准库字符串包

// 3. 避免使用下划线或混合大小写
package my_utils // 不推荐
package myUtils // 不推荐
package fileutil // 推荐:file utility

// 4. 包名通常与目录名保持一致
// 目录: mymodule/utils/
// 包名: package utils

命名最佳实践

  • 使用简短、清晰的名称,如 fmthttpjson
  • 避免使用通用名称如 utilcommonbase,这些名称不提供足够信息
  • 如果包提供某种服务,可以用 httplog 这样的动词形式
  • 如果包包含某种数据结构,可以用 listheap 这样的名词形式
  • 避免重复:httputilhttputil 更清晰

包的导入

// 单个导入
import "fmt"
import "os"

// 批量导入(推荐)
import (
"fmt"
"os"
"strings"
)

// 重命名导入(避免命名冲突)
import (
f "fmt" // 将 fmt 重命名为 f
myfmt "fmt" // 自定义别名
)

// 仅导入但不使用(用于包的初始化)
import _ "image/png" // 注册 PNG 解码器

导入路径的几种形式

// 标准库
import "fmt"
import "io/ioutil"

// 相对路径(不推荐,模块模式下应避免)
import "./internal/utils" // 不推荐

// 远程模块
import "github.com/gin-gonic/gin"
import "golang.org/x/crypto/bcrypt"

导出规则

Go 通过首字母大小写来控制名称是否可以被外部包访问:

package mypackage

// 大写开头 - 导出(可以被其他包访问)
var PublicVar = "我可以被访问"
const PublicConst = 42
type PublicStruct struct {
Name string // 导出
}

// 小写开头 - 不导出(只能在本包内访问)
var privateVar = "我只能在本包内访问"
type privateStruct struct {
name string // 不导出
}

func PublicFunc() {} // 导出函数
func privateFunc() {} // 不导出函数

为什么要这样设计?

这种设计有几个显著优势:

  1. 显式且简单:不需要额外的关键字来声明可见性
  2. 文档友好:导出的名称自动成为包的公共 API
  3. 重构方便:将内部名称改为大写即可导出,无需修改其他代码

internal 包的妙用

internal 目录是 Go 1.4 引入的特殊目录,其内的包不能被外部模块导入:

// 项目结构:
// myproject/
// ├── internal/
// │ └── auth/
// │ └── auth.go
// └── pkg/
// └── handler/
// └── handler.go

// internal/auth/auth.go
package auth

func ValidateToken(token string) bool {
// 内部验证逻辑
return true
}

func internalHelper() {
// 辅助函数
}

// pkg/handler/handler.go
package handler

import "myproject/internal/auth" // ✅ 同一项目内可以导入

// 使用
func HandleRequest(token string) {
auth.ValidateToken(token) // ✅ 可以访问
// auth.internalHelper() // ❌ 编译错误:未导出
}

// 外部项目
// import "myproject/internal/auth" // ❌ 编译错误:不能导入 internal 包

internal 的访问规则

internal 目录中的包只能被以下代码导入:

  • internal 目录的父目录树中的代码
  • 父目录的同级目录中的代码

例如,对于路径 /a/b/c/internal/d/e/f

  • /a/b/c 目录及其子目录中的代码可以导入
  • /a/b/g 目录中的代码可以导入
  • /a 目录中的代码不能导入

init 函数

每个包可以有一个或多个 init() 函数,用于包初始化时的设置工作:

package mypackage

import "fmt"

// init 函数在包被导入时自动执行
func init() {
fmt.Println("mypackage 已初始化")
}

// 可以有多个 init 函数,按文件名字母顺序执行
func init() {
fmt.Println("mypackage 第二个初始化")
}

init 函数的特点

  • 无参数、无返回值
  • 自动执行,不能被调用
  • 每个文件可以有多个 init 函数
  • 按照文件名字母顺序执行(同一文件内按声明顺序)
  • 只执行一次

init 函数的执行顺序

导入包 -> 常量初始化 -> 变量初始化 -> init() -> main()

init 函数的常见用途

// 示例一:数据库驱动的 init 注册
package mysql

import "database/sql"

func init() {
sql.Register("mysql", &driver{})
}

// 使用时只需要导入包
import (
_ "github.com/go-sql-driver/mysql" // 空白标识符导入,只为执行 init
"database/sql"
)

// 然后就可以使用
db, _ := sql.Open("mysql", "user:pass@/dbname")

// 示例二:配置初始化
package config

var AppConfig *Config

func init() {
var err error
AppConfig, err = LoadConfig("config.yaml")
if err != nil {
panic(err)
}
}

// 示例三:验证前置条件
package main

func init() {
if os.Getenv("API_KEY") == "" {
panic("API_KEY environment variable is required")
}
}

模块 (Module)

什么是模块

模块是 Go 1.11 引入的官方依赖管理工具。一个模块是一起发布和版本化的一组包的集合。模块通过 go.mod 文件定义,包含:

  • 模块路径(模块的唯一标识符)
  • Go 版本要求
  • 依赖要求

模块与包的关系

理解模块和包的关系很重要:

模块 (Module)
├── go.mod # 模块定义文件
├── go.sum # 依赖校验文件
├── main.go # 包 main
├── utils/
│ ├── string.go # 包 utils
│ └── math.go # 包 utils(同一个包可以多个文件)
├── handler/
│ └── http.go # 包 handler
└── internal/
└── auth/
└── auth.go # 包 auth(内部包)
  • 模块:整个项目,由 go.mod 定义
  • :模块内的代码组织单位,一个目录一个包
  • 包路径 = 模块路径 + 目录路径(相对于模块根目录)

例如,模块 example.com/myapputils 目录的包路径是 example.com/myapp/utils

创建模块

# 在项目目录执行
go mod init github.com/yourname/yourproject

# 或者使用自定义模块名
go mod init mymodule

这会创建一个 go.mod 文件:

module github.com/yourname/yourproject

go 1.22

go.mod 文件详解

go.mod 文件是模块的核心配置文件,支持多种指令:

module github.com/yourname/myproject  // 模块路径(必须)

go 1.22 // Go 版本要求

toolchain go1.22.0 // 工具链版本(Go 1.21+)

godebug http2client=0 // GODEBUG 设置(Go 1.23+)

require ( // 直接依赖
github.com/gin-gonic/gin v1.9.1
github.com/stretchr/testify v1.8.4
)

require ( // 间接依赖
golang.org/x/crypto v0.14.0 // indirect
)

exclude ( // 排除特定版本
example.com/old/thing v1.2.3
)

replace ( // 替换依赖
example.com/bad/thing v1.4.5 => example.com/good/thing v1.4.5
example.com/local/thing => ./local/path
)

retract ( // 撤回版本
v1.9.0 // 撤回单个版本
[v1.9.0, v1.9.5] // 撤回版本范围
)

tool ( // 工具依赖(Go 1.24+)
golang.org/x/tools/gopls@latest
github.com/go-critic/go-critic/cmd/gocritic@latest
)

tool 指令(Go 1.24+)

Go 1.24 引入了 tool 指令,用于跟踪可执行依赖。这是管理开发工具依赖的官方方式,替代了之前常用的 "tools.go" 模式。

传统方式的问题

在 Go 1.24 之前,开发者通常使用一个特殊的 tools.go 文件来管理工具依赖:

//go:build tools
// +build tools

package tools

import (
_ "golang.org/x/tools/gopls"
_ "github.com/go-critic/go-critic/cmd/gocritic"
)

这种方式有几个缺点:

  • 需要创建额外的文件
  • 语法不够直观
  • 容易忘记添加构建标签

tool 指令的使用

# 添加工具依赖
go get -tool golang.org/x/tools/gopls@latest

# 这会在 go.mod 中添加 tool 指令

生成的 go.mod 文件:

module example.com/myproject

go 1.24

require golang.org/x/tools/gopls v0.15.0

tool golang.org/x/tools/gopls@latest

使用工具

# 使用 go tool 运行工具
go tool gopls

# 运行所有工具(meta-pattern)
go tool tool

# 安装工具到 GOBIN
go install tool

tool 指令的优势

  1. 官方支持:无需创建额外的文件
  2. 版本跟踪:工具版本与模块依赖统一管理
  3. 便于安装:一键安装所有开发工具
  4. 更新方便go get -u tool 更新所有工具

常见用例

// go.mod
module example.com/myproject

go 1.24

tool (
golang.org/x/tools/gopls@latest // Go 语言服务器
github.com/go-critic/go-critic/cmd/gocritic@latest // 代码检查
github.com/golangci/golangci-lint/cmd/golangci-lint@latest
honnef.co/go/tools/cmd/staticcheck@latest
)

更新所有工具

# 更新所有工具到最新版本
go get tool

# 安装所有工具到 GOBIN 目录
go install tool

go 指令详解

go 指令指定模块所需的最低 Go 版本。从 Go 1.21 开始,这个指令是强制性的:

go 1.22.0

go 指令的作用

  1. 版本要求:指定编译此模块所需的最低 Go 版本

  2. 语言特性控制

    • 编译器会拒绝使用该版本之后引入的语言特性
    • 例如,如果 go 1.12,则不能使用 1_000_000 这种数字字面量(Go 1.13 引入)
  3. 行为变化

    • go 1.14+:自动启用 vendor 模式
    • go 1.16+all 模式只匹配主模块传递导入的包
    • go 1.17+:在 go.mod 中显式记录间接依赖
    • go 1.21+:版本要求变为强制性

require 指令

require 声明依赖要求,指定最低版本:

// 单个依赖
require github.com/gin-gonic/gin v1.9.1

// 多个依赖(推荐格式)
require (
github.com/gin-gonic/gin v1.9.1
github.com/stretchr/testify v1.8.4
)

// 间接依赖(自动添加)
require (
golang.org/x/crypto v0.14.0 // indirect
)

直接依赖 vs 间接依赖

  • 直接依赖:模块中代码直接 import 的包
  • 间接依赖:被直接依赖所依赖的包(标记 // indirect

exclude 指令

exclude 排除特定版本的依赖,防止被使用:

exclude (
example.com/old/thing v1.2.3 // 排除特定版本
example.com/broken v2.0.0 // 这个版本有严重 bug
)

使用场景

  • 某个依赖版本存在安全漏洞
  • 某个依赖版本与其他依赖不兼容
  • 某个依赖版本有严重的 bug

注意事项

  • exclude 只在主模块的 go.mod 中生效
  • 被排除的版本不会影响版本选择算法

replace 指令

replace 用另一个模块路径或本地路径替换原始依赖:

// 替换为其他模块版本
replace example.com/bad/thing v1.4.5 => example.com/good/thing v1.4.5

// 替换为本地路径(开发调试常用)
replace (
example.com/mylib => ../mylib
golang.org/x/crypto => /home/user/x/crypto
)

// 替换特定版本
replace example.com/mod v1.0.0 => example.com/mod v1.0.1

常见使用场景

  1. 本地开发:同时开发多个相互依赖的模块
  2. Fork 修复:使用自己 fork 的版本等待上游合并
  3. 版本修复:使用修复版本的依赖
  4. 私有模块:将公共模块重定向到私有仓库

retract 指令(Go 1.16+)

retract 用于撤回已发布的版本,标记某些版本不应被使用:

// 撤回单个版本
retract v1.9.0

// 撤回版本范围
retract [v1.9.0, v1.9.5]

// 带说明的撤回
retract (
v1.0.0 // 发布后发现有严重 bug
[v1.1.0, v1.1.3] // 这些版本不兼容 go.mod 变化
)

为什么需要 retract?

  • 发现严重 bug 或安全漏洞
  • 意外发布的版本(如误操作)
  • 版本不兼容问题
  • 版本号错误(如本应是 v2.0.0 却发布了 v1.9.0)

retract 的工作方式

  1. 当用户执行 go getgo list -m -u 时,go 命令会检查撤回信息
  2. 如果尝试使用被撤回的版本,会显示警告信息
  3. 撤回的版本在版本选择中会被降权

godebug 指令(Go 1.23+)

godebug 指令用于在模块级别设置 GODEBUG 选项:

// 单个设置
godebug http2client=0

// 多个设置
godebug (
http2client=0
tls13=1
)

效果:当此模块作为主模块时,这些 GODEBUG 设置会自动应用,相当于在编译时为每个 main 包添加 //go:debug 指令。

语义版本控制

Go 模块遵循语义版本控制(Semantic Versioning 2.0.0),版本号格式为 vX.Y.Z

v1.2.3
│ │ │
│ │ └── 补丁版本(Patch):bug 修复
│ └──── 小版本(Minor):新功能,向后兼容
└────── 大版本(Major):不兼容的变更

版本比较规则

v1.2.3 < v1.2.4 < v1.3.0 < v2.0.0
  • 大版本、小版本、补丁版本从左到右比较
  • 大版本为 0 或有预发布后缀的版本被视为不稳定版本

预发布版本

v1.2.3-alpha    // 预发布版本
v1.2.3-beta.1 // 带编号的预发布版本
v1.2.3-rc.1 // 候选发布版本

预发布版本排序在对应正式版本之前:

v1.2.3-alpha < v1.2.3-beta < v1.2.3-rc.1 < v1.2.3

特殊后缀

v2.0.0+incompatible  // 迁移到模块前发布的版本(不兼容)
v1.2.3+meta // 构建元数据(比较时忽略)
v1.0.0+dirty // 包含未提交更改(Go 1.24+)

主版本后缀

从大版本 2 开始,模块路径必须包含主版本后缀:

// v1.x.x 版本
module example.com/mod

// v2.x.x 及以上版本
module example.com/mod/v2

// v3.x.x 及以上版本
module example.com/mod/v3

为什么需要主版本后缀?

这实现了导入兼容性规则:如果旧包和新包有相同的导入路径,新包必须向后兼容旧包。

大版本升级意味着不兼容的变更。通过主版本后缀,不同大版本的包有不同的导入路径,可以同时存在:

import (
"example.com/mod" // v1.x.x
modv2 "example.com/mod/v2" // v2.x.x,需要别名避免冲突
)

主版本升级示例

v1 版本 (go.mod):

module example.com/mod

go 1.22

v2 版本 (go.mod):

module example.com/mod/v2

go 1.22

使用不同版本:

package main

import (
"example.com/mod" // v1.x.x
modv2 "example.com/mod/v2" // v2.x.x
)

func main() {
mod.OldFunction() // v1 API
modv2.NewFunction() // v2 API
}

伪版本(Pseudo-versions)

当依赖特定提交(没有对应的语义版本标签)时,Go 使用伪版本:

vX.0.0-yyyymmddhhmmss-abcdefabcdef
│ │ │
│ │ └── 12位提交哈希前缀
│ └───────────────── UTC 时间戳
└───────────────────────── 基础版本

伪版本的三种形式

// 形式一:无已知基础版本
v0.0.0-20191109021931-daa7c04131f5

// 形式二:基础版本是预发布版本
v1.2.3-pre.0.20191109021931-daa7c04131f5

// 形式三:基础版本是正式版本
v1.2.4-0.20191109021931-daa7c04131f5

伪版本的用途

  • 引用尚未打标签的提交
  • 测试开发中的代码
  • 使用特定的 bug 修复提交
# 使用特定提交
go get example.com/mod@daa7c041

# 使用分支名
go get example.com/mod@main

# 结果:自动转换为伪版本
// go.mod
require example.com/mod v0.0.0-20191109021931-daa7c04131f5

依赖管理操作

添加依赖

# 添加最新版本
go get github.com/gin-gonic/gin

# 添加特定版本
go get github.com/gin-gonic/[email protected]

# 添加特定提交
go get github.com/gin-gonic/gin@abc123

# 添加分支最新
go get github.com/gin-gonic/gin@main

# 添加工具依赖(Go 1.24+)
go get -tool golang.org/x/tools/gopls@latest

# 添加多个工具依赖
go get -tool honnef.co/go/tools/cmd/staticcheck@latest

更新依赖

# 更新到最新版本
go get github.com/gin-gonic/gin@latest

# 更新到最新的小版本
go get github.com/gin-gonic/[email protected]

# 更新所有依赖
go get -u
go get -u=patch // 只更新补丁版本

# 更新所有依赖到最新
go get -u all

移除依赖

# 移除未使用的依赖
go mod tidy

# 移除特定依赖
go mod edit -droprequire=example.com/mod

查看依赖

# 查看依赖列表
go list -m all

# 查看依赖更新
go list -m -u all

# 查看依赖详情
go list -m -json github.com/gin-gonic/gin

# 查看依赖图
go mod graph

# 查看为什么需要某个依赖
go mod why github.com/gin-gonic/gin

go mod tidy

go mod tidy 是最常用的依赖管理命令:

# 整理依赖(添加缺失的,移除未使用的)
go mod tidy

# 只查看差异,不修改文件(Go 1.23+)
go mod tidy -diff

tidy 做什么

  1. 添加源代码中 import 但 go.mod 中缺失的依赖
  2. 移除 go.mod 中不再使用的依赖
  3. 更新 go.sum 文件

go.sum 文件

go.sum 记录了所有依赖的校验和,用于验证下载的模块未被篡改:

github.com/gin-gonic/gin v1.9.1 h1:4idEAncQsUjtTtTtT8F...
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7Y...

每行格式

<模块路径> <版本> <校验和>
<模块路径> <版本>/go.mod <校验和>

两条记录

  • 第一条:模块内容的校验和
  • 第二条:go.mod 文件的校验和

重要规则

  • go.sum 必须提交到版本控制系统
  • 不要手动编辑 go.sum
  • 校验失败会阻止构建

模块代理

Go 支持通过模块代理下载依赖,提高下载速度和可靠性。

GOPROXY 配置

# 默认配置(Go 1.13+)
GOPROXY=https://proxy.golang.org,direct

# 常用配置
GOPROXY=https://goproxy.cn,direct # 国内常用
GOPROXY=https://goproxy.io,direct # 国内常用
GOPROXY=https://mirrors.tencent.com/go/,direct

# 多代理配置(按顺序尝试)
GOPROXY=https://proxy.golang.org,https://goproxy.cn,direct

# 禁用代理
GOPROXY=direct

# 完全离线
GOPROXY=off

代理值说明

含义
URL使用指定的代理服务器
direct直接从源站下载
off禁止下载

常用代理服务器

代理URL提供方
官方代理https://proxy.golang.orgGoogle
七牛云https://goproxy.cn七牛云
阿里云https://mirrors.aliyun.com/goproxy/阿里云
腾讯云https://mirrors.tencent.com/go/腾讯云

模块代理协议

Go 模块代理遵循特定协议,支持以下请求:

$GOPROXY/<module>/@v/list            # 列出所有版本
$GOPROXY/<module>/@v/<version>.info # 版本信息
$GOPROXY/<module>/@v/<version>.mod # go.mod 文件
$GOPROXY/<module>/@v/<version>.zip # 模块源码
$GOPROXY/<module>/@latest # 最新版本信息

私有模块配置

对于私有仓库或公司内部的模块,需要特殊配置:

GOPRIVATE

# 设置私有模块路径(跳过代理和校验和验证)
GOPRIVATE=gitlab.mycompany.com,github.com/mycompany/*

# 常见配置
GOPRIVATE=*.internal.company.com,gitlab.company.com

GOPRIVATE 同时设置了 GONOPROXYGONOSUMDB

GONOPROXY

# 指定不使用代理的模块
GONOPROXY=gitlab.mycompany.com

GONOSUMDB

# 指定不验证校验和的模块
GONOSUMDB=gitlab.mycompany.com

GOAUTH 环境变量(Go 1.24+)

Go 1.24 引入了 GOAUTH 环境变量,提供灵活的私有模块认证方式:

# 基本语法
GOAUTH=off # 禁用认证
GOAUTH=gitlab.com=token.txt # 为特定域名指定认证文件

# 使用 .netrc 格式文件
GOAUTH=$HOME/.netrc

# 多个认证源
GOAUTH=gitlab.com=token.txt,github.com=github_token.txt

认证文件格式

# token.txt(Bearer Token 格式)
Bearer ghp_xxxxxxxxxxxxxxxxxxxx

# 或者直接写入 token
ghp_xxxxxxxxxxxxxxxxxxxx

使用场景

# 配置 GitLab 私有仓库访问
export GOAUTH=gitlab.mycompany.com=$HOME/.gitlab_token

# 配置多个私有仓库
export GOAUTH=gitlab.company.com=$HOME/.gitlab_token,github.myenterprise.com=$HOME/.github_token

# 下载私有模块
go get gitlab.mycompany.com/myteam/private-module

与 .netrc 的配合使用

# ~/.netrc
machine gitlab.mycompany.com
login oauth2
password ghp_xxxxxxxxxxxxxxxxxxxx

# 配置 GOAUTH 使用 .netrc
export GOAUTH=$HOME/.netrc

优势

  • 不需要在 URL 中嵌入凭证
  • 支持多种认证方式(Bearer Token、Basic Auth)
  • 可以按域名配置不同的认证源
  • 与现有工具链无缝集成

配置建议

# ~/.bashrc 或 ~/.zshrc
export GOPROXY=https://goproxy.cn,https://proxy.golang.org,direct
export GOPRIVATE=gitlab.company.com,*.internal.company.com
export GOSUMDB=sum.golang.org

使用 .netrc 认证

对于需要认证的私有仓库:

# ~/.netrc
machine gitlab.company.com
login your-username
password your-token

或者使用 Git 配置:

git config --global url."https://user:[email protected]".insteadOf "https://gitlab.company.com"

模块废弃

模块作者可以标记模块为废弃状态:

// Deprecated: use example.com/mod/v2 instead.
module example.com/mod

废弃信息的使用

  • go list -m -u 会检查所有依赖的废弃信息
  • go get 会显示废弃警告
  • 废弃信息从 @latest 版本的 go.mod 中读取

版本选择算法

Go 使用**最小版本选择(Minimal Version Selection, MVS)**算法来选择依赖版本:

基本原则

  1. 选择满足所有约束的最小版本
  2. 不是选择最新版本,而是选择"刚好够用"的版本

示例

模块 A 要求:example.com/lib >= v1.2.0
模块 B 要求:example.com/lib >= v1.3.0

结果:选择 v1.3.0(满足所有要求的最小版本)

MVS 的优势

  1. 可重现构建:不同开发者得到相同结果
  2. 避免意外升级:不会自动使用更高版本
  3. 简单明了:规则简单,易于理解

项目结构

推荐的目录结构

myproject/
├── go.mod # 模块定义
├── go.sum # 依赖校验
├── README.md # 项目说明
├── LICENSE # 许可证

├── cmd/ # 可执行命令
│ └── myapp/
│ └── main.go # 主程序入口

├── internal/ # 内部包(不能被外部导入)
│ ├── handler/ # HTTP 处理器
│ ├── service/ # 业务逻辑
│ └── repository/ # 数据访问层

├── pkg/ # 可被外部导入的包
│ ├── utils/ # 工具函数
│ └── config/ # 配置处理

├── api/ # API 定义(OpenAPI 等)
├── test/ # 测试数据和脚本
├── docs/ # 文档
└── scripts/ # 脚本文件

cmd 目录约定

cmd 目录存放可执行程序的入口点:

// cmd/myapp/main.go
package main

import (
"github.com/myproject/internal/handler"
"github.com/myproject/pkg/config"
)

func main() {
cfg := config.Load()
handler.Start(cfg)
}

这种结构的好处:

  1. 多个命令行工具可以共享内部代码
  2. 明确区分库代码和入口代码
  3. 便于 go install 安装
# 安装特定命令
go install github.com/myproject/cmd/myapp@latest

工作区 (Workspace) 模式

Go 1.18 引入了工作区模式,方便同时开发多个模块。

初始化工作区

# 创建工作区
go work init ./module1 ./module2

# 添加模块到工作区
go work use ./module3

这会创建 go.work 文件:

go 1.22

use (
./module1
./module2
)

replace example.com/mod => ./local/mod

工作区的使用场景

  1. 同时开发多个相关模块
  2. 本地测试未发布的依赖
  3. 解决依赖冲突

go.work 文件

go 1.22

use (
./myapp
./mylib // 本地开发的库
)

replace (
example.com/remote => ./local/remote
)

注意go.work 文件通常不提交到版本控制,因为它是本地开发配置。

添加到 .gitignore:

go.work
go.work.sum

常用命令

模块操作

# 初始化模块
go mod init example.com/myproject

# 整理依赖
go mod tidy

# 下载依赖
go mod download

# 验证依赖
go mod verify

# 编辑 go.mod
go mod edit -require=example.com/[email protected]
go mod edit -droprequire=example.com/mod

# 复制依赖到 vendor
go mod vendor

# 查看模块图
go mod graph

# 解释为什么需要依赖
go mod why example.com/mod

# 运行工具(Go 1.24+)
go tool gopls # 运行特定工具
go tool tool # 更新并安装所有工具

# 添加工具依赖(Go 1.24+)
go get -tool golang.org/x/tools/gopls@latest

构建和运行

# 构建当前模块
go build
go build -o myapp

# 构建时嵌入版本信息(Go 1.24+)
# go build 会自动从 git 标签和提交设置主模块版本
# 如果有未提交的更改,会添加 +dirty 后缀
go build -o myapp

# 禁用版本控制信息嵌入
go build -buildvcs=false -o myapp

# 运行
go run main.go
go run ./cmd/myapp

# 测试
go test ./...
go test -race ./...

# 安装
go install ./cmd/myapp

最佳实践

1. 模块路径命名

# 个人或小项目
module github.com/username/projectname

# 组织
module github.com/organization/projectname

# 公司内部
module gitlab.company.com/team/projectname

2. 版本标签规范

# 发布新版本前打标签
git tag v1.0.0
git push origin v1.0.0

# 大版本升级
git tag v2.0.0
git push origin v2.0.0

3. 工具依赖管理(Go 1.24+)

使用 tool 指令管理开发工具:

// go.mod
tool (
golang.org/x/tools/gopls@latest
honnef.co/go/tools/cmd/staticcheck@latest
)
# 安装所有工具
go install tool

# 更新所有工具
go get tool

这比传统的 tools.go 方式更简洁:

// 不再需要这个文件
//go:build tools
package tools

import (
_ "golang.org/x/tools/gopls"
)

3. 依赖管理建议

  1. 使用明确的版本:避免使用 @latest@master 在生产环境
  2. 定期更新依赖
    go list -m -u all  # 查看可用更新
    go get -u # 更新依赖
  3. 定期运行 tidy
    go mod tidy
  4. 提交 go.sum:确保构建可重现

4. 代码组织建议

  1. 小包原则:保持包小而专注
  2. 命名一致:包名与目录名一致
  3. 合理分层
    cmd/     -> 入口点
    internal/ -> 内部实现
    pkg/ -> 公共库
  4. 避免循环依赖:使用接口解耦

5. 发布模块

# 1. 确保代码质量
go test ./...
go vet ./...

# 2. 更新版本
# 编辑代码...

# 3. 提交更改
git add .
git commit -m "release v1.1.0"

# 4. 打标签
git tag v1.1.0
git push origin main
git push origin v1.1.0

# 5. 添加到代理索引
# 通常代理会自动发现,也可以手动触发
curl https://sum.golang.org/lookup/example.com/[email protected]

6. 处理依赖问题

依赖冲突

# 查看依赖图
go mod graph | grep problematic-module

# 使用 replace 解决
replace problematic-module => fixed-module v1.0.0

版本不兼容

# 查看为什么需要某个版本
go mod why example.com/mod

# 强制使用特定版本
go get example.com/[email protected]

小结

  1. 是代码组织的基本单位,通过首字母大小写控制导出
  2. 模块是依赖管理单元,由 go.mod 定义
  3. 语义版本控制确保版本号的含义明确
  4. 主版本后缀让不同大版本可以共存
  5. go.mod 指令(require、replace、exclude、retract)提供灵活的依赖管理
  6. 合理使用 internal 目录增强封装性
  7. 工作区模式方便本地多模块开发
  8. 遵循最佳实践可以避免常见的依赖管理问题

练习

  1. 创建一个新模块,添加几个依赖,使用 go mod tidy 整理
  2. 创建 internal 包和一个 pkg 包,验证内部包的访问限制
  3. 创建一个工作区,同时开发两个相互依赖的模块
  4. 编写一个包含 init 函数的包,理解其执行时机
  5. 发布一个模块到 GitHub,尝试从另一个模块引用它

参考资源