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

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

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

参考资料【Go 语言的错误处理机制是一个优秀的设计吗?】是知乎上的一个回答,阐述了 Go 对待错误和异常的不同处理方式,前者使用 error,后者使用 panic,这样的处理比较 Java 那种错误异常一锅端的做法更有优势。

【如何优雅的在Golang中进行错误处理】对于在业务上如何处理 error,给出了一些很好的示例。

尝试破局

这部分的内容主要来自 Dave cheney GoCon 2016 的演讲,参考资料可以直达原文。

经常听到 Go 有很多“箴言”,说得很顺口,但理解起来并不是太容易,因为它们大部分都是有故事的。例如,我们常说:

Don’t communicating by sharing memory, share memory by communicating.

文中还列举了很多,都很有意思:

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

下面我们讲三条关于 error 的“箴言”。

Errors are just values

Errors are just values 的实际意思是只要实现了 Error 接口的类型都可以认为是 Error,重要的是要理解这些“箴言”背后的道理。

作者把处理 error 的方式分为三种:

  • Sentinel errors
  • Error Types
  • Opaque errors

我们来挨个说。首先 Sentinel errors,Sentinel 来自计算机中常用的词汇,中文意思是“哨兵”。以前在学习快排的时候,会有一个“哨兵”,其他元素都要和“哨兵”进行比较,它划出了一条界限。

这里 Sentinel errors 实际想说的是这里有一个错误,暗示处理流程不能再进行下去了,必须要在这里停下,这也是一条界限。而这些错误,往往是提前约定好的。

例如,io 包里的 io.EOF,表示“文件结束”错误。但是这种方式处理起来,不太灵活:

  1. func main() { 
  2.     r := bytes.NewReader([]byte("0123456789")) 
  3.      
  4.     _, err := r.Read(make([]byte, 10)) 
  5.     if err == io.EOF { 
  6.         log.Fatal("read failed:", err) 
  7.     } 

必须要判断 err 是否和约定好的错误 io.EOF 相等。

再来一个例子,当我想返回 err 并且加上一些上下文信息时,就麻烦了:

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

在 readfile 函数里判断 err 不为空,则用 fmt.Errorf 在 err 前加上具体的 file 信息,返回给调用者。返回的 err 其实还是一个字符串。

造成的后果时,调用者不得不用字符串匹配的方式判断底层函数 readfile 是不是出现了某种错误。当你必须要这样才能判断某种错误时,代码的“坏味道”就出现了。

顺带说一句,err.Error() 方法是给程序员而非代码设计的,也就是说,当我们调用 Error 方法时,结果要写到文件或是打印出来,是给程序员看的。在代码里,我们不能根据 err.Error() 来做一些判断,就像上面的 main 函数里做的那样,不好。

Sentinel errors 最大的问题在于它在定义 error 和使用 error 的包之间建立了依赖关系。比如要想判断 err == io.EOF 就得引入 io 包,当然这是标准库的包,还 Ok。如果很多用户自定义的包都定义了错误,那我就要引入很多包,来判断各种错误。麻烦来了,这容易引起循环引用的问题。

因此,我们应该尽量避免 Sentinel errors,仅管标准库中有一些包这样用,但建议还是别模仿。

第二种就是 Error Types,它指的是实现了 error 接口的那些类型。它的一个重要的好处是,类型中除了 error 外,还可以附带其他字段,从而提供额外的信息,例如出错的行数等。

标准库有一个非常好的例子:

  1. // PathError records an error and the operation and file path that caused it. 
  2. type PathError struct { 
  3.     Op string 
  4.     Path string 
  5.     Err error 

PathError 额外记录了出错时的文件路径和操作类型。

通常,使用这样的 error 类型,外层调用者需要使用类型断言来判断错误:

  1. // underlyingError returns the underlying error for known os error types. 
  2. func underlyingError(err error) error { 
  3.     switch err := err.(type) { 
  4.     case *PathError: 
  5.         return err.Err 
  6.     case *LinkError: 
  7.         return err.Err 
  8.     case *SyscallError: 
  9.         return err.Err 
  10.     } 
  11.     return err 

但是这又不可避免地在定义错误和使用错误的包之间形成依赖关系,又回到了前面的问题。

即使 Error types 比 Sentinel errors 好一些,因为它能承载更多的上下文信息,但是它仍然存在引入包依赖的问题。因此,也是不推荐的。至少,不要把 Error types 作为一个导出类型。

最后一种,Opaque errors。翻译一下,就是“黑盒 errors”,因为你能知道错误发生了,但是不能看到它内部到底是什么。

(编辑:宁德站长网)

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