Go 1.13 errors 新特性(错误封装及 Unwrap、Is、As函数)的使用
- 错误的封装
- 解封:
- 更优雅的错误判断:
- 获得指定类型错误:
- 总结
由于在工作中接触到了
函数以及后续对错误进行判断时使用到了 Unwrap 与 Is ,因此去了解一下 1.13 版本中 errors 包中引入的新特性。本文参考资料包括但不限于:
Go 1.13 errors 基本用法
golang关于两个参数的问题记录
错误的封装
在 1.13 引入了错误处理的新特性之后,我们可以对错误进行多次的封装,我们可以简单地把错误的封装理解为套娃,第一个错误套在第二个错误之中,第二个错误又可以套在第三个错误之中,如果你想的话可以将这个过程不断进行下去。
为了实现错误的"套娃",通常使用 函数即可,除此之外也可以使用自定义结构体的方式实现(可参考 Go 1.13 errors 基本用法)
由于 函数的使用比较方便,因此接下来都将使用该方法实现方法的封装。
方法使用 %w 参数返回一个被包装的 error。例如
err1 := errors.New("this is a error")
err2 := fmt.Errorf("this is a error including the first error: %w\n", err1)
通过该方法很容易就实现了对错误的封装。下面是另一个相似的例子
firstErr := errors.New("this is the first error")
secondErr := fmt.Errorf("this is the second error, it include the first error: %w", firstErr)
thirdErr := fmt.Errorf("this is the third error, it include the second error: %w", secondErr)
// 输出各个错误
fmt.Printf("%+v\n", firstErr) // this is the first error
fmt.Printf("%+v\n", secondErr) // this is the second error, it include the first error: this is the first error
fmt.Printf("%+v\n", thirdErr) // this is the third error, it include the second error: this is the second error, it include the first error: this is the first error
解封:
现在我们可以简单的实现错误的套娃了,此时自然也会有将套娃一层层打开的需求,此时就需要使用 函数了。下面的例子可以很清楚的解释错误的封装与解封。
firstErr := errors.New("this is the first error")
secondErr := fmt.Errorf("this is the second error, it include the first error: %w", firstErr)
thirdErr := fmt.Errorf("this is the third error, it include the second error: %w", secondErr)
// 输出各个错误
fmt.Printf("%+v\n", firstErr) // this is the first error
fmt.Printf("%+v\n", secondErr) // this is the second error, it include the first error: this is the first error
fmt.Printf("%+v\n", thirdErr) // this is the third error, it include the second error: this is the second error, it include the first error: this is the first error
// Unwrap 函数测试
// 从 thirdErr 中解封各个错误
unwrapFirst := errors.Unwrap(thirdErr)
unwrapSecond := errors.Unwrap(unwrapFirst)
unwrapThird := errors.Unwrap(unwrapSecond)
unwrapFourth := errors.Unwrap(unwrapThird)
fmt.Println(unwrapFirst) // this is the second error, it include the first error: this is the first error
fmt.Println(unwrapSecond) // this is the first error
fmt.Println(unwrapThird) // <nil>
fmt.Println(unwrapFourth) // <nil>
从例子中有两个发现:
- 对一个没有封装错误的错误进行解封会得到一个 nil (已经找到最小的套娃了,无法继续拆开了)
- 对一个 nil 进行解封不会报错,会得到一个 nil
更优雅的错误判断:
方法判断被包装的 error 是否是包含指定错误,与直接使用
==
判断不同,Is
方法可以用于判断错误中是否包含指定的错误。Is
函数签名为:func Is(err, target error) bool
,第一个参数为需要被判断的函数,第二个参数为目标错误,返回布尔类型变量表示是否包含指定错误。通过下面一个简单的例子去理解该函数
...(与前面代码一致)
// Is 函数测试
fmt.Println(errors.Is(firstErr, firstErr)) // true
fmt.Println(errors.Is(secondErr, firstErr)) // true
fmt.Println(errors.Is(thirdErr, firstErr)) // true
fmt.Println(errors.Is(firstErr, secondErr)) // false
fmt.Println(errors.Is(secondErr, secondErr)) // true
fmt.Println(errors.Is(thirdErr, secondErr)) // true
结果也很清晰
- 前一组测试使用
firstErr
作为目标错误,因此三个错误中都包含该错误,因此都返回 true - 第二组测试使用
secondErr
作为目标错误,因此firstErr
中不包含该错误,因此只有firstErr
返回 false
获得指定类型错误:
理解 As
方法首先需要理解 error
的定义,go 中 error
实际上就是一个包含 Error
方法的接口,只要实现了 Error
方法都可以作为 error
。As
函数的作用就是从错误中获取第一个指定类型的错误。还是从实验中体验用法,该实验过程参考 golang关于两个参数的问题记录。需要注意一点:实际上As函数是在使用reflect包对两个参数的类型进行比较,其中第二个参数是第一个参数类型的指针类型
// 定义一个结构体用于实现 error 接口
type FuncErr struct {
msg string
code int
}
// 实现 error 接口
func (f FuncErr) Error() string {
return f.msg
}
// 方便验证的调试方法
func (f FuncErr) PrintInfo() {
fmt.Printf("msg: %s, code: %d\n", f.msg, f.code)
}
// 相当于我们自定义 error 的构造函数
func throw(s string, c int) error {
return FuncErr{
msg: s,
code: c,
}
}
func WrappingError() {
// 测试 方法
// 1. 生成一个 FuncErr 类型的错误
funcErr := throw("this is a func error", 1)
// 2. 将该错误包装两层
firstWrapped := fmt.Errorf("this is the first wrapped error that include %w", funcErr)
secondWrapped := fmt.Errorf("this is the second wrapped error that include %w", firstWrapped)
// 使用 方法将错误解封到 FuncErr 类型
var targetError FuncErr
// As 函数测试
fmt.Println(errors.As(funcErr, &targetError)) // true
fmt.Println(targetError) // this is a func error
targetError.PrintInfo() // msg: this is a func error, code: 1
fmt.Println(errors.As(firstWrapped, &targetError)) // true
fmt.Println(targetError) // this is a func error
targetError.PrintInfo() // msg: this is a func error, code: 1
fmt.Println(errors.As(secondWrapped, &targetError)) // true
fmt.Println(targetError) // this is a func error
targetError.PrintInfo() // msg: this is a func error, code: 1
}
这段程序中主要做了以下几件事:
- 定义一个自定义的结构体
FuncErr
并实现了error
接口,即定义一个我们自己错误类型 - 利用构造函数得到一个
FuncErr
实例 - 对该错误进行两层封装
- 对上述的三个错误调用
As
函数从中提取FuncErr
类型的错误
总结
至此,基本上把 errors
中的新特性简单介绍完了。以下为本次实验的完整代码
package wrappingerror
import (
"errors"
"fmt"
)
func WrappingError() {
firstErr := errors.New("this is the first error")
secondErr := fmt.Errorf("this is the second error, it include the first error: %w", firstErr)
thirdErr := fmt.Errorf("this is the third error, it include the second error: %w", secondErr)
// 输出各个错误
fmt.Printf("%+v\n", firstErr) // this is the first error
fmt.Printf("%+v\n", secondErr) // this is the second error, it include the first error: this is the first error
fmt.Printf("%+v\n", thirdErr) // this is the third error, it include the second error: this is the second error, it include the first error: this is the first error
// Unwrap 函数测试
// 从 thirdErr 中解封各个错误
unwrapFirst := errors.Unwrap(thirdErr)
unwrapSecond := errors.Unwrap(unwrapFirst)
unwrapThird := errors.Unwrap(unwrapSecond)
unwrapFourth := errors.Unwrap(unwrapThird)
fmt.Println(unwrapFirst) // this is the second error, it include the first error: this is the first error
fmt.Println(unwrapSecond) // this is the first error
fmt.Println(unwrapThird) // <nil>
fmt.Println(unwrapFourth) // <nil>
// Is 函数测试
fmt.Println(errors.Is(firstErr, firstErr)) // true
fmt.Println(errors.Is(secondErr, firstErr)) // true
fmt.Println(errors.Is(thirdErr, firstErr)) // true
fmt.Println(errors.Is(firstErr, secondErr)) // false
fmt.Println(errors.Is(secondErr, secondErr)) // true
fmt.Println(errors.Is(thirdErr, secondErr)) // true
// 测试 方法
// 1. 生成一个 FuncErr 类型的错误
funcErr := throw("this is a func error", 1)
// 2. 将该错误包装两层
firstWrapped := fmt.Errorf("this is the first wrapped error that include %w", funcErr)
secondWrapped := fmt.Errorf("this is the second wrapped error that include %w", firstWrapped)
// 使用 方法将错误解封到 FuncErr 类型
var targetError FuncErr
// As 函数测试
fmt.Println(errors.As(funcErr, &targetError)) // true
fmt.Println(targetError) // this is a func error
targetError.PrintInfo() // msg: this is a func error, code: 1
fmt.Println(errors.As(firstWrapped, &targetError)) // true
fmt.Println(targetError) // this is a func error
targetError.PrintInfo() // msg: this is a func error, code: 1
fmt.Println(errors.As(secondWrapped, &targetError)) // true
fmt.Println(targetError) // this is a func error
targetError.PrintInfo() // msg: this is a func error, code: 1
}
type FuncErr struct {
msg string
code int
}
func (f FuncErr) Error() string {
return f.msg
}
func (f FuncErr) PrintInfo() {
fmt.Printf("msg: %s, code: %d\n", f.msg, f.code)
}
func throw(s string, c int) error {
return FuncErr{
msg: s,
code: c,
}
}
在 main
中调用即可完成测试代码的执行
package main
import "godemo/wrappingerror"
func main() {
wrappingerror.WrappingError()
}