Golang error浅析

由于Golang的语言设计的原因,不管是不是愿意,每个golang开发者的几乎每一段代码都需要与error做缠斗。下面我就简单分析一下golang中的error相关。

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为代表的库,通过实现WrapCause方法对来提供了包装/拆包错误的能力,提供了类似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 specifically
default:
        // unknown error
}

After Go1.13

对于上面描述的错误处理,相比于较为成熟的exception处理模式,天生缺乏错误栈信息的缺点让很多开发者非常不满,虽然第三方库或多或少的弥补了这个缺点,但是作为开发中占比非常大的一部分代码,官方库的缺乏支持还是令人不满。所以Go team在1.13版本中进一步完善了错误相关的官方库支持。
首先,提供了%w构造方法和errors.Unwrap的方法对来支持类似WrapCause相关的能力。

// 为错误提供更丰富的上下文信息,方便定位错误
if _, err := ioutil.ReadAll(r);err != nil {
        return fmt.Errorf("read file failed with err:%w", err)
}

// 判断错误的根错误是什么,根据最初的错误类型判断需要走什么错误处理逻辑
rawErr := errors.Unwrap(err)

不仅如此,官方库还带来了两个错误比较相关的API:

if errors.Is(err, io.EOF){
    ...
}

var eof io.EOF
if 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. 针对基础错误类型,一般通过直接声明变量或者自定义结构:
// 常规的无额外参数的error
var 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 int64
    Msg 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{
        ...
    }
}
  1. 对于调用三方库获取的报错,一般将额外信息(比如调用参数,上下文信息等方便定位问题的信息)包装之后向上层调用方直接抛出:
if _,err:=ioutil.ReadAll(r);err!=nil{
    return fmt.Errorf("read file failed:%w", err)
}

// 调用方
if err!=nil{
    if errors.Is(err, io.EOF){
        ...
    }
}

关于错误日志的处理部分,为了防止处处打日志造成的上下文信息分散和大量信息冗余,一般建议的处理方式是对于内部方法的调用,使用%w包装错误和必要的额外信息,直接返回到上层;对于最外层方法(一般是http handler或者rpc handler),将错误包装上下文,打印到错误日志中,再使用errors.Is或者errors.As方法,根据错误类型进行不同的错误处理逻辑。这样的好处是,对于全局而言,有且只有最外层一份错误日志,而这个错误信息时包装了层层调用信息的,内容最为齐全。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容