应优先使用Go标准库和少量精简第三方模块,当出现重复引入、测试慢、CI误报、过度间接引用等现象时,需警惕重量级依赖;HTTP服务可用net/http+ServeMux替代gin/echo,配置用encoding/json+os.ReadFile替代viper,日志用log/slog替代zap/logrus。

别用重量级依赖——多数场景下,Go 标准库 + 少量精简第三方模块就能覆盖 90% 需求。所谓“替代”,本质不是找另一个大包来换,而是识别真实需求后,用更小、更可控的组合达成同样目标。
什么时候该怀疑自己用了“重量级依赖”
出现以下任意一种现象,就该停下检查依赖是否过度:
-
go list -f '{{.Deps}}' ./... | grep -v std输出里反复出现同一非标准库模块(比如github.com/spf13/cobra却只用了cobra.Command的基础命令注册) - 一个
go test跑完要 2 秒以上,其中 1.5 秒花在import和初始化第三方包上(尤其带init()的) - 修改某个 handler 逻辑,CI 上却报
github.com/xxx/yyy的测试失败——你根本没动过那行代码 -
go mod graph显示某模块被 5+ 个业务包间接引入,但每个只用它的一个函数(比如uuid.New())
HTTP 服务:用 net/http + http.ServeMux 替代 gin 或 echo
90% 的内部 API、管理后台、健康检查接口,根本不需要路由树、中间件链、上下文封装。标准库完全够用,且无隐藏副作用。
常见错误是把 gin.Context 当成万能容器往里塞数据,结果 handler 依赖 gin 的私有字段,无法单元测试。
立即学习“go语言免费学习笔记(深入)”;
- 路由注册统一用
http.ServeMux,前缀路径靠http.StripPrefix处理 - 中间件写成普通函数:
func(AuthMiddleware(http.Handler) http.Handler),不绑定框架生命周期 - JSON 序列化直接用
json.Marshal+http.Error,避免c.BindJSON()这类隐式 panic - 如果真需要路径参数解析,手写一行正则或用
path.Clean()+strings.Split(),比引入完整路由引擎更轻、更可控
配置加载:用 encoding/json + os.ReadFile 替代 viper
viper 带来的复杂性远超收益:环境变量覆盖逻辑难调试、远程配置拉取阻塞启动、自动重载引发并发问题。而大多数项目配置是静态的、一次读取、全程只读。
- 定义结构体时加
json:tag,直接json.Unmarshal(bytes, &cfg) - 环境区分靠
os.Getenv("ENV")读取文件名(如config.dev.json),不依赖viper.SetEnvPrefix - 密码、密钥等敏感字段,用
os.LookupEnv单独获取,不混在配置文件里 - 需要热更新?用
fsnotify监听文件变化 + 原子替换指针,比viper.WatchConfig()更明确、更易测
日志:用 log/slog(Go 1.21+)替代 zap 或 logrus
slog 是 Go 官方提供的结构化日志方案,API 稳定、无额外依赖、性能接近 zap,且天然支持 context.Context 透传。
- 初始化只需
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, nil))) - 结构化字段用
slog.String("user_id", id),不拼接字符串 - 避免
logrus.WithFields()创建临时 map,slog的slog.Group更轻量 - 若需异步写入,自己套一层
chan+ goroutine,比zap.NewProductionConfig().Build()更透明
真正难的是判断“要不要替”。很多团队删掉 gin 后发现路由不够灵活,其实是没想清楚:那个“灵活”是真实需求,还是历史惯性?精简不是目的,可控才是——所有依赖都应能被三行代码替换,否则它就已经失控了。


















