公共库必须放 pkg/ 而非 internal/,因 internal/ 受 go 工具链硬性限制无法被外部 module 导入;pkg/ 是约定俗成的可导出边界,需配单元测试、文档、无业务耦合及版本承诺;开发阶段用 go mod replace 指向本地路径实现即时验证,但 replace 仅作用于当前模块且 ci 前须删除。

公共库该放在 pkg/ 还是 internal/?
必须放 pkg/。Go 工具链对 internal/ 有硬性限制:任何含 /internal/ 路径的包,都无法被当前模块以外的其他 module 导入。如果你把公共能力(比如通用校验、HTTP 封装、UUID 生成)放进 internal/util,外部项目或兄弟模块引用时会直接报错 import "xxx/internal/util": use of internal package not allowed。
而 pkg/ 是约定俗成的“可导出边界”——它不带语言级限制,但靠命名和文档约束复用意图。例如 pkg/logger、pkg/httpx、pkg/validate 都应具备:
- 完整单元测试(覆盖边界 case)
- 清晰的文档注释(说明适用场景与限制)
- 无业务逻辑耦合(不 import
service/order或internal/config) - 版本兼容性承诺(如 v1.0.x 保持 API 不变)
go mod replace 在公共库开发阶段怎么用?
本地开发时,别急着推 tag 或发 PR,先用 replace 让依赖方即时验证改动。比如你在 pkg/validate 里加了个新函数,想让 service/user 立刻试用:
go mod edit -replace github.com/yourorg/pkg/validate=../pkg/validate
这条命令会往 go.mod 里写一行 replace github.com/yourorg/pkg/validate => ../pkg/validate。效果是:service/user 编译时直接读取本地文件,跳过远程下载和版本锁定。
注意两个坑:
-
replace只作用于当前模块,不会透传给它的下游模块;如果service/user又被cmd/api依赖,cmd/api仍会走远程版本,除非也在自己的go.mod里加replace - CI 构建前必须删掉
replace并go mod tidy,否则构建环境找不到../pkg/validate路径
公共库要不要自己声明 go.mod?
要,而且必须独立声明。哪怕它只是你主项目的子目录,只要目标是被多个模块复用,就得是自治的 module。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
进到 pkg/validate 目录,运行:
go mod init github.com/yourorg/pkg/validate
这会产生一个专属的 go.mod,里面只写它真正需要的依赖(比如 golang.org/x/exp/utf8string),而不是继承主项目的全部依赖。好处很明显:
- 依赖最小化:避免把主项目用的
github.com/gorilla/mux也塞进validate的go.sum - 版本独立演进:你可以为
validate单独发v2.0.0,不影响主项目或其他使用它的模块 - 可测试性提升:能单独
go test ./...,不被主项目初始化逻辑干扰
唯一例外是纯工具函数集合(比如 pkg/slices 仅封装 slice 操作),且确定永远不引入第三方依赖,才可考虑不设 go.mod,直接作为主模块的一部分管理。
当多个业务模块都依赖同一公共库,版本冲突怎么办?
Go 的 MVS(最小版本选择)算法会自动选一个满足所有依赖方要求的版本,但前提是语义化版本(SemVer)用得规范。常见翻车点:
- 公共库发布
v1.2.0后,又打了v1.2.1修复 bug,但没同步更新go.sum校验和 → 下游go build失败,报checksum mismatch - 某业务模块强制
require github.com/yourorg/pkg/validate v1.1.0,而另一模块要求>=v1.2.0→ MVS 选v1.2.0,但前者代码可能调用v1.2.0新增的函数,导致编译失败 - 公共库作者在
v1.2.0里误改了导出函数签名(违反 SemVer 的 patch 版本兼容性)→ 所有依赖它的模块都会 break
解法很直接:公共库每次发版前,跑一遍 go list -m all | grep validate 查哪些模块在用它,再用 go mod graph 看实际解析路径,最后手动验证关键调用点。别信“小版本不破坏兼容性”的直觉,得实测。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










