不能靠go list -m all直接看出冗余依赖——它只输出扁平化模块列表,不体现调用深度、重复引入路径或权重;同一模块即使被多个路径多次引入也仅列一次,且不标注//indirect或实际import状态。

不能靠 go list -m all 直接看出冗余依赖——它只列模块名,不告诉你谁在引用、引用几次、路径多深。
为什么 go list -m all 会漏掉关键信息
这个命令输出的是扁平化模块列表,同一模块即使被五个不同路径间接引入十次,也只出现一次。比如 github.com/gorilla/mux 被 github.com/astaxie/beego、github.com/labstack/echo/v4 和你自己的 internal/router 三路带入,go list -m all 完全看不出这种重复权重。
更麻烦的是:它不标出哪些是 // indirect,也不区分是否真被代码 import ——只是“当前解析出的模块集合”,不是“实际用到的”。
- 常见误操作:看到某个旧版库出现在列表里,就以为是冗余,直接删
require行 → 导致go build报错找不到包 - 真正冗余的标志是:模块在
go.mod里有require,但所有.go文件(含*_test.go)都不 import 它,且没被任何init()或嵌入 interface 触发
用 go mod graph 定位重复引入和高权重依赖
这才是查冗余的起点。每行 A B@v1.2.3 表示 A 依赖 B,可管道过滤、计数、反向追溯:
- 查某模块被多少路径引入:
go mod graph | grep 'golang.org/x/net@' | wc -l - 看谁在拖慢构建:找高频出现的底层模块(如
golang.org/x/text、google.golang.org/protobuf),再用go mod why golang.org/x/text查具体链路 - 发现多版本共存:
go mod graph | grep 'github.com/sirupsen/logrus@'→ 如果输出含v1.8.0和v1.12.0,说明上游依赖未收敛
注意:go mod graph 不处理 replace 或 exclude,输出的是当前解析结果,不是原始声明。
验证“未使用”比看起来更难
Go 官方工具不分析 import 是否被实际调用,因为 init()、测试文件、嵌入 interface 等场景下 import 是静默生效的。只能逼近判断:
- 先筛直接依赖:
go list -f '{{if not .Indirect}}{{.Path}}{{end}}' all - 逐个注释
import _ "xxx"或普通 import,再跑go build -a -v;报错才说明真在用 - 别忘了测试文件:
go list -f '{{.ImportPath}}' ./... | grep test,很多“未用依赖”藏在xxx_test.go里 - 第三方工具如
go-unused可扫 AST,但对泛型、反射、插件式注册等仍有漏判风险
清理后必须验证的三个硬检查点
go mod tidy 会删掉未 import 的模块,但它不保证构建不出问题。执行后立刻做:
- 跑
go build -mod=readonly ./...:确认没意外触发网络拉取(否则说明某依赖的go.mod缺失或校验失败) - 执行
go test ./...,尤其关注集成测试和 e2e 测试——有些依赖只在 runtime 时通过plugin.Open或reflect.Import加载 - 检查
go mod graph | wc -l行数是否下降,再对比go list -m all | wc -l,两者差值大说明传递依赖被有效剪枝
真正容易被忽略的,是那些没写 import 但靠 go:generate 或 build tag 隐式引入的模块——它们不会被 tidy 清掉,也不会出现在 graph 里,却可能让 go build 在特定环境下失败。

















