登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go errors.Is 的自定义错误判等方法

来源:17golang原创

时间:2026-10-27 21:26:30 310浏览 收藏

Go 自定义错误只要实现 Is(target error) bool,就能为 errors.Is 增加分类匹配规则。该方法应只比较当前错误与目标;包装关系由标准库遍历。这里的“判等”是判断错误是否符合约定分类,不要求两个对象拥有相同地址,也不代表业务字段全部相等。

官方地址:https://pkg.go.dev/errors

确定目标:先区分错误身份与业务分类

面对“资源名不同,但都属于配额不足”的场景,我会先把调用方真正需要的问题写清楚:它想知道应否降低请求量,而不是比较两条报错文案。资源名称、限制值放进具体错误;稳定分类交给一个共享的 ErrQuota 标识。这样的分工使提示文字可以调整,分支判断仍保持原意。

errors.Is 会检查传入错误及其包装树,既能使用相等比较,也能使用当前错误实现的 Is 方法。若只是返回、包装同一个哨兵错误,通常不必自定义 Is;只有需要把有自身类型和字段的错误映射到某个分类时,才值得增加这层规则。以下示例属于接口设计示例,没有把示意结果当作本机执行记录。

设计阶段:让具体错误匹配稳定标识

先定义一次 ErrQuota,再定义 QuotaError。Resource 和 Limit 解释失败背景;Is 只认可 ErrQuota,不拿错误字符串作分类,不把所有非空错误都算作配额错误。示例还处理了空接收者,避免业务分类方法自身解引用空指针。

package main

import (
    "errors"
    "fmt"
)

// 分类标识只声明一次,调用方复用它。
var ErrQuota = errors.New("quota exceeded")

type QuotaError struct {
    Resource string
    Limit int
}

func (e *QuotaError) Error() string {
    // 防御性处理 typed nil;正常业务失败始终返回非空指针。
    if e == nil {
        return ""
    }
    return fmt.Sprintf("%s quota exceeded (limit=%d)", e.Resource, e.Limit)
}

func (e *QuotaError) Is(target error) bool {
    // 只比较当前错误的分类,不在方法内遍历包装树。
    return e != nil && target == ErrQuota
}

func reserveSlots(requested, limit int) error {
    if requested > limit {
        return &QuotaError{Resource: "worker slots", Limit: limit}
    }
    // 成功时返回真正的 nil 接口。
    return nil
}

func submit() error {
    if err := reserveSlots(7, 4); err != nil {
        // %w 保留底层错误,调用方仍可识别分类。
        return fmt.Errorf("submit task: %w", err)
    }
    return nil
}

func main() {
    err := submit()
    if err == nil {
        fmt.Println("accepted")
        return
    }
    if errors.Is(err, ErrQuota) {
        // 匹配到业务分类后给出提示;这里不自动重试。
        fmt.Println("reduce requested slots")
        return
    }
    fmt.Println("unexpected error:", err)
}

这段代码中的配额限制是示例业务约定,并非标准库规定。reserveSlots 在成功时直接返回 nil,失败时才构造 QuotaError;调用方通过分类决定提示内容。按照方法定义,具体的非空 QuotaError 能匹配 ErrQuota,Resource 和 Limit 不参与这一分类比较。

具体错误字段与稳定分类标识的结构关系
说明图:具体错误组保存 Resource、Limit 和 Is 分类规则,分类契约组保存 ErrQuota。连线表示字段归属和匹配依赖,不表示执行步骤。

衔接阶段:保留包装关系,让外层调用方分类

submit 使用 fmt.Errorf 的 %w 保存底层错误。调用方取得的是带上下文的外层错误,仍使用 errors.Is(err, ErrQuota)。如果改用 %v,仅把文字放进新错误,包装树不包含原来的 QuotaError,原有分类能力就不会通过这层文字自动恢复。

自定义 Is 返回 false,并不要求标准库停止搜索。若当前错误还有 Unwrap 暴露的子错误,errors.Is 仍可检查它们。因此,Is 不能作为“屏蔽底层某类错误”的过滤器。本文的 QuotaError 没有底层原因,也无需为了分类额外伪造 Unwrap。

我会把接入过程分成三个小目标:先约定分类含义,再在错误生产处保留具体字段,最后让消费处只依赖需要的分类。若消费方确实需要 Resource、Limit 等数据,另用 errors.As 提取相应类型;errors.Is 的布尔值不会把字段交给调用方。

外层包装错误与分类匹配接口的依赖关系
说明图:包装边界中的上下文错误通过 %w 关联具体错误;调用边界中的 errors.Is 同时依赖传入错误和目标标识。图展示静态接口关系,没有伪造运行输出。

落地阶段:明确方向、空值与比较范围

匹配有方向。errors.Is(err, target) 询问 err 一侧错误树中的对象是否匹配 target,并不自动调用 target.Is(err)。不能因为具体 QuotaError 能匹配 ErrQuota,就推导出反向调用也一定成立。

不要递归调用自己。在 Is 方法中再次执行 errors.Is(e, target),会重新进入同一方法。也不要手工拆解任一侧的包装关系;浅比较当前对象与目标,把遍历留给外层标准库函数。

别用相同文案重新创建目标。errors.New 每次产生独立错误值,因此另写一个相同文字的 errors.New,并不等于共享的 ErrQuota。示例的 Is 正是通过这个共享标识建立契约。

typed nil 不是成功值。把值为 nil 的 *QuotaError 装入 error 接口后,接口本身通常仍不等于 nil。示例中的空接收者保护只避免方法出错,不能把这样的接口改成真正的 nil。成功路径应明确 return nil。

目标使用可比较的错误值。本文的目标是 errors.New 返回的指针型错误标识,适合直接比较。若想按业务字段匹配另一个复杂错误,应先明确字段含义、做类型判断,再比较必要字段;不要把任意 error 的整体相等当成万能协议。

交付契约:给调用方一张速查表

任务本文做法接口含义
判断配额分类errors.Is(err, ErrQuota)寻找符合分类的错误
保留失败上下文fmt.Errorf 配合 %w外层文字与底层原因共同保留
实现自定义分类Is(target error) bool仅对当前对象作浅比较
读取具体错误字段errors.As取得类型对象再读取字段
正常成功return nil不返回装有空指针的 error

推荐把文档里的承诺写成“哪些具体失败能被哪些公开标识匹配”,而不是“错误内容相同就相等”。自定义 Is 一旦成为调用方的分支依据,修改它的分类规则就可能改变上层处理,应与公开接口一样慎重维护。

只实现 Error 方法够不够?

Error 只提供文字。共享哨兵的直接返回或包装可以依靠默认匹配;需要自定义分类映射时,再实现 Is。

是否应该把所有字段相同的错误都匹配成功?

取决于业务契约。本文只回答配额分类,不承诺字段等价,也不把 Resource 的空字符串设计成通配符,避免调用方误解匹配范围。

为什么外层报错内容不同仍能命中?

因为 %w 保留了底层错误对象,errors.Is 检查包装关系,不依靠外层字符串相同。

技术依据:Go errors.Is 官方接口说明,以及同页 New、As 的接口定义。代码、业务字段与两张说明图为本文原创。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>