主题
第10章 面试题精选 —— 包管理与 Go Module
★ 基础题
Q1:Go 的 package 是什么?
答:一个目录下所有 .go 文件构成一个 package(包)。同一目录的所有源文件必须属于同一个 package。
package main + func main() = 可执行程序;其他名字是库。
Q2:Go 怎么控制可见性?没有 public/private 吗?
答:靠首字母大小写。
| 写法 | 可见性 |
|---|---|
Foo、Add | 包外可见(导出) |
foo、add | 仅包内可见 |
适用于:函数、类型、变量、常量、struct 字段、接口方法名等所有标识符。
Q3:GOPATH 和 Go Module 有什么区别?
| GOPATH(旧) | Go Module(新) | |
|---|---|---|
| 项目位置 | 必须在 $GOPATH/src | 任何位置 |
| 依赖管理 | 全局共享 | 项目级隔离 |
| 版本管理 | 不支持 | 支持(语义化版本) |
| 配置文件 | 无 | go.mod + go.sum |
Go 1.11 引入 module,1.16 起默认开启,
GOPATH模式已基本淘汰。
Q4:go.mod 和 go.sum 的作用?
- go.mod:声明 module 名、Go 版本、依赖列表(require / replace / exclude)
- go.sum:每个依赖的内容哈希,防止依赖被篡改(类似 npm 的 package-lock.json)
两者都要提交到 Git。
Q5:怎么初始化一个新项目?
bash
mkdir myapp && cd myapp
go mod init github.com/yourname/myapp
go get github.com/gin-gonic/gin
go run .★★ 进阶题
Q6:go mod tidy 做了什么?
- 扫描代码里的
import,自动添加缺失的依赖到go.mod - 删除
go.mod里有但代码不再使用的依赖 - 更新
go.sum,删除多余条目
经验:每次提交代码前跑一次,保持 go.mod 干净。
Q7:直接依赖 vs 间接依赖(indirect)?
require (
github.com/gin-gonic/gin v1.9.1
)
require (
github.com/bytedance/sonic v1.10.2 // indirect
)- 直接依赖:你的代码 import 的
- 间接依赖(indirect):你的依赖 又依赖的库;或某个间接依赖的版本被你显式提升过
go mod tidy 会自动维护 indirect 标记。
Q8:什么是 internal 包?
放在 internal/ 目录里的包只能被该 internal 父目录及其子目录里的代码 import。
myapp/
├── internal/secret/ ← 私有包
├── cmd/main.go ✅ 可以 import
└── ...外部项目 otherapp 如果 import myapp/internal/secret → 编译报错。
这是 Go 内置的"私有化"机制,比注释更可靠。
Q9:怎么在不发布的情况下用本地修改的依赖?
用 replace:
// go.mod
require github.com/some/lib v1.0.0
replace github.com/some/lib => ../my-fork或替换成另一个 module:
replace github.com/old/lib => github.com/new/lib v2.0.0Q10:Go Module 主版本号 ≥ 2 时怎么处理 import 路径?
强制要求 import 路径带 /v2、/v3 后缀:
go
import "github.com/some/lib/v2"并且在 v2 分支的 go.mod 里:
module github.com/some/lib/v2这是 Go 的 SIV(Semantic Import Versioning) 设计:把不兼容版本视为不同的包,可以同时依赖 v1 和 v2。
★★★ 深度题
Q11:init 函数的执行顺序是怎样的?
- 被依赖的包先 init(深度优先)
- 同一包的多个文件按文件名字典序依次 init
- 同一文件内多个 init 按出现顺序
- 所有 init 执行完,才进入
main()
依赖图:main → cache → db → config
执行:
config.init() → db.init() → cache.init() → main.init() → main()Q12:你怎么解决依赖冲突(同一个包被引到多个版本)?
Go 的 MVS(Minimum Version Selection)算法:选出所有 require 中要求版本的最大值。
举例:
A 要 lib v1.2.0
B 要 lib v1.5.0
C 要 lib v1.3.0MVS 选 v1.5.0(满足所有人要求的最小公倍数)。
如果遇到不兼容的版本,可用:
replace强制替换- 升级到 v2 主版本(路径不同,可共存)
Q13:vendor/ 目录是什么?什么时候用?
go mod vendor 把所有依赖拷贝到项目根目录的 vendor/。
好处:
- 离线构建
- 避免依赖源被删除的风险
- CI 加速(不必每次拉取)
坏处:
- 仓库变大
- 升级依赖需要重跑 vendor
默认
go build在有 vendor/ 时优先用 vendor,可用-mod=mod强制走网络。
Q14:go get 在 1.16 前后行为有何区别?
- 1.16 前:
go get pkg既"安装"又"修改 go.mod" - 1.16 起:
go get只负责依赖管理;安装命令独立成go install pkg@version
最佳实践:
- 添加/升级依赖 →
go get - 安装可执行工具 →
go install
Q15:你怎么发布一个开源 Go 库?
- 代码放 GitHub(如
github.com/me/awesome) go mod init github.com/me/awesome- 写代码、加测试
- 打 git tag:
git tag v1.0.0 && git push --tags - 用户就能
go get github.com/me/awesome@v1.0.0 - 通过 pkg.go.dev 自动展示文档
主版本升到 v2 时:
go.mod改为module github.com/me/awesome/v2- 用户 import 路径加
/v2
Q16:GOPROXY=https://goproxy.cn,direct 是什么意思?
依次尝试两个源:
https://goproxy.cn:七牛云 Go 模块代理(国内加速)direct:直接从源仓库(如 GitHub)拉取
,direct 表示前一个失败时直连,避免私有仓库被代理拦截。
私有仓库通常配合 GOPRIVATE:
go env -w GOPRIVATE=*.mycompany.com📌 速记
package 是文件夹;import 是引用;首字母决定可见;go.mod 管依赖;internal 锁权限。
🎯 自测练习
- 创建一个 module,里面定义
mathx包,main 调用它 - 写一个
internal/secret包,让外部项目无法 import - 用
go mod why <pkg>找出某个依赖为什么被引入 - 故意让
go.mod缺一个依赖,看go mod tidy怎么修 - 配置
GOPROXY为国内镜像
进入第11章 →