前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go编程模式 - 4.错误处理

Go编程模式 - 4.错误处理

作者头像
junedayday
发布2021-08-05 14:52:20
3860
发布2021-08-05 14:52:20
举报
文章被收录于专栏:Go编程点滴Go编程点滴

注:本文的灵感来源于GOPHER 2020年大会陈皓的分享,原PPT的链接可能并不方便获取,所以我下载了一份PDF到git仓,方便大家阅读。我将结合自己的实际项目经历,与大家一起细品这份文档。

目录

  • 函数式处理
  • 对象嵌入错误
  • 错误包装

Functional

type Number struct {
 a int
 b string
 c bool
 d []int32
 e error
}

func (n *Number) parse(r io.Reader) error {
 if err := binary.Read(r, binary.BigEndian, &n.a); err != nil {
  return err
 }
 if err := binary.Read(r, binary.BigEndian, &n.b); err != nil {
  return err
 }
 if err := binary.Read(r, binary.BigEndian, &n.c); err != nil {
  return err
 }
 if err := binary.Read(r, binary.BigEndian, &n.d); err != nil {
  return err
 }
 if err := binary.Read(r, binary.BigEndian, &n.e); err != nil {
  return err
 }
 return nil
}

引入了函数式编程的方式,我们看看有什么改变

func (n *Number) parse(r io.Reader) error {
 // 先定义一个error
 var err error

 // 定义函数,注意这里的err的作用域是来自上面定义的
 read := func(data interface{}) {
  // 先检查error,如果已经有错误则不检查
  if err != nil {
   return
  }
  err = binary.Read(r, binary.BigEndian, data)
 }

 // 注意,这个函数的调用逻辑和之前的差别在于一点:
 // 即使前面的发生了error,下面的函数也会被调用
 read(&n.a)
 read(&n.b)
 read(&n.c)
 read(&n.d)
 read(&n.e)

 return err
}

ErrorObject

先看一个标准库中的实现

func main() {
 input := bytes.NewReader([]byte("hello"))
 
 // 扫描数据,这里不会直接返回错误
 scanner := bufio.NewScanner(input)
 for scanner.Scan() {
  token := scanner.Text()
  fmt.Println(token)
 }
 
 // 从Err()方法中获取错误
 if err := scanner.Err(); err != nil {
  fmt.Println(err)
 }
}

它的根本思想,是将error嵌入到了对象中。那我们借鉴一下

type Reader struct {
 r   io.Reader
 err error
}


func (r *Reader) read(data interface{}) {
 if r.err == nil {
  r.err = binary.Read(r.r, binary.BigEndian, data)
 }
}

func (n *Number) parse(reader io.Reader) error {
 r := Reader{r: reader}

 r.read(&n.a)
 r.read(&n.b)
 r.read(&n.c)
 r.read(&n.d)
 r.read(&n.e)

 return r.err
}

捎带提一句:个人不太喜欢上面scanner的错误处理方式,这个要求使用方对这个包很熟悉,否则很容易忘掉后面的错误处理逻辑。但后面处理错误的逻辑,就很直接地将错误返回,可读性很强。

Wrap

耗子叔给的例子是调用了github.com/pkg/errors下的wrap包,不过我更倾向于直接用原生的。

func main() {
 // 原始 error
 err := errors.New("level 1")
 fmt.Println(err)
 // level 1

 // wrap一下error,注意error的占位符是%w
 wraped := fmt.Errorf("%v: %w", "level 2", err)
 fmt.Println(wraped)
 // level 2: level 1

 // unwrap 后获得原来的错误
 unwraped := errors.Unwrap(wraped)
 fmt.Println(unwraped)
 // level 1

 // 过度unwrap会导致错误变成nil
 unwraped2 := errors.Unwrap(unwraped)
 fmt.Println(unwraped2)
 // nil
}

但在实际项目实践中,Wrap的这个特性并不好用:

如何Wrap Error,在多人协同开发、多模块开发过程中,很难统一。而一旦不统一,容易出现示例中的过度Unwrap的情况。

所以,我认为与其花大精力在制定错误的标准上,还不如利用fmt.Errorf将错误信息直观地表述出来。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-01-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Go编程点滴 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
  • Functional
  • ErrorObject
  • Wrap
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档