CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Go 语言入门/课时/模块与包管理
RawBlame
课时目录12 节

Go 语言入门

  1. 01Go 是什么
  2. 02基础语法与类型
  3. 03数组、切片与 map
  4. 04结构体与接口
  5. 05模块与包管理
  6. 06并发模型
  7. 07错误处理
  8. 08泛型入门
  9. 09I/O 与 CLI
  10. 10Web 与数据库
  11. 11测试与基准
  12. 12综合练习

模块与包管理

go mod、packages、vendor、go build/run。

30 分钟

模块与包管理

这一课你将能做到

  1. 用 go mod init 创建模块,说出 go.mod 每一行的含义
  2. 把项目拆成多个包,正确运用「首字母大写导出、小写包内私有」规则和导入路径
  3. 熟练使用 go get、go mod tidy、go build、go run、go vet 等日常命令

为什么要学

前面的代码都是单文件单包,玩具阶段。真实项目一定是多包目录 + 若干第三方依赖:net/http 封装在哪个包、别人的库怎么引入、版本冲突谁说了算、为什么别人的包你能 import 而有的不行——这些全部由 Go 的模块系统(go modules,Go 1.11+ 引入,1.16 起默认开启)管理。

包(package)是代码组织单位,模块(module)是依赖与版本单位。理清这两层,项目结构、依赖管理、团队协作就都有了秩序。

先建立图像

一个典型 Go 项目的目录树与它的模块文件:

calculator/                     ← 项目根目录 = 模块根
├── go.mod                      ← 模块身份证 + 依赖清单
├── go.sum                      ← 依赖内容的校验清单(哈希)
├── main.go                     ← package main,程序入口
└── pkg/
    └── ops/
        └── ops.go              ← package ops,业务子包

go.mod 内容:
module example.com/calculator   ← 模块路径:别人 import 你时的前缀

go 1.22                          ← 语言版本要求

require (
	github.com/google/uuid v1.6.0   ← 依赖 + 精确版本
)

导入路径 = 模块路径 + 子目录。比如 example.com/calculator/pkg/ops。

跟着做

步骤 1:创建模块

$ mkdir calculator && cd calculator
$ go mod init example.com/calculator
go: creating new go.mod: module example.com/calculator

模块路径通常写「域名 + 路径」(方便将来发布到 GitHub);本地练手随便起一个不冲突的即可。打开 go.mod 确认内容。

步骤 2:拆出第一个子包

创建 pkg/ops/ops.go:

package ops // 包名惯例:小写、单个单词、与目录名一致

import "fmt"

// Add 首字母大写 → 导出(公开),任何包都能调用
func Add(a, b int) int {
	return a + b
}

// multiply 首字母小写 → 未导出(私有),只在 ops 包内可见
func multiply(a, b int) int {
	return a * b
}

// Double 是包内函数,演示私有函数的用法
func Double(a int) int {
	return multiply(a, 2)
}

func init() {
	// init 函数:包被导入时自动执行,可执行初始化
	fmt.Println("ops 包已加载")
}

步骤 3:在 main 中导入并调用

项目根目录创建 main.go:

package main

import (
	"fmt"

	"example.com/calculator/pkg/ops" // 导入路径 = 模块路径 + 子目录
)

func main() {
	fmt.Println(ops.Add(1, 2)) // 3
	fmt.Println(ops.Double(5)) // 10
	// fmt.Println(ops.multiply(2, 3)) // 编译错误:multiply 未导出
}

运行:

$ go run .
ops 包已加载
3
10

两点注意:导入未使用的包是编译错误;init() 在 main 之前自动执行。

步骤 4:添加第三方依赖

引入一个最常见的库 github.com/google/uuid:

$ go get github.com/google/uuid
go: added github.com/google/uuid v1.6.0

使用它:

package main

import (
	"fmt"

	"github.com/google/uuid"
)

func main() {
	id := uuid.NewString()
	fmt.Println("生成的 UUID:", id)
}

再观察 go.mod——require 块自动多了一行,同时多出了 go.sum(记录每个依赖模块的哈希,用于校验内容未被篡改)。删掉 go.mod 里的依赖后再执行 go mod tidy,它会自动增删 require 条目,保持「代码里 import 的」和「go.mod 里声明的」完全一致。

步骤 5:常用命令清单

$ go mod init <路径>      # 初始化模块
$ go get <包>@<版本>      # 添加/升级依赖,@latest 拿最新
$ go mod tidy            # 整理依赖:补齐缺失、清除多余
$ go mod vendor          # 把依赖拷进 vendor/ 目录(离线构建用)
$ go run .               # 编译并运行当前包
$ go build ./...         # 编译所有包;-o 指定输出文件
$ go fmt ./...           # 格式化
$ go vet ./...           # 静态检查可疑代码
$ go test ./...          # 跑测试(第 11 课展开)

./... 读作「当前目录及所有子目录」,是 Go 命令行最常见的通配符。

步骤 6:交叉编译与版本管理

$ $env:GOOS="linux"; go build -o app .          # PowerShell 下产出 Linux 二进制
$ go get github.com/google/uuid@v1.3.1          # 锁定旧版本
$ go list -m all                                # 列出全部依赖的最终版本

