找回密码
 立即注册
首页 业界区 安全 [tldr] GO语言异常处理

[tldr] GO语言异常处理

任娅翠 7 天前
在开发中, 处理异常是很重要的, 考虑各种错误情况并且提出对应的解决办法是保证不出大BUG的重要之处.
error in go

GO语言的异常是很简单的, 只需要实现Error函数接口即可
  1. func (e ErrA) Error() string {
  2.         return "ErrA"
  3. }
复制代码
过于简单的实现, 和C一样
errors

这是GO的一个库, 专门用来处理异常
Go语言的标准库一般会在后面加一个s代表是处理这种类型的标准库, 例如strings
异常处理

error类别判断

类型断言实现类别判断

Go语言提供了类型断言的方式, 针对接口, 可以实现不同类型之间的相互转换
这就是为什么errA需要是error类型, 因为是接口类型才能转换
  1.         var errA error = ErrA{}
  2.         var errB error = ErrB{}
  3.         fmt.Println(errA.Error())
  4.         fmt.Println(errB.Error())
  5.         if e, ok := errA.(ErrB); ok {
  6.                 fmt.Println(e)
  7.                 fmt.Println("found ErrB")
  8.         } else {
  9.                 fmt.Println("not found ErrB")
  10.         }
复制代码

  • 这个简单好用, 如果需要嵌套的异常处理, 那么应该选择使用这个.
  • 当然可以使用switch语句针对多种错误类型进行处理, 针对多个类型的时候使用这个方式可以更好的处理
  1. func main() {
  2.         var errA error = ErrA{}
  3.         var errB error = ErrB{wrappedErr: errA}
  4.         for err := errB; err != nil; err = errors.Unwrap(err) {
  5.                 fmt.Println(err)
  6.                 switch err.(type) {
  7.                 case ErrA:
  8.                         fmt.Println("ErrA")
  9.                 case ErrB:
  10.                         fmt.Println("ErrB")
  11.                 default:
  12.                         fmt.Println("default")
  13.                 }
  14.                 fmt.Println()
  15.         }
  16. }
复制代码
上述代码是使用switch配合类型断言来处理异常
errors函数类别判断

通过errors.Is或者errors.As函数实现类别的判断
Go语言官方提供的异常处理, 也只是简单的封装.


  • 这个函数的本质是检查这个error是否包含某个error(一个error可以包含多个error, 嵌套error)
  1.         var errA error = ErrA{}
  2.         var errB error = ErrB{}
  3.         fmt.Println(errA.Error())
  4.         fmt.Println(errB.Error())
  5.         if errors.Is(errA, errB) {
  6.                 fmt.Println("errA is errB")
  7.         } else {
  8.                 fmt.Println("errA is not errB")
  9.         }
复制代码
使用这个函数让代码看起来十分的简洁


  • 使用errors提供的函数在处理嵌套的error的时候可能存在问题, 我们在下面讨论
嵌套error

有些时候我们需要把error一层一层wrap起来然后向上抛出异常进行处理. 像是多层的Throw错误.
errors库提供了Unwrap函数, 如果error实现了这个接口, 那么就可以实现error嵌套
  1. package main
  2. import (
  3.         "errors"
  4.         "fmt"
  5. )
  6. type ErrA struct {
  7.         wrappedErr error
  8. }
  9. func (e ErrA) Error() string {
  10.         return fmt.Sprintf("ErrA -> %v", e.wrappedErr)
  11. }
  12. func (e ErrA) Unwrap() error {
  13.         return e.wrappedErr
  14. }
  15. type ErrB struct {
  16.         wrappedErr error
  17. }
  18. func (e ErrB) Error() string {
  19.         return fmt.Sprintf("ErrB -> %v", e.wrappedErr)
  20. }
  21. func (e ErrB) Unwrap() error {
  22.         return e.wrappedErr
  23. }
  24. func main() {
  25.         errA := ErrA{}
  26.         errB := ErrB{}
  27.         fmt.Println(errA.Error())
  28.         fmt.Println(errB.Error())
  29.         if errors.Is(errA, ErrB{}) {
  30.                 fmt.Println("errA is ErrB")
  31.         } else {
  32.                 fmt.Println("errA is not ErrB")
  33.         }
  34.         fmt.Println()
  35.         var wrapErr error = ErrA{wrappedErr: ErrB{}}
  36.         for err := wrapErr; err != nil; err = errors.Unwrap(err) {
  37.                 fmt.Println(err)
  38.         }
  39. }
