Go 1.13版本 xerrors 包装错误
Go 1.13: xerrors
Go 2 系列语言更改的一部分是新的错误检查提案。
错误检查提案为其他地方(在 github.com/pkg/errors 等包中)尝试过的错误添加了几个功能,并带有一些新的实现技巧。 该提案已在提示中实施,为 Go 1.13 做准备。 您今天可以通过使用 Go from tip 或使用包 golang.org/x/xerrors 和 Go 1.12 来尝试一下。
额外的功能完全基于库,不涉及对编译器或运行时的更改。 一个重要的新功能是错误包装。
一个工作示例:包装“key not found” 错误
我们正在为 Tailscale 构建的产品包括一个名为 taildb 的简单键值存储。 与许多简单的 KV 存储一样,您可以读取键值。
// Get fetches and unmarshals the JSON blob for the key k into v.
// If the key is not found, Get reports a "key not found" error.
func (tx *Tx) Get(k string, v interface{}) (err error)让我们来谈谈 “key not found.”
版本 1
第一个 API 版本将”key not found”错误定义为:
var ErrNotFound = errors.New("taildb: key not found")使用taildb的代码可以轻松使用:
var val Value
if err := tx.Get("my-key", &val); err == taildb.ErrNotFound {
	// no such key
} else if err != nil {
	// something went very wrong
} else {
	// use val
}这很好,直到我进行一些调试并遇到一个归结为:
my_http_handler: taildb: key not found这不是一个非常有用的错误消息.
版本 2
鉴于 Get 方法具有键名,最好将其包含在错误消息中。
所以我遵循了 Go 中的一个常见策略,即在 taildb 包中引入错误类型:
type KeyNotFoundError struct {
	Name string
}
func (e KeyNotFoundError) Error() string {
	return fmt.Errorf("taildb: key %q not found")
}这很好用!检查此特定错误的代码有点混乱,但它可以工作:
var val Value
err := tx.Get("my-key", &val)
if err != nil {
	if _, isNotFound := err.(taildb.KeyNotFoundError); isNotFound {
		// no such key
	} else {
		// something went very wrong
	}
} else {
	// use val
}但这种直接搭配的风格有一个缺陷。如果任何中间代码将信息添加到错误中,我们将无法再检查错误的类型。考虑如下函数:
func accessCheck(tx *taildb.Tx, key string) error {
	var val Value
	if err := tx.Get(key, &val); err != nil {
		return fmt.Errorf("access check: %v", err)
	}
	if !val.AccessGranted {
		return errAccessDenied
	}
	return nil
}在这里,我们在数据库之上实现逻辑,检查用户是否具有某种访问权限。报告 nil 错误将授予访问权限,否则访问将被拒绝。拒绝访问的原因可能是 !AccessGranted 或一些底层数据库错误。所有关于错误的文本信息都被保留了,但是使用 fmt.Errorf 意味着我们不能再检查访问错误是否是 KeyNotFoundError。
版本 3
新的 xerrors 库通过提供一个版本的 Errorf 来解决此问题,该版本保留了新错误中的底层错误对象:
	if err := tx.Get(key, &val); err != nil {
		return xerrors.Errorf("access check: %w", err)
	}%w for wrap.
从表面上看,Errorf 的这种实现与 fmt 中的实现完全一样。在底层,保留类型意味着我们现在可以检查 KeyNotFoundError 的原因链:
var val Value
if err := accessCheck(tx, "my-key"); err != nil {
	var notFoundErr taildb.KeyNotFoundError
	if xerrors.As(err, ¬FoundErr) {
		// no such key
	} else {
		// something went very wrong
	}
} else {
	// use val
}Great!
版本 4
我们可以做得更好。我们替换导出的 KeyNotFoundError 的唯一原因是我们可以在错误消息中添加一些额外的文本,同时使类型可测试。新的 xerrors 为我们提供了一种更简单的方法来做到这一点。
所以让我们回到第一个定义:
var ErrNotFound = errors.New("key not found")在taildb里面我们可以写:
func (tx *Tx) Get(k string, v interface{}) (err error) {
	// ...
	if noSuchKey {
		return xerrors.Errorf("taildb: %q: %w", k, ErrNotFound)
	}
}我们想要的所有信息都在这里。当我们将错误打印到日志时,我们会看到 taildb: "my-key": key not found。要检查从 accessCheck 返回的错误,我们可以编写:
var val Value
if err := accessCheck(tx, "my-key"); xerrors.Is(err, taildb.ErrNotFound) {
	// no such key
} else if err != nil {
	// something went very wrong
} else {
	// use val
}简单!
Go 1.13
新的 xerrors 将在 Go 1.13 中升级到标准库的错误包中。
链接不是 xerrors.Errorf,而是直接构建到我们今天使用的 fmt.Errorf 函数中:
如果最后一个参数是错误的并且格式字符串以“: %w”结尾, 返回的错误实现 errors.Wrapper 并带有返回它的 Unwrap 方法。
当然,这看起来不错。然而,距离 Go 1.13 仅三个月之遥!在那之后,所有这些新的变化(这篇文章只介绍一个)将在[Go 1 兼容性承诺](https://golang.org/doc/go1compat)下的标准库中被永久冻结。对于如此高的标准,这个包[可悲地测试不足](https://godoc.org/golang.org/x/xerrors?importers)。
我鼓励你从今天开始使用 golang.org/x/xerrors,或者更好的是,通过 从源代码安装 直接针对 Go 提示开始开发。更多的人需要尝试一下。