这样无疑提升了错误的表达力,如果不想自己单独定义一个错误类型,只想附加某些信息,可以依赖fmt.Errorf:
newErr := fmt.Errorf("permission error:\npid:%v\nuid:\ninfo:%w", pid, uid, sysErr) sysErr == newErr.(interface {Unwrap() error}).Unwrap()fmt.Errorf新的占位符%w只能在一个格式化字符串中出现一次,他会把error的信息填充进去,然后返回一个实现了Unwrap的新error,它返回传入的那个error。另外提案里的Wrapper接口目前还没有实现,但是标准库用了我在上面的做法暂时实现了Wrapper的功能。
因为错误链的存在,我们不能在简单的用等于号基于判断基于值的error了,但好处是我们现在还可以判断基于类型的error。
为了能继续让error表现自己的值语义,errors包里增加了Is和As以及辅助它们的Unwrap函数。
Unwraperrors.Unwrap会调用传入参数的Unwrap方法,As和Is使用它来追溯整个错误链。
像上一小节的代码就可以简化成这样:
newErr := fmt.Errorf("permission error:\npid:%v\nuid:\ninfo:%w", pid, uid, sysErr) sysErr == errors.Unwrap(newErr).Unwrap() Is我们提到等于号的比较很多时候已经不管用了,有的时侯一个error只是对另一个的包装,当这个error产生时另一个也已经发生了,这时候我们只需要比较处于上层的error值即可,这时候你就需要errors.Is帮忙了:
newErr := fmt.Errorf("permission error:\npid:%v\nuid:\ninfo:%w", pid, uid, sysErr) errors.Is(newErr, sysErr) errors.Is(newErr, os.ErrExists)你永远也不知道程序会被怎样扩展,也不知道error之间的关系未来会怎样变化,因此总是用Is代替==是不会犯错的。
不过凡事总有例外,例如io.EOF就不需要使用Is去比较,因为它程序意义上算不上是error,而且一般也不会有人包装它。
As除了传统的基于值的判断,对某个类型的错误进行处理也是一个常见需求。例如前文的A,B都来自error,假设我们现在要处理所有基于这个error的错误,常见的办法是switch进行比较或者依赖于基类的多态能力。
显而易见的是switch判断的做法会导致大量重复的代码,而且扩展困难;而在golang里没有继承只有组合,所以有运行时多态能力的只有interface,这时候我们只能借助错误链让errors.As帮忙了:
// 注意As的第二个参数只能是你需要判断的类型的指针,不可以直接传一个nil进去 var p1 *os.SyscallError var p2 *os.PathError errors.As(newErr, &p1) errors.As(newErr, &p2)如果p1和p2的类型在newErr所在的错误链上,就会返回true,实现了一个很简陋的多态效果。As总是用于替代if _, ok := err.(type); ok这样的代码。
当然,上面的函数一方面让你少写了很多代码,另一方面又严重依赖反射,特别是错误链很长的时候需要反复追溯多次,所以这里有两条忠告:
不要过渡包装,没什么是加间接层解决不了的,但是中间层太多不仅影响性能也会干扰后续维护;
如果你实在在意性能,而且保证不存在对现有error的扩展(例如io.EOF),那么使用传统方案也无伤大雅。
就个人而言我不认为新的错误处理方法解决了什么本质的问题,但作为迈出尝试的第一步,还是值得肯定的。