前言
这是Go十大常见错误系列的第5篇:go语言Error管理。素材来源于Go布道者,现Docker公司资深工程师Teiva Harsanyi[1]。
本文涉及的源代码全部开源在:Go十大常见错误源代码[2],欢迎大家关注公众号,及时获取本系列最新更新。
场景
Go语言在错误处理(error handling)机制上经常被诟病。
errors.New
pkg/errors包
由于Go标准库里errors包的功能比较少,所以很多人可能用过开源的*pkg/errors*[3]包来处理Go语言里的error。
比较早使用Go语言做开发,并且使用*pkg/errors*[4]包的开发者也会犯一些错误,下文会详细讲到。
pkg/errors
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")
}
postHandlerinsertdbQuery
dbQueryerrors.NewinsertdbQuerypostHandlerinsert
函数调用链的每一层,要么返回error,要么打印error,遵循了上面提到的error处理法则。
error判断
在业务逻辑里,我们经常会需要判断error类型,根据error的类型,决定下一步的操作:
-
比如可能做重试操作,直到成功。 -
比如可能直接打印错误日志,然后退出函数。
db
dbdb.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
}
pkg/errorserrors.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)
}
可能的错误在哪里呢?
err.(type)errors.Cause(err).(type)
*db.DBErrorerr.(type)*db.DBError
推荐阅读
开源地址
文章和示例代码开源在GitHub: Go语言初级、中级和高级教程[5]。
公众号:coding进阶。关注公众号可以获取最新Go面试题和技术栈。
个人网站:Jincheng's Blog[6]。
知乎:无忌[7]。
福利
我为大家整理了一份后端开发学习资料礼包,包含编程语言入门到进阶知识(Go、C++、Python)、后端开发技术栈、面试题等。
关注公众号「coding进阶」,发送消息 backend 领取资料礼包,这份资料会不定期更新,加入我觉得有价值的资料。还可以发送消息「进群」,和同行一起交流学习,答疑解惑。
References
-
https://itnext.io/the-top-10-most-common-mistakes-ive-seen-in-go-projects-4b79d4f6cd65
参考资料
Teiva Harsanyi: https://teivah.medium.com/
[2]Go十大常见错误源代码: https://github.com/jincheng9/go-tutorial/tree/main/workspace/senior/p28
[3]pkg/errors: https://github.com/pkg/errors
[4]pkg/errors: https://github.com/pkg/errors
[5]Go语言初级、中级和高级教程: https://github.com/jincheng9/go-tutorial
[6]Jincheng's Blog: https://jincheng9.github.io/
[7]无忌: https://www.zhihu.com/people/thucuhkwuji