加入收藏 | 设为首页 | 会员中心 | 我要投稿 宁德站长网 (https://www.0593zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 运营中心 > 建站资源 > 优化 > 正文

一文悟透备受争议的 Go 语言错误处理

发布时间:2019-09-19 22:54:20 所属栏目:优化 来源:佚名
导读:副标题#e# 写过 C 的同学知道,C 语言中常常返回整数错误码(errno)来表示函数处理出错,通常用 -1 来表示错误,用 0 表示正确。 而在 Go 中,我们使用 error 类型来表示错误,不过它不再是一个整数类型,是一个接口类型: typeerrorinterface{ Error()strin

譬如下面这段伪代码:

  1. func fn() error { 
  2.     x, err := bar.Foo() 
  3.     if err != nil { 
  4.         return err 
  5.     } 
  6.      
  7.     // use x 
  8.     return nil 

作为调用者,调用完 Foo 函数后,只用知道 Foo 是正常工作还是出了问题。也就是说你只需要判断 err 是否为空,如果不为空,就直接返回错误。否则,继续后面的正常流程,不需要知道 err 到底是什么。

这就是处理 Opaque errors 这种类型错误的策略。

当然,在某些情况下,这样做并不够用。例如,在一个网络请求中,需要调用者判断返回的错误类型,以此来决定是否重试。这种情况下,作者给出了一种方法:

  1. type temporary interface { 
  2.     Temporary() bool 
  3. func IsTemporary(err error) bool { 
  4.     te, ok := err.(temporary) 
  5.     return ok && te.Temporary() 

就是说,不去判断错误的类型到底是什么,而是去判断错误是否具有某种行为,或者说实现了某个接口。

来个例子:

type temporary interface { Temporary() bool}func IsTemporary(err error) bool { te, ok := err.(temporary) return ok && te.Temporary()}

拿到网络请求返回的 error 后,调用 IsTemporary 函数,如果返回 true,那就重试。

这么做的好处是在进行网络请求的包里,不需要 import 引用定义错误的包。

handle not just check errors

这一节要说第二句箴言:“Don’t just check errors, handle them gracefully”。

  1. func AuthenticateRequest(r *Request) error { 
  2.  err := authenticate(r.User) 
  3.  if err != nil { 
  4.  return err 
  5.  } 
  6.  return nil 

上面这个例子中的代码是有问题的,直接优化成一句就可以了:

  1. func AuthenticateRequest(r *Request) error { 
  2.  return authenticate(r.User) 

还有其他的问题,在函数调用链的最顶层,我们得到的错误可能是:No such file or directory。

这个错误反馈的信息太少了,不知道文件名、路径、行号等等。

尝试改进一下,增加一点上下文:

  1. func AuthenticateRequest(r *Request) error { 
  2.  err := authenticate(r.User) 
  3.  if err != nil { 
  4.  return fmt.Errorf("authenticate failed: %v", err) 
  5.  } 
  6.  return nil 

这种做法实际上是先错误转换成字符串,再拼接另一个字符串,最后,再通过 fmt.Errorf 转换成错误。这样做破坏了相等性检测,即我们无法判断错误是否是一种预先定义好的错误了。

应对方案是使用第三方库:github.com/pkg/errors。提供了友好的界面:

  1. // Wrap annotates cause with a message. 
  2. func Wrap(cause error, message string) error 
  3. // Cause unwraps an annotated error. 
  4. func Cause(err error) error 

通过 Wrap 可以将一个错误,加上一个字符串,“包装”成一个新的错误;通过 Cause 则可以进行相反的操作,将里层的错误还原。

有了这两个函数,就方便很多:

  1. func ReadFile(path string) ([]byte, error) { 
  2.     f, err := os.Open(path) 
  3.     if err != nil { 
  4.         return nil, errors.Wrap(err, "open failed") 
  5.     } 
  6.     defer f.Close() 
  7.      
  8.     buf, err := ioutil.ReadAll(f) 
  9.     if err != nil { 
  10.         return nil, errors.Wrap(err, "read failed") 
  11.     } 
  12.     return buf, nil 

(编辑:宁德站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!