www.zhblog.net

错误的比对


如果函数或方法返回错误,要比对的不单只是nil与否,例如,读取文件时,会需要判断返回的错误是否为io.EOF,那么io.EOF这些错误是什么呢?在io包的io.go源码中可以看到,它们就是个errors.New建出的值罢了:

var ErrShortWrite = errors.New("short write")
var ErrShortBuffer = errors.New("short buffer")
var EOF = errors.New("EOF")
var ErrUnexpectedEOF = errors.New("unexpected EOF")
var ErrNoProgress = errors.New("multiple Read calls return no data or error")

errors包的errors.go可以看到,errors.New创建的是个结构值,只有一个string字段,并且实现了Error方法:

func New(text string) error {
    return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

字符串是可以比较的(Comparable),errorString结构也是个可以比较的,因此可以直接使用==来比较错误是否为io.EOF等,在开发自己的应用程序或程序库时,对于通用、简单的错误,也可以如上定义。

errors.New创建的实例,能携带的信息就只是字符串罢了,如果错误发生时,需要传递更多的环境信息,怎么办呢?

在方法定义返回错误时的error其实是个内置的接口,定义的正是Error方法:

type error interface {
    Error() string
}

也就是说,只要有实现Error方法,都可以作为error实例返回,例如,os.PathErroros包的error.go是这么定义的:

type PathError struct {
    Op   string
    Path string
    Err  error
}

func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }

func (e *PathError) Unwrap() error { return e.Err }

func (e *PathError) Timeout() bool {
    t, ok := e.Err.(timeout)
    return ok && t.Timeout()
}

也就是说,若错误是PathError实例,可以有透过字段或者是方法来获取更多信息,例如:

if e, ok := err.(*PathError); ok {
    // 透过 e 获取字段或调用方法
}

若要多种类型要判断,可以使用类型switch语法,例如os包的error.go内部实现就有个例子:

func underlyingError(err error) error {
    switch err := err.(type) {
    case *PathError:
        return err.Err
    case *LinkError:
        return err.Err
    case *SyscallError:
        return err.Err
    }
    return err
}

PathError中还包含了Err字段,这并非必要,其应用的情境是在调用某函数时检查到错误,除了创建另一个错误实例收集当时的环境信息之外,你可能会想要包裹来源的错误实例,以便后续调用者可以进一步检视错误根源。

然而,当某个错误包裹了另一个错误,也就表示后续调用者得知道该错误的细节,如果这些细节来自另一个底层,而你不想曝露,就不要直接包裹它,这时在目前应用程序或程序库的抽象层面中,抽取出来源错误中的信息,包装为目前层次的错误就可以了。


展开阅读全文

评论

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 心情