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 包提供了 IsAs 两个方法进行错误检查。在最基础的情况下, IsAs 分别和Link2中提到的哨兵错误和类型断言的方式类似,进行错误值的判断以及错误类型的断言。不过, IsAs 最大的提升之处在于,它们会考虑错误链中的所有错误

举个例子:

  1. 假如 A[B[C]]
  2. IsAs都会一层一层地往里进行比较,直到匹配

Is

Is 将错误与具体的错误值进行比较

  1. // Similar to:
  2. // if err == ErrNotFound { … }
  3. if errors.Is(err, ErrNotFound) {
  4. // something wasn't found
  5. }

As

As 将用于测试错误是否为特定的错误类型,

  1. // Similar to:
  2. // if e, ok := err.(*QueryError); ok { … }
  3. var e *QueryError
  4. if errors.As(err, &e) {
  5. // err is a *QueryError, and e is set to the error's value
  6. }

4 是否要对错误进行包装

需要认识到,包装后的错误,其内部的信息对于外部而言是可见的(对程序而言是可见的,对人而言没有差别)。如果要避免暴露实现细节,那么就不要包装,而是可以沿用之前的 %v 等方式。

5 自定义

之前提到,包装将会形成一个错误链,对于每一个节点的错误而言,在进行判断时,都是使用自己的 AsIsUnwrap 方法(当然,是有默认方法的),所以可以自己对这些细节进行定义,比如:

  1. type Error struct {
  2. Path string
  3. User string
  4. }
  5. func (e *Error) Is(target error) bool {
  6. t, ok := target.(*Error)
  7. if !ok {
  8. return false
  9. }
  10. return (e.Path == t.Path || t.Path == "") &&
  11. (e.User == t.User || t.User == "")
  12. }
  13. if errors.Is(err, &Error{User: "someuser"}) {
  14. // err's User field is "someuser".
  15. }

6 简要总结

一共三个函数+一个 %w

  • fmt.Errorf + %w
  • Unwrap
  • Is
  • As

    Go2的错误处理


Link3就是Go2的草案以及一些提议的汇总,Go官方针对现在社区中的一些问题,以及自己的需求,面向社区给出了一些自己的提议(比如check+handle),并征求建议以及意见。像这篇文章的内容,就是Go2的草案中所提到,因为与Go1现有的内容没有冲突,所以提前发布的特性。

草案中提到的一些问题、目标以及解决方案大致有:

  • if err != nil 泛滥
    • 使用check handle进行集中处理
    • 可以看看errors are values这篇文章,如果check+handle最终上线了,这篇文章中的解决方案就比较过时了
  • 使错误检查更简单
  • 提供更多的错误细节
  • 必须保持对哨兵错误以及错误类型两种错误检查的支持
  • 可能会修改error的语义以提供更多支持

    Link

  • 官方博客

  • 优雅的处理错误:关于API上错误处理的设计
  • Go2 design draft