Go 1.13 特性: 新的 Error 方法和 Error 返回值设计建议

Tags: golang 

本篇目录

说明

错误处理大概是最容易被忽略的最重要的事情。Go 1.3 的 errors 新增了三个方法,As、Is 和 Unwrap:

func As(err error, target interface{}) bool
func Is(err, target error) bool
func Unwrap(err error) error
func New(text string) error

Working with Errors in Go 1.13 详细介绍了新增这三个方法的缘由,以及函数 Error 返回设计建议。 这三个新方法不是新的语法能力,只是吸收了一些比较常用的 error 处理方法。

语法糖 Is() 和 As()

Is() 和 As() 比较简单,就是两个语法糖,一个是 value 比较,一个是类型断言。

errors.Is():

if errors.Is(err, ErrNotFound) {
    // something wasn't found
}

//等同于

if err == ErrNotFound {
    // something wasn't found
}

errors.As():

var e *QueryError
if errors.As(err, &e) {
    // err is a *QueryError, and e is set to the error's value
}

等同于

if e, ok := err.(*QueryError); ok {
    // err is a *QueryError, and e is set to the error's value
}

Error 传递中存在的问题与常用做法

在 Go 语言中,Error 是变量,传递错误信息的能力比较弱。函数 A 调用函数 B,函数 B 调用函数 C,如果要在函数 A 中获得函数 C 中的错误,通常在函数 B 中用下面的方法向 A 传递:

if err != nil {
    return fmt.Errorf("decompress %v: %v", name, err)
}

但是这种方法只保留了 err 中的文本信息,err 的类型信息全部丢失了。如果要保留 err 的类型,函数 B 可以返回下面类型 struct 变量:

type QueryError struct {
    Query string
    Err   error
}

将函数 C 返回的 err 保留在成员 Err 中。

Unwrap 接口约定了 error 的嵌套方法

Go 1.13 提供的 Unwrap 方法简化了解出内层 Err 的操作。

如下所示,如果 Error Struct 实现了 Unwrap() error 接口:

func (e *QueryError) Unwrap() error { return e.Err }

可以用 errors package 中的 Unwrap() 方法直接解出内层的 Err:

// Create: 2019/12/03 14:31:00 Change: 2019/12/03 14:42:06
// FileName: error-unwrap.go
// Copyright (C) 2019 lijiaocn <[email protected] wechat:lijiaocn> wechat:lijiaocn
//
// Distributed under terms of the GPL license.

package main

import (
    "errors"
)

type QueryError struct {
    Query string
    Err   error
}

func (e *QueryError) Error() string {
    return e.Query + ": " + e.Err.Error()
}

func (e *QueryError) Unwrap() error {
    return e.Err
}

func FunC() error {
    return errors.New("err in Function C")
}

func FunB() error {
    err := FunC()
    if err != nil {
        return &QueryError{Query: "query error", Err: err}
    }
    return nil
}

func main() {
    err := FunB()
    if err != nil {
        println(err.Error())
        // 解出内层的 Err
        if inter := errors.Unwrap(err); inter != nil {
            println(inter.Error())
        }
    }
}

errors.Unwrap(err) 会调用传入参数的 Unwrap() erorr 方法,并将该方法的返回值返回,上面代码的执行结果是:

query error: err in Function C
err in Function C

注意 Unwrap() error 接口不是强制的,QueryError 可以不实现该方法,如果传入参数没有实现 Unwrap() error,errors.Unwrap() 返回 nil。

自定义 Error 类型是否要实现 Unwrap() ?

Working with Errors in Go 1.13 用了不少篇幅讨论这个问题。

其实就一个原则,如果要向上层传递更多 error 信息就实现,如果不需要就不实现。

但是…感觉还是不如异常捕获方便….应该在语法层面支持 try…catch….

回顾:Go 的 Error 设计思路

Rob Pike 在 2015 年写了一篇文章回应人们对 Go 的 Error 设计的质疑 :Errors are values

Go 的 Error 遭遇的最严重的质疑是,到处都是判断 err 是否 nil 的语句,如下:

_, err = fd.Write(p0[a:b])
if err != nil {
    return err
}
_, err = fd.Write(p1[c:d])
if err != nil {
    return err
}
_, err = fd.Write(p2[e:f])
if err != nil {
    return err
}
// and so on

这是没有 try…catch 导致的。Rob Pike 解释说,他们希望 err 就是一个普通的变量,不想设计成异常或者复杂的接口,一个变量只要有 Error() string 方法就可以作为 error 类型使用。

对于 err != nil 代码太多的质疑, Rob Pike 解释可以用其它方法避免,总之就是坚持最初的设计,不想引入异常。

参考

  1. 李佶澳的博客
  2. Working with Errors in Go 1.13
  3. Package errors
  4. Errors are values
  5. Unwrap

推荐阅读

Copyright @2011-2019 All rights reserved. 转载请添加原文连接,合作请加微信lijiaocn或者发送邮件: [email protected],备注网站合作

友情链接:  系统软件  程序语言  运营经验  水库文集  网络课程  微信网文  发现知识星球