Go语言通过返回error值处理错误,第三方库严格遵循不忽略、不泛化、不隐藏、要分类、可恢复则重试、不可恢复则透传或转换的原则;必须检查err是否为nil,优先使用errors.Is或类型断言判断特定错误(如sql.ErrNoRows、context.DeadlineExceeded),避免字符串匹配;推荐用fmt.Errorf("%w")包装错误以保留上下文,但禁止重复包装;多错误可用errors.Join合并;应统一映射为业务错误码并结构化日志记录,避免泄露敏感信息。

Go 语言本身没有异常(exception)机制,而是通过返回 error 值显式表达错误,第三方库也严格遵循这一约定。处理第三方库错误的核心原则是:**不忽略、不泛化、不隐藏、要分类、可恢复则重试,不可恢复则透传或转换为业务语义明确的错误**。
检查并判断 error 是否为 nil
这是最基础也最容易被忽略的一环。几乎所有 Go 第三方库(如 github.com/go-sql-driver/mysql、gopkg.in/yaml.v3、github.com/aws/aws-sdk-go)都按标准返回 (result, error)。若直接忽略 err,会导致静默失败。
- ✅ 正确写法:if err != nil { return err } 或做具体处理
- ❌ 错误写法:_ = json.Unmarshal(data, &v)(丢弃 error)
- 注意:有些库方法可能返回
nil, nil(例如某些缓存 Get 操作未命中),需结合文档确认语义
区分底层错误类型,避免用字符串匹配
很多库提供可识别的错误变量或类型(如 os.IsNotExist、sql.ErrNoRows),应优先使用类型断言或专用判断函数,而非 strings.Contains(err.Error(), "timeout")。
- ✅ 推荐:if errors.Is(err, context.DeadlineExceeded) { ... }
- ✅ 对 SQL 驱动:if errors.Is(err, sql.ErrNoRows) { ... }
- ✅ 自定义错误类型(如
minio.BucketNotFound)可用类型断言:if _, ok := err.(minio.BucketNotFound); ok { ... } - ⚠️ 字符串匹配仅作为兜底,且需谨慎——错误信息可能随版本变化
包装错误以保留上下文,但避免重复包装
使用 fmt.Errorf("xxx: %w", err) 或 errors.Join 添加调用上下文,便于定位问题源头;但不要层层套娃(如 "failed to save user: failed to insert into db: pq: duplicate key...")。
立即学习“go语言免费学习笔记(深入)”;
- ✅ 合理包装:return fmt.Errorf("failed to upload file to S3: %w", s3Err)
- ✅ 多错误合并:errors.Join(err1, err2)(适用于并行操作失败汇总)
- ❌ 避免:fmt.Errorf("upload failed: %s", err.Error())(丢失原始 error 链)
- ? 提示:启用
go build -gcflags="-l" -ldflags="-s -w"不影响 error 包装链的调试能力
统一错误码与日志记录策略
面向外部 API 或微服务时,建议将第三方库错误映射为内部错误码(如 ErrStorageUnavailable = 5001),并在日志中结构化记录原始错误类型和关键字段。
- ✅ 日志示例:log.Error("storage write failed", "module", "filestore", "err_type", fmt.Sprintf("%T", err), "err_msg", err.Error())
- ✅ HTTP 层转换:if errors.Is(err, io.ErrUnexpectedEOF) { return httperror.BadRequest("invalid payload") }
- ⚠️ 注意:不要在日志里打印敏感信息(如数据库连接串、密钥、完整 stack trace),可借助
errors.Unwrap或自定义Error()方法控制输出
基本上就这些。Go 的错误处理不复杂,但容易因“省事”而埋下隐患。坚持显式检查、精准判断、适度包装、统一归因,第三方库错误就能从麻烦变成清晰的系统信号。










