Error Wrap
Go在1.13版本中,除了将module转正之外,还对错误处理进行了一定的改善。关于Wrap这个内容,大致来源于github.com/pkg/errors中。不过两者在内容以及实现上,还是有一定的差别。关于第三方包的基本介绍在Link2中有介绍,这里就直接介绍官方的实现。
1 错误链(错误套娃🪆)
从整体来看,所谓的Wrap动作,就是将原先的错误再包装一层,附加一些其他的错误信息以形成一个新的错误信息。这个动作似乎和 fmt.Errorf("...%v...", err)
类似(不过 %w
只能匹配错误,不能匹配字符串),都是将原始的信息包装在一个新的信息之中,但最大的区别是, Errorf("%v")
这个操作将会丢失原本的错误信息。当然,这是针对机器说的,机器无法从新的错误中将原本的错误信息还原出来。
而所谓的Unwrap动作,自然就是将外包装拆掉,还原出里面一层的错误。
2 打包错误
我更愿意称之为嵌套错误,感觉更加形象。Go使用 fmt.Errorf()
配合 %w
的方法对错误进行嵌套,而不没有提供单独的Wrap方法。这样在一定程度上,为原项目错误处理迁移到新的方式提供了便利。
%w
与 %v
的最大区别是, %w
生成的新错误,会自带 Unwrap
方法,Unwrap方法就是还原底层错误信息的作用,不需要过多的介绍。
3 检查错误
errors
包提供了 Is
和 As
两个方法进行错误检查。在最基础的情况下, Is
和 As
分别和Link2中提到的哨兵错误和类型断言的方式类似,进行错误值的判断以及错误类型的断言。不过, Is
和 As
最大的提升之处在于,它们会考虑错误链中的所有错误。
举个例子:
假如 A[B[C]]
Is和As都会一层一层地往里进行比较,直到匹配
Is
Is
将错误与具体的错误值进行比较
// Similar to:
// if err == ErrNotFound { … }
if errors.Is(err, ErrNotFound) {
// something wasn't found
}
As
As
将用于测试错误是否为特定的错误类型,
// Similar to:
// if e, ok := err.(*QueryError); ok { … }
var e *QueryError
if errors.As(err, &e) {
// err is a *QueryError, and e is set to the error's value
}
4 是否要对错误进行包装
需要认识到,包装后的错误,其内部的信息对于外部而言是可见的(对程序而言是可见的,对人而言没有差别)。如果要避免暴露实现细节,那么就不要包装,而是可以沿用之前的 %v
等方式。
5 自定义
之前提到,包装将会形成一个错误链,对于每一个节点的错误而言,在进行判断时,都是使用自己的 As
、 Is
和 Unwrap
方法(当然,是有默认方法的),所以可以自己对这些细节进行定义,比如:
type Error struct {
Path string
User string
}
func (e *Error) Is(target error) bool {
t, ok := target.(*Error)
if !ok {
return false
}
return (e.Path == t.Path || t.Path == "") &&
(e.User == t.User || t.User == "")
}
if errors.Is(err, &Error{User: "someuser"}) {
// err's User field is "someuser".
}
6 简要总结
一共三个函数+一个 %w
Link3就是Go2的草案以及一些提议的汇总,Go官方针对现在社区中的一些问题,以及自己的需求,面向社区给出了一些自己的提议(比如check+handle),并征求建议以及意见。像这篇文章的内容,就是Go2的草案中所提到,因为与Go1现有的内容没有冲突,所以提前发布的特性。
草案中提到的一些问题、目标以及解决方案大致有:
- if err != nil 泛滥
- 使用check handle进行集中处理
- 可以看看errors are values这篇文章,如果check+handle最终上线了,这篇文章中的解决方案就比较过时了
- 使错误检查更简单
- 提供更多的错误细节
- 必须保持对哨兵错误以及错误类型两种错误检查的支持
-
Link
- 优雅的处理错误:关于API上错误处理的设计
- Go2 design draft