服务粉丝

我们一直在努力
当前位置:首页 > 财经 >

Go1.20 继续小修小补 errors 库。。。

日期: 来源:脑子进煎鱼了收集编辑:陈煎鱼

大家好,我是煎鱼。

Go 的错误处理机制一直是无数人提了又争,被拒了又提的地方。最近 Go1.20 即将发布,针对 errors 标准库,有一个新的小修小补优化(wrapping multiple errors)。

今天来学习这个三顾茅庐最终不怎么成功的阉割版提案。

回顾 Go1.13 改进 errors

在 Go1.13 中,errors 标准库引入了 Wrapping Error 的概念,并增加了 Is/As/Unwarp 三个方法,用于对所返回的错误进行二次处理和识别。

简单来讲,Go 的 error 可以嵌套了,提供了三个配套的方法。例子:

func main() {
 e := errors.New("脑子进煎鱼了")
 w := fmt.Errorf("快抓住:%w", e)
 fmt.Println(w)
 fmt.Println(errors.Unwrap(w))
}

输出结果:

$ go run main.go
快抓住:脑子进煎鱼了
脑子进煎鱼了

在上述代码中,变量 w 就是一个嵌套一层的 error。最外层是 “快抓住:”,此处调用 %w 意味着 Wrapping Error 的嵌套生成。因此最终输出了 “快抓住:脑子进煎鱼了”。

需要注意的是,Go 并没有提供 Warp 方法,而是直接扩展了 fmt.Errorf 方法。而下方的输出由于直接调用了 errors.Unwarp 方法,因此将 “取” 出一层嵌套,最终直接输出 “脑子进煎鱼了”。

对 Wrapping Error 有了基本理解后,我们简单介绍一下三个配套方法:

func Is(err, target error) bool
func As(err error, target interface{}) bool
func Unwrap(err error) error

errors.Is

方法签名:

func Is(err, target error) bool

方法例子:

func main() {
 if _, err := os.Open("non-existing"); err != nil {
  if errors.Is(err, os.ErrNotExist) {
   fmt.Println("file does not exist")
  } else {
   fmt.Println(err)
  }
 }

}

errors.Is 方法的作用是判断所传入的 err 和 target 是否同一类型,如果是则返回 true。

errors.As

方法签名:

func As(err error, target interface{}) bool

方法例子:

func main() {
 if _, err := os.Open("non-existing"); err != nil {
  var pathError *os.PathError
  if errors.As(err, &pathError) {
   fmt.Println("Failed at path:", pathError.Path)
  } else {
   fmt.Println(err)
  }
 }

}

errors.As 方法的作用是从 err 错误链中识别和 target 相同的类型,如果可以赋值,则返回 true。

errors.Unwarp

方法签名:

func Unwrap(err error) error

方法例子:

func main() {
 e := errors.New("脑子进煎鱼了")
 w := fmt.Errorf("快抓住:%w", e)
 fmt.Println(w)
 fmt.Println(errors.Unwrap(w))
}

该方法的作用是将嵌套的 error 解析出来,若存在多级嵌套则需要调用多次 Unwarp 方法。

问题在哪里

在 Go1.13 后,我们可以通过 fmt.Errorf 方法的把多个错误存进错误树中。

Errorf 方法内部代码如下:

func Errorf(format string, a ...any) error {
 ...
 var err error
 if p.wrappedErr == nil {
  err = errors.New(s)
 } else {
  err = &wrapError{s, p.wrappedErr}
 }
 p.free()
 return err
}

type wrapError struct {
 msg string
 err error
}

简单来讲,就是基于 wrapError 结构体实现了 Error interface,然后一层层往上套 error ,形成了错误树。

这看上去,一切都很美好,有个场景没有被考虑在内...如果有多个错误怎么办,又或是想将多个错误封装成一个,想自定义呢?这得咋整?

按逻辑来看,取出来的得一个个 Unwrap,再根据诉求去自己写自定义逻辑。这是比较麻烦的,API 没有充分提供帮助。

新提案

之前有提过类似的提案,可惜惨遭拒绝了。@Damien Neil 大佬熟络 Go 团队的流程、规范、风格,再度提出《errors: add support for wrapping multiple errors[1]》,挑战争议领域。

在诸多让步和讨论后,接纳了一个错误可以封装多个错误的特性,方案是原 Go1.13 API 的修改和 Go1.20 新增 errors.Join 方法和配套的方法改造。

Unwrap 函数将支持会封装多个错误:

Unwrap() []error

术语从 “错误链” 修改为 “错误树”。配套方法 errors.Is、errors.As、fmt.Errorf 都进行了改造。

对应如下:

  • errors.Is:如果能够匹配上任何错误,则返回 true。
  • errors.As:返回第一个匹配的错误。
  • fmt.Errorf:将会把多个错误封装在用户定义的布局中。

新 API Join 函数签名如下:

func Join(errs ...error) error 

对应的例子:

func main() {
 err1 := errors.New("err1")
 err2 := errors.New("err2")
 err := errors.Join(err1, err2)
 fmt.Println(err)
 if errors.Is(err, err1) {
  fmt.Println("err is err1")
 }
 if errors.Is(err, err2) {
  fmt.Println("err is err2")
 }
}