复制代码
可以使用for循环语句来解开error
但是这个for循环拿出来的error使用errors.Is函数是无法准确识别当前的error属于哪个类别的, 因为所有被wrap的error类别都可以被识别到.
当然也可以通过errors库提供的Join函数实现合并多个error的功能, var wrappedErr error = errors.Join(errA, errB)语句提供了合并多个error的方式.
合并之后的error实现的Unwrap接口是返回[]error的, 但是在errors库使用的时候, 会被视为同一个error
可以通过errors.As或者errors.Is判断当前的错误是否包含某一个特定的错误
errors处理嵌套error

当然可以使用errors库来处理嵌套的error, 因为只要符合error接口规范的都可以使用errors进行处理
但是存在一些不方便的地方, 比如, 一个嵌套ErrA的ErrB错误对象, 同时可以被认为是ErrA和ErrB类型. 下面是一个例子
  1. package main
  2. import (
  3.         "errors"
  4.         "fmt"
  5. )
  6. type ErrA struct {
  7.         wrappedErr error
  8. }
  9. func (e ErrA) Error() string {
  10.         return fmt.Sprintf("ErrA -> %v", e.wrappedErr)
  11. }
  12. func (e ErrA) Unwrap() error {
  13.         return e.wrappedErr
  14. }
  15. type ErrB struct {
  16.         wrappedErr error
  17. }
  18. func (e ErrB) Error() string {
  19.         return fmt.Sprintf("ErrB -> %v", e.wrappedErr)
  20. }
  21. func (e ErrB) Unwrap() error {
  22.         return e.wrappedErr
  23. }
  24. func main() {
  25.         var errA error = ErrA{}
  26.         var errB error = ErrB{wrappedErr: errA}
  27.         for err := errB; err != nil; err = errors.Unwrap(err) {
  28.                 fmt.Println(err)
  29.                 if errors.Is(err, errA) {
  30.                         fmt.Println("errA is the cause of the error")
  31.                 }
  32.                 fmt.Println()
  33.         }
  34. }
复制代码
结果是
1.png

无法识别当前这一层的error是ErrB
造成这个问题的原因是, errors.Is的源码实现如下:
  1. func is(err, target error, targetComparable bool) bool {
  2.         for {
  3.                 if targetComparable && err == target {
  4.                         return true
  5.                 }
  6.                 if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
  7.                         return true
  8.                 }
  9.                 switch x := err.(type) {
  10.                 case interface{ Unwrap() error }:
  11.                         err = x.Unwrap()
  12.                         if err == nil {
  13.                                 return false
  14.                         }
  15.                 case interface{ Unwrap() []error }:
  16.                         for _, err := range x.Unwrap() {
  17.                                 if is(err, target, targetComparable) {
  18.                                         return true
  19.                                 }
  20.                         }
  21.                         return false
  22.                 default:
  23.                         return false
  24.                 }
  25.         }
  26. }
复制代码
如果遇上可以Unwrap的接口类型直接再次Unwrap去找是否存在一个类型是符合条件的
对此, 需要使用类型断言的方式来判断错误类型
  1. func main() {
  2.         var errA error = ErrA{}
  3.         var errB error = ErrB{wrappedErr: errA}
  4.         for err := errB; err != nil; err = errors.Unwrap(err) {
  5.                 fmt.Println(err)
  6.                 switch err.(type) {
  7.                 case ErrA:
  8.                         fmt.Println("ErrA")
  9.                 case ErrB:
  10.                         fmt.Println("ErrB")
  11.                 default:
  12.                         fmt.Println("default")
  13.                 }
  14.                 fmt.Println()
  15.         }
  16. }
复制代码
效果如下:
2.png

成功识别出了错误的类型


  • 如果需要准确识别错误的具体类型, 而不是检查这个报错包含哪些错误的话, 应该使用这个
参考

GO语言的errors库
Go语言(golang)新发布的1.13中的Error Wrapping深度分析

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册