错误处理大概是最容易被忽略的最重要的事情。Go 1.3 的 errors 新增了三个方法,As、Is 和 Unwrap:
func As(err error, target interface{}) bool
func Is(err, target error) bool
func Unwrap(err error) error
func New(text string) error
Working with Errors in Go 1.13 详细介绍了新增这三个方法的缘由,以及函数 Error 返回设计建议。 这三个新方法不是新的语法能力,只是吸收了一些比较常用的 error 处理方法。
Is() 和 As() 比较简单,就是两个语法糖,一个是 value 比较,一个是类型断言。
errors.Is():
if errors.Is(err, ErrNotFound) {
// something wasn't found
}
//等同于
if err == ErrNotFound {
// something wasn't found
}
errors.As():
var e *QueryError
if errors.As(err, &e) {
// err is a *QueryError, and e is set to the error's value
}
等同于:
if e, ok := err.(*QueryError); ok {
// err is a *QueryError, and e is set to the error's value
}
在 Go 语言中,Error 是变量,传递错误信息的能力比较弱。函数 A 调用函数 B,函数 B 调用函数 C,如果要在函数 A 中获得函数 C 中的错误,通常在函数 B 中用下面的方法向 A 传递:
if err != nil {
return fmt.Errorf("decompress %v: %v", name, err)
}
但是这种方法只保留了 err 中的文本信息,err 的类型信息全部丢失了。如果要保留 err 的类型,函数 B 可以返回下面类型 struct 变量:
type QueryError struct {
Query string
Err error
}
将函数 C 返回的 err 保留在成员 Err 中。
Go 1.13 提供的 Unwrap 方法简化了解出内层 Err 的操作。
如下所示,如果 Error Struct 实现了 Unwrap() error
接口:
func (e *QueryError) Unwrap() error { return e.Err }
可以用 errors package 中的 Unwrap() 方法直接解出内层的 Err:
// Create: 2019/12/03 14:31:00 Change: 2019/12/03 14:42:06
// FileName: error-unwrap.go
// Copyright (C) 2019 lijiaocn <[email protected] wechat:lijiaocn> wechat:lijiaocn
//
// Distributed under terms of the GPL license.
package main
import (
"errors"
)
type QueryError struct {
Query string
Err error
}
func (e *QueryError) Error() string {
return e.Query + ": " + e.Err.Error()
}
func (e *QueryError) Unwrap() error {
return e.Err
}
func FunC() error {
return errors.New("err in Function C")
}
func FunB() error {
err := FunC()
if err != nil {
return &QueryError{Query: "query error", Err: err}
}
return nil
}
func main() {
err := FunB()
if err != nil {
println(err.Error())
// 解出内层的 Err
if inter := errors.Unwrap(err); inter != nil {
println(inter.Error())
}
}
}
errors.Unwrap(err) 会调用传入参数的 Unwrap() erorr 方法,并将该方法的返回值返回,上面代码的执行结果是:
query error: err in Function C
err in Function C
注意 Unwrap() error 接口不是强制的,QueryError 可以不实现该方法,如果传入参数没有实现 Unwrap() error,errors.Unwrap() 返回 nil。
Working with Errors in Go 1.13 用了不少篇幅讨论这个问题。
其实就一个原则,如果要向上层传递更多 error 信息就实现,如果不需要就不实现。
但是…感觉还是不如异常捕获方便….应该在语法层面支持 try…catch….
Rob Pike 在 2015 年写了一篇文章回应人们对 Go 的 Error 设计的质疑 :Errors are values。
Go 的 Error 遭遇的最严重的质疑是,到处都是判断 err 是否 nil 的语句,如下:
_, err = fd.Write(p0[a:b])
if err != nil {
return err
}
_, err = fd.Write(p1[c:d])
if err != nil {
return err
}
_, err = fd.Write(p2[e:f])
if err != nil {
return err
}
// and so on
这是没有 try…catch 导致的。Rob Pike 解释说,他们希望 err 就是一个普通的变量,不想设计成异常或者复杂的接口,一个变量只要有 Error() string 方法就可以作为 error 类型使用。
对于 err != nil 代码太多的质疑, Rob Pike 解释可以用其它方法避免,总之就是坚持最初的设计,不想引入异常。