输出结果:

err1
err2
err is err1
err is err2

被 Join 的多个 error 默认将会通过换行符 \n 进行分隔来组装。

核心就是通过新增的 errors.Join 方法实现多个错误封装到一个错误中。方便了你去做多个错误的一次性提取,如果需要自定义错误,那就要再自己开发。

社区内也有对多个错误支撑的比较好的,有需要的小伙伴可以再看看。以下是比较有名的三个库:

  • hashicorp/go-multierror[2]
  • go.uber.org/multierr[3]
  • tailscale.com/util/multierr[4]

总结

Go 错误处理已经做了多次补丁的补全了,虽然这次主要是支持了 wrapping multiple errors,起码也是能够解决个别场景。

像是前两年,就有同学做表单校验,在内部系统,想把 errors 一次性全部返回出来的,结果 validate []error 只支持返回第一条,也没办法简单的一次性提取,麻烦的很。

Go1.20 将会发布本文提到的新提案,修修补补又从 1.13 到 1.20。

推荐阅读

参考资料

[1]

errors: add support for wrapping multiple errors: https://github.com/golang/go/issues/53435

[2]

hashicorp/go-multierror: https://pkg.go.dev/github.com/hashicorp/go-multierror

[3]

go.uber.org/multierr: https://pkg.go.dev/go.uber.org/multierr

[4]

tailscale.com/util/multierr: https://pkg.go.dev/tailscale.com/util/multierr


关注和加煎鱼微信,

一手消息和知识,拉你进技术交流群

相关阅读

  • Go 语言代码风格规范-概述篇

  • 每门开发语言都会有其特有的风格规范(亦或指南),开发者遵循规范能带来显著收益,有效促进团队协作、减少 bug 错误、降低维护成本等。Google 开源的 Google Style Guides (https:/
  • Go 标准库 net/http库知道吗?能说说优缺点吗?

  • 前言哈喽,大家后,我是asong;这几天看了一下Go语言标准库net/http的源码,所以就来分享一下我的学习心得;为什么会突然想看http标准库呢?因为在面试的时候面试官问我你知道Go语言的n
  • Go版本大于1.13,程序里这样做错误处理才地道

  • 大家好,这里是每周都在陪你进步的网管。之前写过几篇关于 Go 错误处理的文章,发现文章里不少知识点都有点落伍了,比如Go在1.13后对错误处理增加了一些支持,最大的变化就是支持了
  • 醒醒吧,未来不会有 Go2 了!

  • 大家好,我是煎鱼。马上春节了,节前最后一更。提前预祝大家春节快乐!本周末在学习的时候,看到 Go 团队大当家 Russ Cox(下称:rsc)在近期分享的《GopherCon 2022: Russ Cox - Compati
  • 紧急提醒!福州严查!

  • 2022年以来,全市市场监管部门深入开展整治校外教育培训机构不规范问题、减轻中小学生课外负担“点题整治”行动,查处了一批校外培训机构违法违规案件,促进全市校外培训机构健康
  • 突然窒息,仍在抢救……注意,最近高发

  • 1月14日湖北宜昌三峡中心人民医院西陵院区急诊科仅下半夜就收治了5名醉汉有的喝得人事不省有的呕吐得一塌糊涂在宜昌三峡中心人民医院伍家院区急诊科每天也都会收治醉汉所谓
  • 苦难的2022,仍需要避世的“主题公园”。

  • 点击收听-评《阿凡达2:水之道》:约5.3分杨超:5分《长江图》导演,《不要抬头》等19期嘉宾这便是“元宇宙电影”的雏形。当你们还在“科幻”或“电影”的维度讨论它是否合格之时,

热门文章

  • “复活”半年后 京东拍拍二手杀入公益事业

  • 京东拍拍二手“复活”半年后,杀入公益事业,试图让企业捐的赠品、家庭闲置品变成实实在在的“爱心”。 把“闲置品”变爱心 6月12日,“益心一益·守护梦想每一步”2018年四

最新文章

  • Go1.20 继续小修小补 errors 库。。。

  • 大家好,我是煎鱼。Go 的错误处理机制一直是无数人提了又争,被拒了又提的地方。最近 Go1.20 即将发布,针对 errors 标准库,有一个新的小修小补优化(wrapping multiple errors)。今
  • Go 语言代码风格规范-概述篇

  • 每门开发语言都会有其特有的风格规范(亦或指南),开发者遵循规范能带来显著收益,有效促进团队协作、减少 bug 错误、降低维护成本等。Google 开源的 Google Style Guides (https:/
  • Go 标准库 net/http库知道吗?能说说优缺点吗?

  • 前言哈喽,大家后,我是asong;这几天看了一下Go语言标准库net/http的源码,所以就来分享一下我的学习心得;为什么会突然想看http标准库呢?因为在面试的时候面试官问我你知道Go语言的n
  • Go版本大于1.13,程序里这样做错误处理才地道

  • 大家好,这里是每周都在陪你进步的网管。之前写过几篇关于 Go 错误处理的文章,发现文章里不少知识点都有点落伍了,比如Go在1.13后对错误处理增加了一些支持,最大的变化就是支持了