Go 1.13 errors 新特性(错误封装及 Unwrap、Is、As函数)的使用

Go 1.13 errors 新特性(错误封装及 Unwrap、Is、As函数)的使用


由于在工作中接触到了 fmt.Errorf函数以及后续对错误进行判断时使用到了 Unwrap 与 Is ,因此去了解一下 1.13 版本中 errors 包中引入的新特性。本文参考资料包括但不限于:

Go 1.13 errors 基本用法
golang关于errors.As两个参数的问题记录

错误的封装

在 1.13 引入了错误处理的新特性之后,我们可以对错误进行多次的封装,我们可以简单地把错误的封装理解为套娃,第一个错误套在第二个错误之中,第二个错误又可以套在第三个错误之中,如果你想的话可以将这个过程不断进行下去。

为了实现错误的"套娃",通常使用 fmt.Errorf 函数即可,除此之外也可以使用自定义结构体的方式实现(可参考 Go 1.13 errors 基本用法)

由于 fmt.Errorf 函数的使用比较方便,因此接下来都将使用该方法实现方法的封装。fmt.Errorf 方法使用 %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

解封: errors.Unwarp

现在我们可以简单的实现错误的套娃了,此时自然也会有将套娃一层层打开的需求,此时就需要使用 errors.Unwrap 函数了。下面的例子可以很清楚的解释错误的封装与解封

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

更优雅的错误判断:errors.Is

errors.Is 方法判断被包装的 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

获得指定类型错误:errors.As

理解 As 方法首先需要理解 error 的定义,go 中 error 实际上就是一个包含 Error 方法的接口,只要实现了 Error 方法都可以作为 errorAs 函数的作用就是从错误中获取第一个指定类型的错误。还是从实验中体验用法,该实验过程参考 golang关于errors.As两个参数的问题记录。需要注意一点:实际上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() {
	// 测试 errors.As 方法
	// 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)

	// 使用 errors.As 方法将错误解封到 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
}

这段程序中主要做了以下几件事:

  1. 定义一个自定义的结构体 FuncErr 并实现了 error 接口,即定义一个我们自己错误类型
  2. 利用构造函数得到一个 FuncErr 实例
  3. 对该错误进行两层封装
  4. 对上述的三个错误调用 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

	// 测试 errors.As 方法
	// 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)

	// 使用 errors.As 方法将错误解封到 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()
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值