设为首页 加入收藏

TOP

Golang error 的突围(四)
2019-09-18 11:10:41 】 浏览:155
Tags:Golang error 突围
rors 库增加了 Is/As/Unwrap三个函数,这将用于支持错误的再次包装和识别处理,为 Go 2 中新的错误处理改进提前做准备。

1.13 支持了 error 包裹(wrapping):

An error e can wrap another error w by providing an Unwrap method that returns w. Both e and w are available to programs, allowing e to provide additional context to w or to reinterpret it while still allowing programs to make decisions based on w.

为了支持 wrapping,fmt.Errorf 增加了 %w 的格式,并且在 error 包增加了三个函数:errors.Unwraperrors.Iserrors.As

fmt.Errorf

使用 fmt.Errorf 加上 %w 格式符来生成一个嵌套的 error,它并没有像 pkg/errors 那样使用一个 Wrap 函数来嵌套 error,非常简洁。

Unwrap

func Unwrap(err error) error

将嵌套的 error 解析出来,多层嵌套需要调用 Unwrap 函数多次,才能获取最里层的 error。

源码如下:

func Unwrap(err error) error {
    // 判断是否实现了 Unwrap 方法
    u, ok := err.(interface {
        Unwrap() error
    })
    // 如果不是,返回 nil
    if !ok {
        return nil
    }
    // 调用 Unwrap 方法返回被嵌套的 error
    return u.Unwrap()
}

对 err 进行断言,看它是否实现了 Unwrap 方法,如果是,调用它的 Unwrap 方法。否则,返回 nil。

Is

func Is(err, target error) bool

判断 err 是否和 target 是同一类型,或者 err 嵌套的 error 有没有和 target 是同一类型的,如果是,则返回 true。

源码如下:

func Is(err, target error) bool {
    if target == nil {
        return err == target
    }

    isComparable := reflectlite.TypeOf(target).Comparable()
    
    // 无限循环,比较 err 以及嵌套的 error
    for {
        if isComparable && err == target {
            return true
        }
        // 调用 error 的 Is 方法,这里可以自定义实现
        if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
            return true
        }
        // 返回被嵌套的下一层的 error
        if err = Unwrap(err); err == nil {
            return false
        }
    }
}

通过一个无限循环,使用 Unwrap 不断地将 err 里层嵌套的 error 解开,再看被解开的 error 是否实现了 Is 方法,并且调用它的 Is 方法,当两者都返回 true 的时候,整个函数返回 true。

As

func As(err error, target interface{}) bool

从 err 错误链里找到和 target 相等的并且设置 target 所指向的变量。

源码如下:

func As(err error, target interface{}) bool {
    // target 不能为 nil
    if target == nil {
        panic("errors: target cannot be nil")
    }
    
    val := reflectlite.ValueOf(target)
    typ := val.Type()
    
    // target 必须是一个非空指针
    if typ.Kind() != reflectlite.Ptr || val.IsNil() {
        panic("errors: target must be a non-nil pointer")
    }
    
    // 保证 target 是一个接口类型或者实现了 Error 接口
    if e := typ.Elem(); e.Kind() != reflectlite.Interface && !e.Implements(errorType) {
        panic("errors: *target must be interface or implement error")
    }
    targetType := typ.Elem()
    for err != nil {
        // 使用反射判断是否可被赋值,如果可以就赋值并且返回true
        if reflectlite.TypeOf(err).AssignableTo(targetType) {
            val.Elem().Set(reflectlite.ValueOf(err))
            return true
        }
        
        // 调用 error 自定义的 As 方法,实现自己的类型断言代码
        if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
            return true
        }
        // 不断地 Unwrap,一层层的获取嵌套的 error
        err = Unwrap(err)
    }
    return false
}

返回 true 的条件是错误链里的 err 能被赋值到 target 所指向的变量;或者 err 实现的 As(interface{}) bool 方法返回 true。

前者,会将 err 赋给 target 所指向的变量;后者,由 As 函数提供这个功能。

如果 target 不是一个指向“实现了 error 接口的类型或者其它接口类型”的非空的指针的时候,函数会 panic。

这一部分的内容,飞雪无情大佬的文章【飞雪无情 分析 1.13 错误】写得比较好,推荐阅读。

总结

Go 语言使用 error 和 panic 处理错误和异常是一个非常好的做法,比较清晰。至于是使用 error 还是 panic,看具体的业务场景。

当然,Go 中的 error 过于简单,以至于无法记录太多的上下文信息,对于错误包裹也没有比较好的办法。当然,这些可以通过第三方库来解决。官方也在新发布的 go 1.13 中对这一块作出了改进,相信在 Go 2 里会有更进一步的优化。

本文还列举了一些处理 error 的示例,例如不要两次处理一个错误,判断错误的行为而不是类型等等。

参考资料里列举了很多错误处理相关的示例,这篇文章作为一个引子。

参考资料

【Go 2 错误提案】https://go.googlesource.com/proposal/+/master/design/29934-error-values.md

【check & handle】https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md

【错误讨论的 issue】https://github.com/golang/go/issues/29934

【error value 的 FAQ】https://github.com/golang/go/wiki/Error

首页 上一页 1 2 3 4 5 下一页 尾页 4/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇golang基础语法 下一篇Go Modules使用教程

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目