Golang学习之error浅析

由于Golang的语言设计的原因,不管是不是愿意,每个golang开发者的几乎每一段代码都需要与error做缠斗 。下面我就简单分析一下golang中的error相关 。
转自:https://www.jianshu.com/p/606d0e60c58d
参考:Go语言中文文档:www.topgoer.com
error是什么?首先需要明确的一点是,golang中对于error类型的定义是什么?不同于很多语言的exception机制,golang在语言层面经常需要显示的做错误处理 。其实从本质上来讲,golang中的error就是一个接口:
// The error built-in interface type is the conventional interface for// representing an error condition, with the nil value representing no error.type error interface {Error() string}和所有接口含义一样,nil表示零值 。
before Go1.13在golang的1.13版本之前,官方给到的错误处理方法寥寥无几,只有用来构造无额外参数的错误的errors.New和构造带额外参数的错误的fmt.Errorf 。当时,经常需要使用标准库之外的扩展库来支持更丰富发错误构造和处理,比如由Dave Cheney主导的github.com/pkg/errors 。
这些额外的error库主要的关注点在于提供方法用于描述错误的层级 。回到上面的错误本身的定义,只是一个包含Error方法的接口,本身缺乏对于类似其他语言中类似traceback的描述能力,无法追踪错误的详细栈信息 。
而以github.com/pkg/errors为代表的库,通过实现Wrap和Cause方法对来提供了包装/拆包错误的能力,提供了类似traceback(但需要开发者自己定义额外信息)和逐层解析并比较错误的能力 。通过这个方法对,我们可以实现下面的用例:
// 为错误提供更丰富的上下文信息,方便定位错误if _, err := ioutil.ReadAll(r);err != nil {return errors.Wrap(err, "read file failed")}// 判断错误的根错误是什么,根据最初的错误类型判断需要走什么错误处理逻辑switch err := errors.Cause(err).(type) {case *io.EOF:// handle specificallydefault:// unknown error}After Go1.13对于上面描述的错误处理,相比于较为成熟的exception处理模式,天生缺乏错误栈信息的缺点让很多开发者非常不满,虽然第三方库或多或少的弥补了这个缺点,但是作为开发中占比非常大的一部分代码,官方库的缺乏支持还是令人不满 。所以Go team在1.13版本中进一步完善了错误相关的官方库支持 。
首先,提供了%w构造方法和errors.Unwrap的方法对来支持类似Wrap和Cause相关的能力 。
// 为错误提供更丰富的上下文信息,方便定位错误if _, err := ioutil.ReadAll(r);err != nil {return fmt.Errorf("read file failed with err:%w", err)}// 判断错误的根错误是什么,根据最初的错误类型判断需要走什么错误处理逻辑rawErr := errors.Unwrap(err)不仅如此,官方库还带来了两个错误比较相关的API:
【Golang学习之error浅析】if errors.Is(err, io.EOF){...}var eof io.EOFif errors.As(err, &eof){...}其中,errors.Is方法会逐层调用Unwrap方法,去和目标 err做比较,知道没有Unwrap方法或者err比较成功 。errors.As方法的作用类似于之前的针对错误的类型断言 。
至此,golang官方库提供了错误的构造方法,错误的比较方法,额外信息包装的能力,总体来说应该算是比较完善了 。
关于Go1.13错误处理相关的实现,可以参考 。
夭折的try另外一个小小的番外插曲,曾经有一个呼声颇高的错误处理相关的提案:引入try关键字来增强错误处理的能力 。主要使用方法如下:
// 包装调用方法readFile := try(ioutil.ReadAll(r))...// 函数层级统一defer func(){if err!=nil{switch err.(type){...}}}()带来的便利是减少了大量的if err!=nil语句,提供函数层级的统一错误处理处(一般在defer处) 。然而最后由于可读性和显式处理错误的种种原因,这个提案被拒绝了 。
更近一步的信息可以参考github上相关的讨论 和设计文档 。
实践基于go1.13提出的现有错误处理工具,我们大概能够采用下面的实践来进行错误处理:

  1. 针对基础错误类型,一般通过直接声明变量或者自定义结构:
// 常规的无额外参数的errorvar BasicErr1 = errors.New("this is a basic error.")func fn() error{...if conditionA{return BasicErr}}// 调用处if err!=nil{if errors.Is(err, BasicErr1){...}}// 带参数信息的错误type CustomErr struct {Code int64Msg string}func (e CustomErr)Error() string{return fmt.Sprintf("%d:%s", e.Code, e.Msg)}func fn() error{...if conditionA{return CustomErr{Code: 123, Msg: "test"}}}// 调用处if err!=nil{if e,ok:=err.(CustomErr);ok{...}}


    推荐阅读