一个go包该拆了当其deps重复、多人改同一文件但关注点不同,或出现测试变慢、依赖环、无关测试失败等信号;核心是职责混杂导致“修改一处需懂三处”。

怎么判断一个 Go 包该拆了
当 go list -f '{{.Deps}}' ./pkg 输出里反复出现同一组外部依赖,或者 git blame 显示多人频繁修改同一文件但关注点完全不同(比如有人改 HTTP handler,有人调数据库事务),基本可以确认:这个包已经承担了不止一种职责。
常见错误现象:go test ./... 越来越慢、go mod graph 里出现意料之外的环、CI 上某个测试失败却和当前改动完全无关。
关键信号不是代码行数,而是「修改一处,得懂三处」——比如改个日志格式,要翻 logger.go、api/handler.go、service/order.go 才能确保不崩。
拆包时 import 路径怎么定才不踩坑
Go 没有“模块命名空间”概念,路径即包名。别用 github.com/yourorg/project/internal/order 这种路径去导出公开类型;如果 order 需被其他业务包引用,它就得是 github.com/yourorg/project/order ——哪怕只是内部用。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- 先在
go.mod里加replace github.com/yourorg/project/order => ./order,本地验证引用无误再提交 - 避免路径含
v1、v2,版本应由go.mod的 module path 控制(如github.com/yourorg/project/v2/order) - 如果旧包里有
init()函数,拆完后检查是否被意外多次执行(尤其涉及全局注册)
接口定义放哪?别让新包依赖旧包的 concrete type
典型错误:把 type Order struct { ... } 放在老包里,新包直接嵌套或接收指针——结果一改字段,所有引用全挂。
正确做法是「接口先行」:
- 在新包根目录建
contract.go,只放type OrderService interface { Create(...) error } - 老包实现该接口,新包只依赖接口,不 import 老包的 struct 定义
- 如果必须传数据,定义
type OrderInput struct在新包内,用mapstructure或手动赋值转换,不暴露原始 struct
否则你会遇到:改个 Order.ID int64 为 string,整个调用链上十几个地方报 cannot use order (type *oldpkg.Order) as type *newpkg.Order。
测试怎么跟着拆,避免 “测了等于没测”
拆包后最常犯的错:保留原包的 xxx_test.go,但里面还 import 老包路径,测试跑的是旧逻辑,新包实际行为完全没覆盖。
实操要点:
- 每个新包必须有独立
xxx_test.go,且package xxx和目录名一致 - 用
go test -v ./order/...确认只跑新包测试;go test -run=^TestCreate$ ./order单测精准定位 - 如果新包依赖外部服务(如 DB),用 interface + mock,别在测试里写
sql.Open(...)——否则拆完发现测试启动要 3 秒,CI 直接超时
真正难的不是拆,是让每个包的边界清晰到——删掉它,编译器立刻报错,而不是等运行时 panic 才发现漏了某处调用。










