当前位置:网站首页>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
边栏推荐
- Comment utiliser le langage MySQL pour les appareils de ligne et de ligne?
- Use Tencent cloud to build a map bed service
- [200 opencv routines] 216 Draw polylines and polygons
- Zero copy technology of MySQL
- [PHP graduation design] design and implementation of textbook management system based on php+mysql+apache (graduation thesis + program source code) -- textbook management system
- Some abilities can't be learned from work. Look at this article, more than 90% of peers
- Automatique, intelligent, visuel! Forte conviction des huit conceptions derrière la solution sslo
- DO280管理应用部署--pod调度控制
- 使用腾讯云搭建图床服务
- Vscode find and replace the data of all files in a folder
猜你喜欢
She is the "HR of others" | ones character
Uncover the "intelligence tax" of mousse: spend 4billion on marketing, and only 7 invention patents
Comment utiliser le langage MySQL pour les appareils de ligne et de ligne?
Win11如何設置用戶權限?Win11設置用戶權限的方法
Vscode find and replace the data of all files in a folder
普通二本,去过阿里外包,到现在年薪40W+的高级测试工程师,我的两年转行心酸经历...
Sales management system of lightweight enterprises based on PHP
What time do you get off work?!!!
How long will it take to achieve digital immortality? Metacosmic holographic human avatar 8i
Zhou Shaojian, rare
随机推荐
【LeetCode】43. String multiplication
苹果自研基带芯片再次失败,说明了华为海思的技术领先性
Use Tencent cloud to build a map bed service
Korean AI team plagiarizes shock academia! One tutor with 51 students, or plagiarism recidivist
【Hot100】17. Letter combination of telephone number
Huawei issued hcsp-solution-5g security talent certification to help build 5g security talent ecosystem
What is the forkjoin framework in the concurrent programming series?
2023届春招实习-个人面试过程和面经分享
程序员职业生涯真的很短吗?
How to use MySQL language for row and column devices?
idea启动Command line is too long问题处理
ABAP call restful API
Comment win11 définit - il les permissions de l'utilisateur? Win11 comment définir les permissions de l'utilisateur
【开源数据】基于虚拟现实场景的跨模态(磁共振、脑磁图、眼动)人类空间记忆研究开源数据集
周少剑,很少见
ssm框架原理
分享在大疆DJI(深圳总部)工作的日常和福利
使用腾讯云搭建图床服务
What time do you get off work?!!!
全面看待企业数字化转型的价值