前言快一个月没有更新技术文章了,这段时间投注了较多的时间学习字节的开源项目 Kitex/Hertz,并维护一些简单的 issue ,有兴趣的同学也可以去了解:
https://www.cloudwego.io/
这段时间迟迟没有更新文章,一方面是接触到了很多大佬,反观自身技术深度远远不及,变得不敢轻易下笔;另一方面反思了一下自己之前的写作,确实也有一些功利的成分,有时为了更新而更新 , 打算纠正 。
接触开源之后,我感受到了开源社区打磨一个项目的认真与严谨 , 后续也希望自己能以此为鉴,对开源、对写作都是如此 。
扯远了 , 写作这篇文章的原因是我在写单元测试的时候,有时会涉及 errors.Is
和 errors.As
方法的调用,借此做一个总结 。
error 的定义首先需要明确 Go 语言中的错误是通过接口定义的,因此是一个引用类型 。
type error interface { Error() string}// Go 提供了一个默认实现type errorString struct {s string}?func (e *errorString) Error() string {return e.s}
那么如果我要创建一个 error 实例,可以选择下面的方式:
func main() { // 此时创建的两个 error 都是 errorString 结构类型的 errA := errors.New("new error a") fmt.Println(errA) errB := fmt.Errorf("new error %s", "b") fmt.Println(errB)}/*打印结果:new error anew error b*/
wrapError 的定义wrapError 是嵌套的 error,也实现了 error 接口的 Error
方法,本质也是一个 error , 并声明了一个 Unwrap
方法用于拆包装 。
type wrapError struct {msg stringerr error}?func (e *wrapError) Error() string {return e.msg}?func (e *wrapError) Unwrap() error {return e.err}
通过 fmt.Errorf
方法配合 %w
占位符创建嵌套类型的 wrapError 。
var BaseErr = errors.New("the underlying base error")?func main() {err1 := fmt.Errorf("wrap base: %w", BaseErr)fmt.Println(err1)err2 := fmt.Errorf("wrap err1: %w", err1)fmt.Println(err2)}/* 打印结果: wrap base: the underlying base error wrap err1: wrap base: the underlying base error*/
为什么 fmt.Errorf
用了占位符 %w
之后创建的就是 wrapError 类型,而用了 fmt.Errorf
但只是选择其他占位符如上述示例中的 %s
创建的就是 errorString 类型?
可以简单看一下 fmt.Errorf
方法的源码:
func Errorf(format string, a ...any) error { p := newPrinter() p.wrapErrs = true p.doPrintf(format, a) s := string(p.buf) var err error if p.wrappedErr == nil { err = errors.New(s) } else { err = &wrapError{s, p.wrappedErr} } p.free() return err}
核心就是 p.doPrintf(format, a)
调用后 , 如果包含 %w
占位符则会先创建内层的 error ,赋值给 p.wrappedErr
,从而触发 wrapError 的创建逻辑 。
【Go 源码解读|如何用好 errors 库的 errors.Is 与 errors.As() 方法】你也可以进一步去看 p.doPrintf(format, a)
的实现印证这个流程 。
errors.Is判断被包装的error是否包含指定错误 。
var BaseErr = errors.New("the underlying base error")?func main() { err1 := fmt.Errorf("wrap base: %w", BaseErr) err2 := fmt.Errorf("wrap err1: %w", err1) println(err2 == BaseErr) // false if !errors.Is(err2, BaseErr) { panic("err2 is not BaseErr") } println("err2 is BaseErr")}/*打印结果:falseerr2 is BaseErr*/
来看一下 errors.Is
方法的源码:
func Is(err, target error) bool { if target == nil { return err == target }? isComparable := reflectlite.TypeOf(target).Comparable() for { if isComparable && err == target { return true } if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) { return true } if err = Unwrap(err); err == nil { return false } }}?func Unwrap(err error) error {u, ok := err.(interface {Unwrap() error})if !ok {return nil}return u.Unwrap()}
如果这个 err 自己实现了 interface{ Is(error) bool }
接口 , 通过接口断言,可以调用 Is
方法判断 err 是否与 target 相等 。
否则递归调用 Unwrap
方法拆包装,返回下一层的 error 去判断是否与 target 相等 。
errors.As提取指定类型的错误,判断包装的 error 链中,某一个 error 的类型是否与 target 相同,并提取第一个符合目标类型的错误的值,将其赋值给 target 。
推荐阅读
- 微信怎么建群步骤(如何增加微信群人数)
- 微信如何建立家群(如何建立微信朋友圈)
- 微信怎么建群微信如何建群(怎样建新群并且设自己为群主)
- 华为手机如何截屏(华为手势截屏)
- 华为手机如何截屏(华为手机如何截长屏)
- 华为手机如何全部截屏(长截屏教程华为)
- 支付宝如何开通借呗额度(支付宝借呗额度没了还能恢复吗)
- 98年属虎女事业发展如何 赶紧来看看她们的情况
- 手相女右手解析 教你如何看懂女人的手相
- 家中有老鼠如何驱赶 家里有老鼠怎么办才能快速驱赶