当前位置:网站首页>Go 语言错误处理为什么更推荐使用 pkg/errors 三方库?
Go 语言错误处理为什么更推荐使用 pkg/errors 三方库?
2022-07-01 16:11:00 【frank.】
大家好,我是 frank。
01
介绍
Go 语言项目开发中,我们通常需要在代码逻辑中进行错误处理,Go 官方标准库 errors
为我们提供了一些方法,比如 New
,Unwarp
,Is
和 As
。
其中,我们用的最多的是 New
,但是,在我们实际 Go 项目开发中,会使用一些分层设计,比如 MVC
,Clean Architecture
等。
在使用分层设计的项目中,如果我们使用 Go 标准库 errors
定义错误,就会遇到错误覆盖的问题。
02
关于标准库 errors 的错误覆盖问题
Go标准库 errors
的 New
方法,只能定义一条简单的错误信息,在分层设计的项目代码中,就会遇到错误覆盖的问题,比如我们本文的示例项目代码,使用的是 Clean Architecture
分层设计。
项目分层目录:
.
├── app
│ └── main.go
├── domain
│ └── user.go
├── go.mod
├── go.sum
└── user
├── delivery
│ └── http
│ └── user.go
├── repository
│ └── mysql
│ └── user.go
└── usecase
└── user.go
在示例项目中,我们先使用 Go 标准库 errors
的 New
方法定义错误,代码片段如下:
repository 层:
func (m *mysqlUserRepository) GetUserById(ctx context.Context, user *domain.User) (err error) {
_, err = m.DB.Get(user)
fmt.Printf("mysqlUserRepository || GetUserById() || uid=%v || err=%v\n", user.Id, err)
return
}
usecase 层:
func (u *userUsecase) GetUserById(ctx context.Context, user *domain.User) (err error) {
if user.Id == 0 {
err = errors.New("invalid request parameter")
}
err = u.userRepo.GetUserById(ctx, user)
fmt.Printf("userUsecase || GetUserById() || uid=%v || err=%v\n", user.Id, err)
return
}
delivery 层:
func (u *UserHandler) GetUserById(c echo.Context) error {
idP, err := strconv.Atoi(c.Param("id"))
if err != nil {
return c.JSON(http.StatusNotFound, err)
}
id := int64(idP)
ctx := c.Request().Context()
user := &domain.User{
Id: id,
}
err = u.UserUsecase.GetUserById(ctx, user)
if err != nil {
err = errors.New("UserUsecase error")
fmt.Printf("UserHandler || GetUserById() || uid=%v || err=%+v\n", id, err)
return c.JSON(http.StatusInternalServerError, err)
}
return c.JSON(http.StatusOK, user)
}
阅读上面三段代码,我们可以发现,我们在每层中都有错误处理的代码,我们故意使用错误的请求参数,并将数据库连接的密码写错,触发应用程序的错误。
输出结果:
mysqlUserRepository || GetUserById() || uid=1 || err=Error 1045: Access denied for user 'root'@'172.17.0.1' (using password: YES)
userUsecase || GetUserById() || uid=1 || err=Error 1045: Access denied for user 'root'@'172.17.0.1' (using password: YES)
UserHandler || GetUserById() || uid=1 || err=UserUsecase error
阅读输出结果,我们可以发现,usecase 层定义的错误,被调用的 repository 层返回错误覆盖;delivery 层定义的错误将 usecase 层返回的错误覆盖。
因为我们在每层都打印了错误,仔细排查,还是可以定位到错误,但是还是比较繁琐,不仅每层打印错误使代码不够优雅,而且也不能快速定位到错误。
怎么解决这个问题呢?使用三方库 github.com/pkg/errors
替换 Go 标准库 errors
。
03
三方库 pkg/errors
使用三方库 pkg/errors
可以解决在分层设计的项目中调用堆栈的错误信息互相覆盖,可以为我们输出错误的堆栈信息,可以在已有错误信息的基础上附加新的错误信息,从而解决输出的错误信息缺失上下文的问题。
我们修改一下 Part 02 的示例代码,将 Go 标准库 errors
替换为三方库 pkg/errors
,相信细心的读者朋友们已经发现,因为这两个包的名字相同,而且都有 New
方法,所以替换起来也比较方便,只需替换导入的包。
示例代码:
import (
// "errors"
"fmt"
"github.com/labstack/echo/v4"
"github.com/pkg/errors"
"github.com/weirubo/learn_go/lesson41/domain"
"net/http"
"strconv"
)
替换后的输出结果:
mysqlUserRepository || GetUserById() || uid=0 || err=Error 1045: Access denied for user 'root'@'172.17.0.1' (using password: YES)
userUsecase || GetUserById() || uid=0 || err=Error 1045: Access denied for user 'root'@'172.17.0.1' (using password: YES)
UserHandler || GetUserById() || uid=0 || err=UserUsecase error
github.com/weirubo/learn_go/lesson41/user/delivery/http.(*UserHandler).GetUserById
/Users/frank/GolandProjects/learn_go/lesson41/user/delivery/http/user.go:36
github.com/labstack/echo/v4.(*Echo).add.func1
/Users/frank/go/pkg/mod/github.com/labstack/echo/[email protected]/echo.go:520
github.com/labstack/echo/v4.(*Echo).ServeHTTP
/Users/frank/go/pkg/mod/github.com/labstack/echo/[email protected]/echo.go:630
net/http.serverHandler.ServeHTTP
/usr/local/go/src/net/http/server.go:2916
net/http.(*conn).serve
/usr/local/go/src/net/http/server.go:1966
runtime.goexit
/usr/local/go/src/runtime/asm_amd64.s:1571
阅读上面的输出结果,我们可以发现错误处理包由 Go 标准库 errors
替换为三方库 pkg/errors
后,输出结果不仅有 Go 标准库 errors
的错误信息,还输出了错误的堆栈信息。
目前为止,我们只是切换了一下导入的包,错误信息就包含了错误的堆栈信息,但是,我们的错误覆盖问题还没有得到解决,我们还需要使用三方库 pkg/errors
的 Wrap
方法,我们再修改一下代码,将 New
方法替换为 Wrap
方法。
delivery 层:
...
if err != nil {
// err = errors.New("UserUsecase error")
err = errors.Wrap(err, "UserUsecase error")
fmt.Printf("UserHandler || GetUserById() || uid=%v || err=%+v\n", id, err)
return c.JSON(http.StatusInternalServerError, err)
}
...
阅读上面这段代码,我们修改 delivery 层的错误处理代码,将 New
方法替换为 Wrap
方法,它可以在已有错误信息的基础上,附加新的错误信息和错误的堆栈信息。
输出结果:
mysqlUserRepository || GetUserById() || uid=0 || err=Error 1045: Access denied for user 'root'@'172.17.0.1' (using password: YES)
userUsecase || GetUserById() || uid=0 || err=Error 1045: Access denied for user 'root'@'172.17.0.1' (using password: YES)
UserHandler || GetUserById() || uid=0 || err=Error 1045: Access denied for user 'root'@'172.17.0.1' (using password: YES)
UserUsecase error
github.com/weirubo/learn_go/lesson41/user/delivery/http.(*UserHandler).GetUserById
/Users/frank/GolandProjects/learn_go/lesson41/user/delivery/http/user.go:37
github.com/labstack/echo/v4.(*Echo).add.func1
/Users/frank/go/pkg/mod/github.com/labstack/echo/[email protected]/echo.go:520
github.com/labstack/echo/v4.(*Echo).ServeHTTP
/Users/frank/go/pkg/mod/github.com/labstack/echo/[email protected]/echo.go:630
net/http.serverHandler.ServeHTTP
/usr/local/go/src/net/http/server.go:2916
net/http.(*conn).serve
/usr/local/go/src/net/http/server.go:1966
runtime.goexit
/usr/local/go/src/runtime/asm_amd64.s:1571
阅读以上输出结果,我们可以发现在 delivery 层定义的错误信息,没有再覆盖调用 usecase
层方法返回的错误信息,二者都被正常输出。
需要注意的是,同时输出错误信息和堆栈信息,占位符需要使用 %+v
,也不要在每层都输出堆栈信息,这样会重复打印堆栈信息,通常做法是如果下层打印了堆栈信息,上层就不要再打印堆栈信息。
此外,三方库 pkg/errors
的另外两个方法 WithMessage
和 WithStack
也比较常用,它们分别是在已有的错误信息的基础上,附加新的错误信息和错误的堆栈信息,我们在实际项目开发中,可以按需选择使用合适的方法。
04
总结
本文我们讲述了使用 Go 标准库 errors
进行错误处理的局限性和不足,为了解决它的不足,我们介绍了使用三方库 pkg/errors
替换 Go 标准库 errors
,和三方库 pkg/errors
的几个常用方法的使用方式。
关于三方库 pkg/errors
的更多方法,感兴趣的读者朋友们可以阅读文档了解如何使用。
参考资料:
- https://pkg.go.dev/github.com/pkg/[email protected]
- https://pkg.go.dev/errors
- https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package
- https://morioh.com/p/777d15fe7828
边栏推荐
- When ABAP screen switching, refresh the previous screen
- Which MySQL functions are currently supported by tablestore in table storage?
- Win11如何设置用户权限?Win11设置用户权限的方法
- There will be a gap bug when the search box and button are zoomed
- 智慧党建: 穿越时空的信仰 | 7·1 献礼
- Nuxt. JS data prefetching
- Idea start command line is too long problem handling
- Comment utiliser le langage MySQL pour les appareils de ligne et de ligne?
- Advanced cross platform application development (24): uni app realizes file download and saving
- How to use MySQL language for row and column devices?
猜你喜欢
Overview | slam of laser and vision fusion
#夏日挑战赛# HarmonyOS canvas实现时钟
Automatique, intelligent, visuel! Forte conviction des huit conceptions derrière la solution sslo
韩国AI团队抄袭震动学界!1个导师带51个学生,还是抄袭惯犯
【SQL语句】请问这边为什么select出了两个上海,查询出了不同的count我想让他变成一个上海,count只显示一个总和
One revolution, two forces, three links: the "carbon reduction" roadmap behind the industrial energy efficiency improvement action plan
PostgreSQL 存储结构浅析
【Hot100】17. Letter combination of telephone number
Crypto Daily: Sun Yuchen proposed to solve global problems with digital technology on MC12
瑞典公布决定排除华为5G设备,但是华为已成功找到新出路
随机推荐
Pocket Network为Moonbeam和Moonriver RPC层提供支持
Talking from mlperf: how to lead the next wave of AI accelerator
表格存储中tablestore 目前支持mysql哪些函数呢?
投稿开奖丨轻量应用服务器征文活动(5月)奖励公布
超视频时代,什么样的技术会成为底座?
【SQL语句】请问这边为什么select出了两个上海,查询出了不同的count我想让他变成一个上海,count只显示一个总和
Thinkphp内核工单系统源码商业开源版 多用户+多客服+短信+邮件通知
【Hot100】17. 电话号码的字母组合
Embedded development: five revision control best practices
process.env.NODE_ENV
idea启动Command line is too long问题处理
Sales management system of lightweight enterprises based on PHP
瑞典公布决定排除华为5G设备,但是华为已成功找到新出路
Does 1.5.1 in Seata support mysql8?
Solution to the problem that the keypad light does not light up when starting up
Seata中1.5.1 是否支持mysql8?
Trace the source of drugs and tamp the safety dike
SQLServer查询: a.id与b.id相同时,a.id对应的a.p在b.id对应的b.p里找不到的话,就显示出这个a.id和a.p
Automatique, intelligent, visuel! Forte conviction des huit conceptions derrière la solution sslo
Microservice tracking SQL (support Gorm query tracking under isto control)