如何用GO语言实现高效的异常处理机制?

摘要:在开发中, 处理异常是很重要的, 考虑各种错误情况并且提出对应的解决办法是保证不出大BUG的重要之处. error in go GO语言的异常是很简单的, 只需要实现Error函数接口即可 func (e ErrA) Error() str
在开发中, 处理异常是很重要的, 考虑各种错误情况并且提出对应的解决办法是保证不出大BUG的重要之处. error in go GO语言的异常是很简单的, 只需要实现Error函数接口即可 func (e ErrA) Error() string { return "ErrA" } 过于简单的实现, 和C一样 errors 这是GO的一个库, 专门用来处理异常 Go语言的标准库一般会在后面加一个s代表是处理这种类型的标准库, 例如strings 异常处理 error类别判断 类型断言实现类别判断 Go语言提供了类型断言的方式, 针对接口, 可以实现不同类型之间的相互转换 这就是为什么errA需要是error类型, 因为是接口类型才能转换 var errA error = ErrA{} var errB error = ErrB{} fmt.Println(errA.Error()) fmt.Println(errB.Error()) if e, ok := errA.(ErrB); ok { fmt.Println(e) fmt.Println("found ErrB") } else { fmt.Println("not found ErrB") } 这个简单好用, 如果需要嵌套的异常处理, 那么应该选择使用这个. 当然可以使用switch语句针对多种错误类型进行处理, 针对多个类型的时候使用这个方式可以更好的处理 func main() { var errA error = ErrA{} var errB error = ErrB{wrappedErr: errA} for err := errB; err != nil; err = errors.Unwrap(err) { fmt.Println(err) switch err.(type) { case ErrA: fmt.Println("ErrA") case ErrB: fmt.Println("ErrB") default: fmt.Println("default") } fmt.Println() } } 上述代码是使用switch配合类型断言来处理异常 errors函数类别判断 通过errors.Is或者errors.As函数实现类别的判断 Go语言官方提供的异常处理, 也只是简单的封装. 这个函数的本质是检查这个error是否包含某个error(一个error可以包含多个error, 嵌套error) var errA error = ErrA{} var errB error = ErrB{} fmt.Println(errA.Error()) fmt.Println(errB.Error()) if errors.Is(errA, errB) { fmt.Println("errA is errB") } else { fmt.Println("errA is not errB") } 使用这个函数让代码看起来十分的简洁 使用errors提供的函数在处理嵌套的error的时候可能存在问题, 我们在下面讨论 嵌套error 有些时候我们需要把error一层一层wrap起来然后向上抛出异常进行处理. 像是多层的Throw错误. errors库提供了Unwrap函数, 如果error实现了这个接口, 那么就可以实现error嵌套 package main import ( "errors" "fmt" ) type ErrA struct { wrappedErr error } func (e ErrA) Error() string { return fmt.Sprintf("ErrA -> %v", e.wrappedErr) } func (e ErrA) Unwrap() error { return e.wrappedErr } type ErrB struct { wrappedErr error } func (e ErrB) Error() string { return fmt.Sprintf("ErrB -> %v", e.wrappedErr) } func (e ErrB) Unwrap() error { return e.wrappedErr } func main() { errA := ErrA{} errB := ErrB{} fmt.Println(errA.Error()) fmt.Println(errB.Error()) if errors.Is(errA, ErrB{}) { fmt.Println("errA is ErrB") } else { fmt.Println("errA is not ErrB") } fmt.Println() var wrapErr error = ErrA{wrappedErr: ErrB{}} for err := wrapErr; err != nil; err = errors.Unwrap(err) { fmt.Println(err) } } 可以使用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类型. 下面是一个例子 package main import ( "errors" "fmt" ) type ErrA struct { wrappedErr error } func (e ErrA) Error() string { return fmt.Sprintf("ErrA -> %v", e.wrappedErr) } func (e ErrA) Unwrap() error { return e.wrappedErr } type ErrB struct { wrappedErr error } func (e ErrB) Error() string { return fmt.Sprintf("ErrB -> %v", e.wrappedErr) } func (e ErrB) Unwrap() error { return e.wrappedErr } func main() { var errA error = ErrA{} var errB error = ErrB{wrappedErr: errA} for err := errB; err != nil; err = errors.Unwrap(err) { fmt.Println(err) if errors.Is(err, errA) { fmt.Println("errA is the cause of the error") } fmt.Println() } } 结果是 无法识别当前这一层的error是ErrB 造成这个问题的原因是, errors.Is的源码实现如下: func is(err, target error, targetComparable bool) bool { for { if targetComparable && err == target { return true } if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) { return true } switch x := err.(type) { case interface{ Unwrap() error }: err = x.Unwrap() if err == nil { return false } case interface{ Unwrap() []error }: for _, err := range x.Unwrap() { if is(err, target, targetComparable) { return true } } return false default: return false } } } 如果遇上可以Unwrap的接口类型直接再次Unwrap去找是否存在一个类型是符合条件的 对此, 需要使用类型断言的方式来判断错误类型 func main() { var errA error = ErrA{} var errB error = ErrB{wrappedErr: errA} for err := errB; err != nil; err = errors.Unwrap(err) { fmt.Println(err) switch err.(type) { case ErrA: fmt.Println("ErrA") case ErrB: fmt.Println("ErrB") default: fmt.Println("default") } fmt.Println() } } 效果如下: 成功识别出了错误的类型 如果需要准确识别错误的具体类型, 而不是检查这个报错包含哪些错误的话, 应该使用这个 参考 GO语言的errors库 Go语言(golang)新发布的1.13中的Error Wrapping深度分析