本文首发稀土,如需转载请联络。
作者:陈明勇
个人网站:chenmingyong.cn
文章继续更新,如果本文能让您有所收成,欢迎重视本号。
微信阅读可搜《Go 技术干货》。这篇文章已被收录于 GitHub github.com/chenmingyon… ,欢迎大家 Star 催更并继续重视。
前语
在 Go 语言中,对于程序中或许呈现的问题,比方数据库连接失利,文件读取过错等,都是运用根据内置的 error 接口类型的值来表明和处理过错。而在分层的项目中,如何最佳处理 error成为众多人重视的问题,本文将探讨 Go 项目分层下的最佳 error 处理方法。准备好了吗?准备一杯你最喜欢的饮料或茶,随着本文一探究竟吧。
常见分层下的 error 处理
以典型的 MVC ( dao → service → controller/middleware) 分层结构举例,常见的过错处理大致如下:
// controller / middleware
res, err := service.GetById(ctx, id)
if err != nil {
log.Errorf(ctx, "service.GetById failed, id=%s, error=%v", err)
}
// service
article, err := dao.GetById(ctx, id)
if err != nil {
log.Errorf(ctx, fmt.Errorf("dao.GetById failed, error=%v", err))
return fmt.Errorf("dao.GetById failed, error=%v", err)
}
// dao
if err != nil {
log.Errorf(ctx, fmt.Errorf("GetById failed, id=%s, error=%v", id, err))
return fmt.Errorf("GetById failed, id=%s, error=%v", id, err)
}
以上过错处理的方法,在每一层都打印一条过错日志,然后对得到的 error 进行二次封装。虽然以上处理方法可以使咱们在查看日志时方便故障排查和问题定位,一起供给了过错的上下文信息,但也存在以下问题:
- 每层都打印日志,带来了大量日志;
- 字符串拼接费时费力,缺少统一标准,或许导致了解困难;
- 经过字符串拼接,取得新
error,损坏了原始error,会导致等值断定失利,难以获取详细的仓库相关。
分层下的最佳 error 处理方法
遵从以下主张,咱们可以更好地处理 error :
- 1、一个
error,应该只被处理一次 - 2、让
error包括更多的信息 - 3、原始
error,应保证完好性,不被损坏 - 4、
error需求被日志记载
什么意思呢?为了确保 error 处理的有效性,对于某一层来说,应该保证每个过错只被处理一次,要么打印 error 信息,要么将其传递给上一层,而不是每一层都独立打印 error 信息。
一起,在传递过错给上一层时,应该顺便有用的额定信息,并确保不损坏原始过错的完好性,以保证过错的可追溯性。终究,经过记载过错日志可以帮助咱们进行问题排查。
在 Dao 层遇到原始过错 Original Error 后,咱们可以将其与需求的额定信息封装,组成一个新的 error ,然后传递给上一层,逐层附加信息,直至传递到 controller 层,终究得到一个全新的 error,其间包括 Original error 和每一层增加的额定信息。
经过终究得到的这个 error,咱们可以像剥洋葱相同逐层解开,追溯到 Original error ,并获取咱们所需的信息。如果 controller 是最顶层,咱们可以打印完好的过错信息,然后获取 Original Error,并打印其所包括的 仓库信息。
Wrap error
虽然前面现已探讨了分层下的最佳 error 处理方法,但咱们会发现官方标准库errors 所供给的函数并不能满足咱们的需求,咱们不能凭借现有函数对原始过错附加额定信息且不损坏其完好性。在这种情况下,咱们可以凭借第三方库 github.com/pkg/errors 来完成咱们的需求。
github.com/pkg/errors 供给了很多有用的函数,例如:
-
Wrap(err error, message string) error:该函数根据原始过错err,回来一个带有仓库跟踪信息和附加信息message的新error -
Wrapf(err error, format string, args ...interface{}) error: 和上面的函数功能是相同的,只不过可以对附加信息进行格式化封装 -
WithMessage(err error, message string) error:该函数根据原始过错err,回来一个附加信息message的新error -
WithMessagef(err error, format string, args ...interface{}) error: 和上面的函数功能是相同的,只不过可以对附加信息进行格式化封装 -
Cause(err error) error:该函数用于提取err中的原始error,它会递归地查看error,直到找到最底层的原始error,如果存在的话
了解了以上函数的功能,咱们来看看项目分层下最佳 error 的详细完成。
// controller / middleware
res, err := service.GetById(ctx, id)
if err != nil {
log.Errorf(ctx, "service.GetById failed, original error: %T %v", errors.Cause(err), errors.Cause(err))
log.Errorf(ctx, "stack trace: \n%+v\n", err)
}
// service
article, err := dao.GetById(ctx, id)
if err != nil {
return errors.WithMessage(err, "dao.GetById failed")
}
// dao
if err != nil {
return errors.Wrapf(err, "GetById failed, id=%s, error=%v", id, err)
}
当在 Dao 层遇到原始过错 Original Error 后,运用 errors.Wrap() 对过错进行封装。这个封装操作可以在保留根因(Origin error)的一起,供给仓库信息,并增加额定的上下文信息,然后将封装后的过错传递给上一层处理。
当 service 层接收到 error 之后,运用 errors.WithMessage() 函数,将额定的信息附加到过错上,并继续将过错向上层传递,直至抵达 controller 层。在 controller 层,咱们可以打印出根因的类型、信息以及仓库信息,以便更好地进行问题排查。
小结
本文对 Go 项目分层下的最佳 error 处理方法进行介绍,并经过运用 github.com/pkg/errors 库中的一些有用函数来供给完成示例。
虽然本文根据 MVC 分层结构进行介绍,但实际上大多数项目的分层结构或许各不相同,因此在确定过错处理方法和策略时需求考虑详细情况。然而,我信任经过参阅本文提出的四点主张和完成示例或其他更好的主张,必定可以确定最佳的过错处理方法。


