Go跨模块错误处理的核心是统一错误类型、明确来源、避免重复包装并保持可追溯性,关键在于错误在合适位置被识别响应,而非捕获所有错误。

Go 中跨模块错误处理的核心是统一错误类型、明确错误来源、避免重复包装,同时保持调用链的可追溯性。关键不在于“捕获所有错误”,而在于“让错误在合适的位置被识别和响应”。
定义模块专属错误类型
每个模块应导出自己的错误变量或自定义错误类型,便于外部识别和判断。避免直接返回 errors.New 或 fmt.Errorf 的裸字符串错误。
- 用
var ErrNotFound = errors.New("item not found")定义可比较的哨兵错误(sentinel error) - 需要携带上下文时,定义结构体错误类型,实现
Error()和Unwrap()方法 - 模块内部可使用
fmt.Errorf("failed to parse config: %w", err)包装底层错误,但只在边界处(如导出函数)做一次
跨模块调用时只包装一次
错误从底层模块向上透传时,中间层不应无意义地反复包装。只有当需要补充当前层语义(如操作意图、失败阶段)时才用 %w 包装。
- ❌ 错误:数据库层 → service 层 → handler 层,每层都
fmt.Errorf("get user: %w", err) - ✅ 推荐:仅在语义变化处包装,例如 service 层将 “db timeout” 转为 “user service unavailable”,handler 层再转为 “API request failed”
- 用
errors.Is(err, mymodule.ErrNotFound)判断哨兵错误,用errors.As(err, &e)提取自定义错误详情
暴露错误信息要分层级
面向终端用户、日志系统、运维排查的错误信息应有区分。模块导出的错误默认只含技术标识(如错误码、类型),不含敏感或冗余描述。
- 日志中用
fmt.Sprintf("%+v", err)查看完整堆栈(需启用fmt的扩展格式) - 返回给前端的错误消息由顶层 handler 统一映射,例如将
mystore.ErrLocked转为 “资源正被使用,请稍后重试” - 模块内调试可用
fmt.Errorf("in store.Delete: %w", err),但不导出带行号/路径的错误字符串
用错误码辅助跨模块判定
当模块间协议较松(如通过 RPC 或 HTTP 调用),纯类型判断不可靠,可引入轻量级错误码机制。
- 定义枚举式错误码常量:
const CodeNotFound = "NOT_FOUND" - 自定义错误类型中嵌入
Code string字段,并提供Code() string方法 - 跨语言或跨进程场景下,错误码比 Go 类型更稳定;模块内部仍优先用
errors.Is做类型判断
基本上就这些。Go 的错误处理不是靠框架兜底,而是靠约定 + 工具 + 习惯——定义清晰、包装克制、判定明确、暴露有度。









