关于go:Go十大常见错误第5篇go语言Error管理

3次阅读

共计 2919 个字符,预计需要花费 8 分钟才能阅读完成。

前言

这是 Go 十大常见谬误系列的第 5 篇:go 语言 Error 治理。素材来源于 Go 布道者,现 Docker 公司资深工程师 Teiva Harsanyi。

本文波及的源代码全副开源在:Go 十大常见谬误源代码,欢送大家关注公众号,及时获取本系列最新更新。

场景

Go 语言在错误处理 (error handling) 机制上常常被诟病。

在 Go 1.13 版本之前,Go 规范库里只有一个用于构建 error 的 errors.New 函数,没有其它函数。

pkg/errors 包

因为 Go 规范库里 errors 包的性能比拟少,所以很多人可能用过开源的 pkg/errors 包来解决 Go 语言里的 error。

比拟早应用 Go 语言做开发,并且应用 pkg/errors 包的开发者也会犯一些谬误,下文会具体讲到。

pkg/errors包的代码格调很好,遵循了上面的 error 解决法令。

An error should be handled only once. Logging an error is handling an error. So an error should either be logged or propagated.

翻译成中文就是:

error 只应该被解决一次,打印 error 也是对 error 的一种解决。所以对于 error,要么打印进去,要么就把 error 返回传递给上一层。

很多开发者在日常开发中,如果某个函数里遇到了 error,可能会先打印 error,同时把 error 也返回给下层调用方,这就没有遵循下面的最佳实际。

咱们接下来看一个具体的示例,代码逻辑是后盾收到了一个 RESTful 的接口申请,触发了数据库报错。咱们想打印如下的堆栈信息:

unable to serve HTTP POST request for customer 1234
 |_ unable to insert customer contract abcd
     |_ unable to commit transaction

假如咱们应用 pkg/errors 包,咱们能够应用如下代码来实现:

func postHandler(customer Customer) Status {err := insert(customer.Contract)
    if err != nil {log.WithError(err).Errorf("unable to serve HTTP POST request for customer %s", customer.ID)
        return Status{ok: false}
    }
    return Status{ok: true}
}

func insert(contract Contract) error {err := dbQuery(contract)
    if err != nil {return errors.Wrapf(err, "unable to insert customer contract %s", contract.ID)
    }
    return nil
}

func dbQuery(contract Contract) error {
    // Do something then fail
    return errors.New("unable to commit transaction")
}

函数调用链是postHandler -> insert -> dbQuery

  • dbQuery应用 errors.New 函数创立 error 并返回给下层调用方。
  • insertdbQuery 返回的 error 做了一层封装,增加了一些上下文信息,把 error 返回给下层调用方。
  • postHandler打印 insert 返回的 error。

函数调用链的每一层,要么返回 error,要么打印 error,遵循了下面提到的 error 解决法令。

error 判断

在业务逻辑里,咱们常常会须要判断 error 类型,依据 error 的类型,决定下一步的操作:

  • 比方可能做重试操作,直到胜利。
  • 比方可能间接打印谬误日志,而后退出函数。

举个例子,假如咱们应用了一个名为 db 的包,用来做数据库的读写操作。

在数据库负载比拟高的状况下,调用 db 包里的办法可能会返回一个长期的 db.DBError 的谬误,对于这种状况咱们须要做重试。

那就能够应用如下的代码,先判断 error 的类型,而后依据具体的 error 类型做对应的解决。

func postHandler(customer Customer) Status {err := insert(customer.Contract)
    if err != nil {switch errors.Cause(err).(type) {
        default:
            log.WithError(err).Errorf("unable to serve HTTP POST request for customer %s", customer.ID)
            return Status{ok: false}
        case *db.DBError:
            return retry(customer)
        }

    }
    return Status{ok: true}
}

func insert(contract Contract) error {err := db.dbQuery(contract)
    if err != nil {return errors.Wrapf(err, "unable to insert customer contract %s", contract.ID)
    }
    return nil
}

下面判断 error 的类型应用了 pkg/errors 包里的 errors.Cause 函数。

常见谬误

对于下面的 error 判断,一个常见的谬误是如下的代码:

switch err.(type) {
default:
  log.WithError(err).Errorf("unable to serve HTTP POST request for customer %s", customer.ID)
  return Status{ok: false}
case *db.DBError:
  return retry(customer)
}

可能的谬误在哪里呢?

下面代码示例里对 error 类型的判断应用了err.(type),没有应用errors.Cause(err).(type)

如果在业务函数调用链中有一个环节对 *db.DBError 做了封装,那 err.(type) 就无奈匹配到*db.DBError,就永远不会触发重试。

举荐浏览

  • Go 十大常见谬误第 1 篇:未知枚举值
  • Go 十大常见谬误第 2 篇:benchmark 性能测试的坑
  • Go 十大常见谬误第 3 篇:go 指针的性能问题和内存逃逸
  • Go 十大常见谬误第 4 篇:break 操作的注意事项
  • Go 面试题系列,看看你会几题?

开源地址

文章和示例代码开源在 GitHub: Go 语言高级、中级和高级教程。

公众号:coding 进阶。关注公众号能够获取最新 Go 面试题和技术栈。

集体网站:Jincheng’s Blog。

知乎:无忌。

福利

我为大家整顿了一份后端开发学习材料礼包,蕴含编程语言入门到进阶常识(Go、C++、Python)、后端开发技术栈、面试题等。

关注公众号「coding 进阶」,发送音讯 backend 支付材料礼包,这份材料会不定期更新,退出我感觉有价值的材料。还能够发送音讯「进群」,和同行一起交流学习,答疑解惑。

References

  • https://itnext.io/the-top-10-…
正文完
 0