Go 包管理与 go mod — 模块、可见性与标准库
写小程序一个文件够了,但真实工程都是由成百上千个 .go 文件组织起来的。Go 用包(package)来分组代码、控制可见性,用 go mod 来管理依赖。这两套机制比多数语言更简洁,但有几个关键规则必须吃透。这一篇是本系列的收官之作,讲清 Go 工程的组织方式。
一、package 声明
每个 .go 文件第一行(除注释外)必须写 package 名字,声明自己属于哪个包。同一个目录下的所有 .go 文件必须同属一个包(同目录同包是铁律)。其中名字叫 main 的包是特殊的——它表示一个可执行程序,必须有 func main() 作为入口;其他包则是库,被别人 import 使用。一个目录下不能同时有两个不同包名的文件(测试包 xxx_test 例外)。
二、可见性:大小写决定公开/私有
Go 的可见性规则简单到令人感动:首字母大写 = 公开(导出),其他包能访问;首字母小写 = 私有,只有本包能访问。变量、函数、类型、结构体字段全都遵守这一条。没有 public / private 关键字,大小写即一切——这是 Go 用约定代替配置的典型设计。
// 文件位置:user/user.go
package user
// User 结构体:首字母大写的类型和字段是导出的
type User struct {
Name string // 导出字段:外部包可访问
Email string // 导出字段
email string // 私有字段:仅 user 包内可见(未导出)
}
// Greet 首字母大写:外部包可调用
func (u *User) Greet() string {
return "Hi, " + u.Name
}
// normalize 首字母小写:仅 user 包内可调用
func (u *User) normalize() string {
return u.email // 包内能访问私有字段
}三、import 导入
要使用别的包,得先 import。单行用 import "fmt",多个包推荐用括号块写法,工具链会自动按字母排序。包名默认就是路径的最后一段(如 encoding/json 的包名是 json),不需要写全路径。
package main
// 1) 常规导入:多行写法,推荐用括号包起来
import (
"fmt"
"os"
"strings"
)
// 2) 几种特殊写法(单行示例,按需选择一种):
// import f "fmt" // 别名:之后用 f.Println 调用
// import . "strings" // 点导入:省略包名前缀,直接写 ToUpper(...)
// import _ "github.com/lib/pq" // 仅副作用:触发 init,如注册数据库驱动
func main() {
// 大写开头的标识符(如 Println、ToUpper)是导出的,小写则包内私有
fmt.Println(strings.ToUpper("hello")) // HELLO
fmt.Println(len(os.Args)) // 命令行参数个数
}除了常规导入,还有三种特殊写法:
- 别名导入
import f "fmt":给包起短名,常用于避免多个包重名或缩写长路径。 - 点导入
import . "strings":省略包名前缀,直接调ToUpper(...)——不推荐常用,容易污染命名空间,主要用在测试里。 - 下划线导入
import _ "image/png":只为触发该包的init()副作用(比如注册 PNG 解码器),不引用其中的名字。数据库驱动常用这种方式。
四、go mod 模块
自 Go 1.11 起,工程以模块(module)为单位管理依赖。一个模块就是一个目录树,根目录有 go.mod 文件。常用命令:
go mod init module-path— 初始化模块,生成go.mod。go get pkg@version— 添加指定版本的依赖(如go get github.com/gin-gonic/gin@v1.9.1)。go mod tidy— 清理未用依赖、补全缺失依赖,提交前跑一下最稳。go install pkg@latest— 编译并安装可执行文件到$GOBIN(一般在$GOPATH/bin)。
go.mod 大致长这样:模块路径、Go 版本、require 依赖列表。模块路径同时是别人 import 你时的前缀,开源项目通常用 github.com/user/repo 这种形式。go.sum 则记录每个依赖的哈希,保证下次构建用的还是同一份代码,防止供应链篡改——这个文件要提交进 git。
module github.com/me/myapp // 模块路径,也是别人 import 它时的前缀
go 1.22 // 使用的 Go 语言版本
require ( // 直接依赖列表
github.com/gin-gonic/gin v1.9.1
github.com/lib/pq v1.10.9
)五、项目结构示例
一个典型 Go 项目的目录组织大概是这样(目录名就是包的 import 路径最后一段):
myapp/
├── go.mod 模块定义:module + go 版本 + require
├── go.sum 依赖哈希校验,防篡改
├── main.go package main,程序入口
├── user/ 子包:import 路径 .../myapp/user
│ └── user.go package user
├── order/
│ └── order.go package order
└── internal/ internal 特殊目录:只能被 myapp/ 下导入
└── repo/
└── repo.go package repo注意 internal/ 目录是 Go 的特殊约定:它里面的包只能被其父目录及子目录下的包导入——别人想 import "github.com/me/myapp/internal/repo" 会编译失败。这是项目内部实现细节的"防火墙",防止外部代码意外依赖你的私有包。大型项目几乎都会用它来隔离内部逻辑。
package main
import (
"fmt"
"github.com/me/myapp/user" // 导入自己模块下的子包
)
func main() {
u := &user.User{
Name: "Ada",
Email: "ada@example.com",
// email: "x", // 编译错误:email 是私有字段,外部不能赋值
}
fmt.Println(u.Greet()) // Hi, Ada
}六、标准库概览
Go 的标准库异常丰富,很多活儿根本不用引第三方包。先翻标准库再考虑第三方,是写 Go 的好习惯:
- fmt — 格式化 I/O;os — 环境变量、文件、命令行参数。
- io — Reader/Writer 抽象;net/http — 几行起一个 HTTP 服务。
- strings / strconv — 字符串处理与基本类型互转。
- time — 时间日期;encoding/json — JSON 序列化。
- database/sql — 数据库访问;sort — 排序;errors — 错误工具。
七、GOPATH 时代 vs 模块时代
老 Go(1.11 之前)用 GOPATH 环境变量管理一切,所有项目和依赖堆在 ~/go/src 下,版本切换很麻烦,而且要求代码必须放在 GOPATH 里才能编译。模块时代(GO111MODULE=on 从 1.16 起已默认)每个项目自带 go.mod,依赖完全隔离,GOPATH 只剩 bin/ 和 pkg/(模块缓存)。新项目直接用模块即可,不用管 GOPATH。常用环境变量有 GOPATH(默认 ~/go)、GOROOT(Go 安装目录,一般不用设)、GOPROXY(国内常设成 https://goproxy.cn 加速)。
小结
- 每个
.go文件属于一个包;package main是可执行程序入口。 - 首字母大小写决定公开/私有,无
public/private关键字。 - go mod 管理依赖:
init、get、tidy是日常三件套。 internal/包只能被父目录下的代码导入,是项目私有边界。- 标准库极其强大,先翻标准库再考虑第三方。
系列完结!推荐回到 Go 教程目录 复习或继续探索。上一篇 Go Channel 通道。