概述
当我们需要在Go项目中设计error,就不得不先知道Go error几种常用方法。标准库是一个非常好的学习方式,除此之外Go1.13的errors特性也需要掌握。
error使用方式
1.直接判等
这里的判等又分为变量判等和类型判等。 适用于pkg中预先定义好了多个error变量或类型,err只可能是这些变量的其中一个。 案例:os.IsExist(err)
// 变量判等var errObj = errors.New(errObj)func IsErrObj(err error) bool { return err == errObj}// 类型判等type PathError struct { Op string Path string Err error}func IsPathError(err error) bool { switch e := err.(type) { case *PathError: return true default: return false }}
2.组合error接口,构建更强大的error接口
适用于构造pkg级别专用的error接口类型,同时在struct中组合Err变量表示底层错误 案例:net.Error interface
package nettype Error interface { error Timeout() bool // Is the error a timeout? Temporary() bool // Is the error temporary?}type AddrError struct { Err string Addr string}
3.Errno模式
我们知道Linux有大量的错误码,表示了各种错误类型,对于很多系统而言错误码非常好用。Go如何兼容这种errono模式呢? 案例:sysacall.Errno
type Errno uintptrfunc (e Errno) Error() string { if 0 <= int(e) && int(e) < len(errors) { s := errors[e] if s != "" { return s } } return "errno " + itoa.Itoa(int(e))}
4.Go1.13的Wrap模式
在一些场景下,error是有链式关系的,我们固然可以自己实现一种链式error类型,但是Go1.13引入了语言级别的支持。它非常简单,只要3个重要的用法:
// 创建errorerr2 := fmt.Errorf("%w", err1)// 判断error链条中是否包含某个err变量ok := errors.Is(err2, err1) // true// 判断error链条中是否可赋值为某个err类型,成功则赋值给targettype Errno intfunc (e *Errno) Error() string { return strconv.Itoa(int(*e))}func test() { var no = Errno(1) no1 := fmt.Errorf("%w", &no) no2 := fmt.Errorf("%w", no1) var target *Errno ok := errors.As(no2, target) fmt.Println(ok, target) // true, 1}
以上代码都依赖 errors.Unwrap 函数,这个函数通过反射解析出链式error的上一个error。 从代码可以看出,error.Is 用于我们有2个err变量的情况下,判断前者是否链接了后者; error.As 用于我们有一个err变量和一种error类型,想要判断链子中是否包含了这种error类型,如果是,我们顺带将值保存在target中,相当于丢弃了一些链式的信息,返璞归真。 这里有2个注意点:
Unwrap依赖反射,我们知道Go的反射是很慢的,所以需要考虑性能的场景慎用
As函数使用是,target本身必须是struct的指针类型,并且要取地址,否则可能会panic
5. Go版本低时的链式error
有时候我们会看到 github.com/pkg/errors 这个包,它其实就是老版本Go想要使用链式error所引用的包,它常用的方法是 Wrap 和 Cause,所以看到这2个函数就可以猜到一个项目没有使用新的errors特性。
原文:https://juejin.cn/post/7102659941533483045