前言
这是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并返回给下层调用方。insert
对dbQuery
返回的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-...