
Go 里 error 不要被依赖注入框架吞掉
依赖注入(比如 wire、fx)帮你自动构造对象,但 error 往往在初始化阶段就该暴露出来——如果框架静默吃掉 error 或只打日志不返回,服务启动失败却没报错,你会在运行时才遇到 nil pointer dereference 这类问题。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 所有依赖构造函数(如
NewService、NewDB)必须返回(*T, error),且调用方必须检查error;不要写成func NewX() *X - 用
wire.Build时,确保 provider 函数签名含error,wire 会自动传播;若 provider 返回error,整个构建失败并提示具体哪一步出错 - 避免在 DI 容器里做“兜底重试”或“默认 fallback 实例”,这会让错误延迟暴露;初始化失败就是失败,不该假装成功
error 传递时别用 fmt.Errorf("%w", err) 包裹三层以上
嵌套太多层 fmt.Errorf 会让错误链过长,调试时难以定位原始错误来源,尤其在 HTTP handler 或 gRPC server 中层层透传后,errors.Is 和 errors.As 判断容易失准。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 只在语义变更处包裹一次:比如数据库层的
sql.ErrNoRows到业务层可转为ErrUserNotFound,用fmt.Errorf("user not found: %w", err) - 中间层(如 repository → service)若无新上下文,直接返回原
error,不加%w - 用
errors.Unwrap或调试工具(如errgo)查链深度;超过 3 层嵌套基本说明设计过深
DI 初始化失败时,error 必须能区分是配置错还是依赖不可达
比如 NewRedisClient 失败,你得知道是 redis://bad-host 地址错(配置问题),还是网络不通(运行环境问题)。统一返回 errors.New("failed to connect redis") 会让运维排查卡住。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 底层客户端(如
redis.NewClient)的错误尽量透传,不要用通用字符串覆盖;保留原始net.OpError、*url.Error等类型 - 在 provider 函数里加简单判断:若
err是*net.OpError,说明网络层失败;若strings.Contains(err.Error(), "invalid URL"),大概率是配置解析错 - 测试时用
gomock或接口 fake 模拟不同错误类型,验证上层是否能正确分类处理
HTTP handler 中 error 透传要和 DI 生命周期对齐
handler 里调用一个 service 方法,这个 service 是通过 DI 注入的;但如果 service 内部某个字段是 lazy-init 的(比如第一次调用才连 DB),那错误实际发生在 handler 执行中,而非启动时——这时你不能指望 DI 框架提前发现。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 把易失败操作(如 DB 连接、远程 config 加载)提到构造函数里完成,而不是藏在方法里;否则 DI 给你一个“看起来健康”的实例,实际是纸老虎
- handler 中捕获 error 后,用
errors.Is(err, context.DeadlineExceeded)或自定义类型判断,别只看字符串匹配 - 对非启动期错误,考虑用
http.Error返回 503 + 可读信息,但日志里仍保留完整 error 链,方便关联 trace
最常被忽略的一点:DI 框架不会帮你校验 error 是否被处理,它只管对象图。哪怕你写了 func NewX() (*X, error),只要调用方没写 if err != nil,程序照样编译通过、启动成功、然后 panic。错误处理这件事,始终得靠人盯住每一层的返回值。










