Go 错误处理基础与进阶

Go 把错误当值而非异常:error 接口、显式检查、errors.Is/As 判定、哨兵错误与自定义错误类型、错误包装 %w、panic 的边界。本文从哲学到实战讲清 Go 错误处理。

最佳实践
应用程序模块协作插画

本文依据官方文档整理,未执行运行验证或性能基准。代码片段展示局部用法,业务函数、数据和环境需按项目补齐;版本与配置以所引文档为准。

直接回答:Go 的错误是值——内置 error 接口只需一个 Error() string 方法,函数显式返回、调用方显式检查。进阶三件套:fmt.Errorf("...: %w", err) 包装保留链、errors.Is 判哨兵、errors.As 取类型。panic 只留给程序员错误与不可恢复场景。

为什么不用异常

try/catch 的控制流是隐形的——读代码看不出哪里会"跳走"。Go 的设计哲学:失败是正常路径的一部分,显式 if err != nil 强迫开发者直面每个失败点。冗长被吐槽多年,但生产系统的可靠性因此受益。

基础模式

type error interface { Error() string }

任何实现该方法的类型都是 error。返回错误的函数签名约定:错误是最后一个返回值。

创建与定制

var ErrNotFound = errors.New("record not found")  // 哨兵错误

type ValidationError struct{ Field, Msg string }
func (e ValidationError) Error() string { return e.Field + ": " + e.Msg }

哨兵错误用于可预料的业务失败;自定义类型携带结构化上下文。

包装与判定

if err != nil {
    return fmt.Errorf("load config: %w", err)  // %w 保留错误链
}

// 调用方
if errors.Is(err, ErrNotFound) { /* 精确匹配链上任意层 */ }
var ve ValidationError
if errors.As(err, &ve) { /* 取出链上的特定类型 */ }

关键纪律:判定用 Is/As 而非字符串比较或类型断言——包装后的链上两者都能穿透。

panic 的边界

panic 只给两类场景:程序员错误(数组越界这类不该发生的)、启动期不可恢复错误(配置缺失)。库应把可预期失败作为 error 返回;明确约定的 Must API 或程序不变量失败另当别论。recover 必须在同一 goroutine 的 deferred 函数中直接调用,无法捕获另一个 goroutine 的 panic,也不能挽救所有运行时致命错误。HTTP handler 的兜底不自动覆盖它创建的后台 goroutine。

常见问题(FAQ)

Q:if err != nil 太啰嗦怎么办?
A:接受它——显式是特性不是缺陷。重复性检查可封装(如 must 助手仅限初始化代码),但业务路径别藏错误。

Q:错误消息该写多详细?
A:每一层包装加"我在做什么"的上下文,别重复下层信息。链式读下来应能重建完整失败路径:"load config: parse yaml: line 3: unexpected character"。

Q:errors.Join 什么时候用?
A:Go 1.20+ 的 errors.Join 合并已收集的错误,本身不启动或同步并发任务;Is/As 可遍历错误树,As 返回第一个匹配。注意含 typed nil 指针的 error 接口本身可能非 nil。

官方参考

资料核对日期:2026-09-29。

延伸阅读

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台