Skip to content

第10章 面试题精选 —— 包管理与 Go Module


★ 基础题

Q1:Go 的 package 是什么?

:一个目录下所有 .go 文件构成一个 package(包)。同一目录的所有源文件必须属于同一个 package。

package main + func main() = 可执行程序;其他名字是库。


Q2:Go 怎么控制可见性?没有 public/private 吗?

:靠首字母大小写

写法可见性
FooAdd包外可见(导出)
fooadd仅包内可见

适用于:函数、类型、变量、常量、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 做了什么?

  1. 扫描代码里的 import,自动添加缺失的依赖到 go.mod
  2. 删除 go.mod 里有但代码不再使用的依赖
  3. 更新 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.0

Q10: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 函数的执行顺序是怎样的?

  1. 被依赖的包先 init(深度优先)
  2. 同一包的多个文件按文件名字典序依次 init
  3. 同一文件内多个 init 按出现顺序
  4. 所有 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.0

MVS 选 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 库?

  1. 代码放 GitHub(如 github.com/me/awesome
  2. go mod init github.com/me/awesome
  3. 写代码、加测试
  4. 打 git tag:git tag v1.0.0 && git push --tags
  5. 用户就能 go get github.com/me/awesome@v1.0.0
  6. 通过 pkg.go.dev 自动展示文档

主版本升到 v2 时:

  1. go.mod 改为 module github.com/me/awesome/v2
  2. 用户 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 锁权限。


🎯 自测练习

  1. 创建一个 module,里面定义 mathx 包,main 调用它
  2. 写一个 internal/secret 包,让外部项目无法 import
  3. go mod why <pkg> 找出某个依赖为什么被引入
  4. 故意让 go.mod 缺一个依赖,看 go mod tidy 怎么修
  5. 配置 GOPROXY 为国内镜像

进入第11章 →