不必要,但对关键、可重用、需判断的错误必须定义;仅当错误被多函数返回、需errors.Is/==判断、含义稳定且属公开契约时,才定义包级var错误变量。

是否该为每个错误定义 var 变量?
不必要,但对关键、可重用、需判断的错误必须定义。go 的错误处理鼓励“按需定义”,不是“按数量定义”。盲目为每个 fmt.Errorf 或 errors.New 都配一个包级变量,反而会增加维护成本和语义模糊性。
哪些错误值得定义为 var?
只在满足以下全部条件时才定义包级错误变量:
- 该错误会被多个函数返回或检查(例如
io.EOF) - 调用方需要通过
errors.Is或==显式判断(如重试逻辑、状态分流) - 错误含义稳定,不随参数动态变化(因此
fmt.Errorf("user %s not found", id)不适合定义为变量) - 属于当前包的公开契约(即导出的
ErrAlreadyClosed),而非内部临时错误
定义方式与常见陷阱
推荐使用 var + errors.New,避免用 fmt.Errorf 初始化变量(后者可能隐含格式化开销且不可比较):
var ( ErrInvalidID = errors.New("invalid user ID") ErrNotFound = errors.New("resource not found") ErrAlreadyExists = errors.New("resource already exists") )
注意:
立即学习“go语言免费学习笔记(深入)”;
- 不要用
fmt.Errorf("invalid ID: %s", id)赋值给包级var—— 这会让变量失去恒定值,errors.Is(err, ErrInvalidID)永远失败 - 不要把带堆栈的错误(如
fmt.Errorf("%w", err))存为全局变量 —— 它们本应是运行时构造的 - 如果错误需携带上下文,改用自定义错误类型,而不是塞进字符串
什么时候直接用 fmt.Errorf 更合适?
绝大多数业务逻辑中的非关键、一次性、带动态信息的错误,直接内联更清晰:
if len(name) == 0 { return fmt.Errorf("CreateUser: name cannot be empty") } if !isValidEmail(email) { return fmt.Errorf("CreateUser: invalid email format: %q", email) }
这类错误通常只被日志记录或透传给上层,无需被下游代码 Is 判断,也不构成 API 稳定性承诺。
过度提取会导致包里堆满 ErrXXX 却没人真去判断;反过来,该提取时不提取,又会让错误处理散落在各处、无法统一拦截。边界就在“是否会被 errors.Is 查”和“是否代表一个稳定语义”。