Go 用「最小版本选择(MVS)」:每个依赖固定在你声明的版本,不会像 npm 那样自动飘到最新次版本,构建高度可复现。

步骤 7:GOPROXY 与 vendor

$ go env GOPROXY                                # 查看当前代理
$ go env -w GOPROXY=https://goproxy.cn,direct   # 国内镜像(第 1 课已配)
$ go mod vendor                                 # 生成 vendor 目录
$ go build -mod=vendor ./...                    # 用 vendor 构建,不联网

vendor 适合 CI 离线环境或对供应链有强管控的团队;一般项目靠 go.sum 校验 + 模块代理即可。

讲清楚要点

  1. package 是编译与代码组织单位,module 是依赖与版本单位:一个模块包含一个或多个包;一个目录只能有一个包。
  2. 导出规则只有一条:标识符首字母大写即可被包外访问,小写则包内私有。没有 public/private 关键字。
  3. 导入路径由 go.mod 决定,与磁盘上的 $GOPATH 无关;项目放在任何目录都可以。
  4. internal 目录是语言级私有机制:example.com/app/internal/... 只能被 example.com/app/... 内的代码导入,第三方项目 import 会直接编译报错。
  5. go.mod 声明依赖,go.sum 校验依赖:前者你手动维护,后者永远自动生成,禁止手改。
  6. 循环导入是编译错误:A 导 B、B 导 A 不允许,需要抽公共包或调整层次。

你可能会懵的地方

  • 模块路径里的域名是真的吗?我要先注册域名吗? 不用。go get 时代码才需要真实可拉取;本地模块和私有项目随便写路径(如 example.com/myapp)。发到 GitHub 后惯例改成 github.com/你/仓库名。

  • go.mod 和 go.sum 的区别? go.mod 是依赖清单(要哪些、什么版本),go.sum 是内容校验表(每个版本的哈希),防止下载被篡改。go.sum 会随 go get/tidy 自动更新。

  • 包名一定要和目录名一致吗? 惯例一致,编译只认 package 声明行;但强烈保持一致,否则 import 路径和引用名对不上,团队会骂人。

  • 为什么有人 import 前面加下划线 _ "net/http/pprof"? 空白导入:不直接使用包,只为触发它的 init()(副作用),比如注册驱动、注册路由。数据库驱动章节会再见到它。

  • 依赖出现两个版本怎么办? Go 允许不同模块各自依赖同一库的不同大版本(v2+ 路径不同),MVS 自动选满足所有要求的最低版本,通常无感知。

错法对照

错法问题正解
导入了包但没用imported and not used 编译错误删除导入,或 _ "pkg" 空白导入
所有代码堆在 main 包无法复用、无法测试按职责拆子包,main 只做组装
手动编辑 go.sum校验失败构建报废go.sum 只由 go 命令自动维护
import 路径写相对路径 import "./pkg/ops"Go 不支持相对导入写完整模块路径
忘了 go mod init 直接 go run报 go.mod file not found先 init 再写代码

卡点急救

  1. no required module provides package ...:依赖没进 go.mod,执行 go mod tidy 自动补齐。
  2. module example.com/x: reading ...: connection refused:网络问题,配置 GOPROXY 国内镜像。
  3. package xxx is not in std:导入路径拼错,或模块路径与实际目录不匹配;用 go list ./... 核对。
  4. cannot find main module:在项目根目录(有 go.mod 的地方)执行命令。

面试常问

  • go modules 解决了什么问题?与 GOPATH 时代的区别? GOPATH 时代所有项目必须放在固定目录、依赖没有版本概念(go get 拉 master)。modules 让项目可放任意目录,依赖有精确版本、内容有哈希校验、构建可复现(MVS 最小版本选择)。

  • vendor 目录什么时候用? 需要离线/可重现构建的 CI、对第三方源有强审计要求的组织。日常开发用模块缓存 + 代理更轻。

  • internal 包的机制是什么? 编译器强制约束:internal 目录下的包只能被其父模块路径内的代码导入,用于隐藏内部实现,是语言层面的可见性边界。

  • Go 如何保证依赖供应链安全? go.sum 记录模块内容哈希;GONOSUMDB/GOSUMDB(sum.golang.org)校验数据库交叉验证;vendor 与 GOFLAGS=-mod=mod 等机制配合审计流程。

自测三问

  1. module example.com/app 下有 internal/store 包,同事在另一个仓库 import 它会怎样?
  2. go get、go mod tidy 各做什么?什么时候必须 tidy?
  3. 一个目录下能放两个不同 package 的 .go 文件吗?为什么?

小结一句话

module 用 go.mod 圈定「我是谁、我依赖谁」,package 用首字母大小写划定「谁能看见我」;掌握 init、tidy、internal 和 ./...,就从玩具代码迈进了工程世界。

延伸

  • 官方教程:Go 模块入门
  • Go Modules Reference
  • 下一课:并发模型——goroutine 与 channel,Go 最锋利也最常被问倒的部分。
加载本节练习…
← 上一课结构体与接口下一课 →并发模型