Go 1.13 的 Errors

Go 1.13 为 error 包带来了新的内容。 它们来自于 Go 2 错误检查的建议。

Go 1.13 的 Errors

Go 1.13 为 error 包带来了新的内容。 它们来自于 Go 2 错误检查的建议。

在Go中,错误是实现 error 接口的任何值。

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {  
   Error() string  
}

本质上,错误是字符串,人们很容易阅读和解释,但程序很难对它们进行推理。

目前有四种常用的方法来处理错误:

  • sentinel errors
  • type assertion
  • ad-hoc checks
  • substring searches

Sentinel errors

一些包定义导出的错误变量并从其函数返回。sql.ErrNoRows 就是个常见的例子:

package sql

// ErrNoRows is returned by Scan when QueryRow doesn’t return a  
// row. In such a case, QueryRow returns a placeholder *Row value that  
// defers this error until a Scan.  
var ErrNoRows = errors.New(“sql: no rows in result set”)

然后我们判断一下:

if err == sql.ErrNoRows {  
 … handle the error …  
 }

Type assertion

与 sentinel errors 类似,在这种情况下,我们希望检查返回的错误是否形成某种特定类型,以便提供更多信息。我们可以在 os 包中看到一个很好的例子:

type PathError struct {  
        Op   [string](https://golang.org/pkg/builtin/#string)  
        Path [string](https://golang.org/pkg/builtin/#string)  
        Err  [error](https://golang.org/pkg/builtin/#error)  
}

func (e *PathError) Error() string  
func (e *PathError) Timeout() bool

使用类型断言,我们可以访问所有可用的额外信息 PathError

if pe, ok := err.(*os.PathError); ok {  
    if pe.Timeout() { ... }  
    ...  
}

Ad-hoc checks

本方法是通过定义函数,抽象出给定的错误可能是什么。 一个明显的优点是包可以公开这些方法,并保留其内部用于错误处理的私有性。

// IsNotExist returns a boolean indicating whether the error is known to  
// report that a file or directory does not exist. It is satisfied by  
// ErrNotExist as well as some syscall errors.  
func IsNotExist(err error) bool

if os.IsNotExist(err) {  
      ...  
}

Substring searches

它的名字说明了一切,通过古老的 strings.Contains 检查错误。 总而言之,是最不受欢迎的。

if strings.Contains(err.Error(), "foo bar") {  
    ...  
}

当我们想要添加更多的上下文/信息时: Wrapping

我们经常需要添加一些更具体的信息,例如解释故障的原因。 对于上述情况,我们无能为力。 例如,我们可能需要声明由于 sql 错误而导致获取操作失败。

Wrapping 本质上是创建一系列错误,使我们能够在保留原始错误的同时添加更多信息。它可以很容易地用任何能够容纳错误的类型和更多信息来实现。给出如下类型:

type myError struct {  
 msg string  
 err error  
}

func Wrap(err error, msg string, args ...interface{}) error {  
   return myError{  
      msg: fmt.Sprintf(msg, args...),  
      err: err,  
   }  
}

我们可以轻松地创建一些方法来遍历错误链,从 Wrapping 错误中给出下面的错误,依此类推。

Go生态系统中有一些可靠的库。Onefootball [github.com/pkg/errors](https://github.com/pkg/errors) (如果你不知道,可以看一下;) ) 在我们的大多数微服务中使用。

然而,这种方法存在一个缺点,我们将 vendor 锁定到任何封装错误的库。因为只能通过库的API访问展开和任何其他信息。

Go 2 错误检查建议

建议 Go 2增加一个用于 unwrapping 错误的接口:

// Unwrap returns the result of calling the Unwrap method on err, if err’s  
// type contains an Unwrap method returning error.  
// Otherwise, Unwrap returns nil.  
type Wrapper interface {  
 Unwrap() error  
}

[我认为它被称为 Unwrapper 而不是 Wrapper]

这个简单的接口允许任何 Go 程序解包任何自定义错误,如果当前的包装器实现 Unwrap() ,我们可以遍历整个错误链,而不必担心有多少自定义错误可能已经混合在一起。

更好的是,还记得 sentinel errorstype assertions 吗? 现在,Go 的错误包可以为它们定义标准方法。 它们是 IsAs:

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

更多细节:

package errors  

// Is reports whether any error in err's chain matches target.  
//  
// The chain consists of err itself followed by the sequence of errors obtained by  
// repeatedly calling Unwrap.  
//  
// An error is considered to match a target if it is equal to that target or if  
// it implements a method Is(error) bool such that Is(target) returns true.  
func Is(err, target error) bool  

// As finds the first error in err's chain that matches target, and if so, sets  
// target to that error value and returns true.  
//  
// An error matches target if the error's concrete value is assignable to the value  
// pointed to by target, or if the error has a method As(interface{}) bool such that  
// As(target) returns true. In the latter case, the As method is responsible for  
// setting target.  
//  
// As will panic if target is not a non-nil pointer to either a type that implements  
// error, or to any interface type. As returns false if err is nil.  
func As(err error, target interface{}) bool 

go 1.13?

Go 1.13 定义 UnwrapIsAs 函数如上所示。

**Unwrap **是对类型error 的内容调用 Unwrap() 的一种简写形式。 由于这两种方法都没有添加到 error 接口中,因此 errors.Unwrap 非常方便。

IsAs 将匹配或类型 assert 并将任何错误转换为目标,遍历错误链直到找到匹配错误或 nil

正如你可能已经注意到的,在 Go 1.13上没有定义接口,上面的三个方法都是动态检查给定的错误是否实现了其中的任何一个:

u, ok := err.(interface { Unwrap() error })  

x, ok := err.(interface { Is(error) bool })  

x, ok := err.(interface { As(interface{}) bool })

包装一个错误? 不要担心,fmt 已经为你覆盖了:

The [Errorf](https://tip.golang.org/pkg/fmt/#Errorf) 函数有一个新的谓词%w,其 运算对象必须是 error。从 Errorf 返回的错误将有一个 Unwrapmethod,它返回运算对象%w.

err := errors.New(“my error”)  
err = fmt.Errorf(“1s wrapping my error with Errorf: %w”, err)  
err = fmt.Errorf(“2nd wrapping my error with Errorf: %w”, err)

迁移到 Go 1.13? 最后一个小贴士

注意一件事:modules 现在默认使用Google官方的镜像和校验和数据库(checksum database)。 go命令请求安装 modules 时将访问官方的镜像并与官方的校验和数据库(checksum database)进行对比校验。 因此,你需要排除你私人仓库中的modules 。 你可以这样操作,在 GOPRIVATE 中设置 modules 路径前缀以逗号分隔的glob模式列表(通过Go语言中的 path.Match 进行匹配)。 例如:

GOPRIVATE=github.com/myOrg/*,*.corp.example.com,domain.io/private

Happy coding!

PS:我还在柏林Golang聚会上发表了关于此问题的演讲,你可以在这里找到幻灯片和代码:https://github.com/AndersonQ/go1_13_errors

本文翻译自A Look At Go 1.13 Errors - Onefootball Locker Room - Medium