Golang中如何简化一系列连续的错误检查
Golang中如何简化一系列连续的错误检查 你好,
我是Go语言的新手,目前正在尝试用Go编写一个小型Web应用的后端API。我有一些代码如下所示。有没有办法简化它,例如,像Java那样使用try-catch来覆盖所有的错误检查?
以下是我的代码块:
func GetContextUser(c *gin.Context) (*dto.UserInfoDto, error) {
token, err := uses.GetRequestToken(c.Request)
if err != nil {
return nil, err
}
user, err := dao.GetUserByToken(token)
if err != nil {
return nil, err
}
addresss, err := dao.GetUserAddress(user.Id)
if err != nil {
return nil, err
}
estates, err := dao.GetAllEstates()
if err != nil {
return nil, err
}
var res = dto.UserInfoDto{
Info: *user,
Addresses: addresss,
Config: dto.Config{
Estates: estates,
},
}
return &res, nil
}
更多关于Golang中如何简化一系列连续的错误检查的实战教程也可以访问 https://www.itying.com/category-94-b0.html
Go(谢天谢地)没有异常这个概念。 您展示的代码在 Go 中是地道的写法。
目前有一些讨论,希望创建一种简化的语法来减少代码的冗余,但目前这就是标准做法。
提示:您可以通过“包装”现有错误来丰富返回的错误信息(例如使用 fmt.Errorf())。
func main() {
fmt.Println("hello world")
}
更多关于Golang中如何简化一系列连续的错误检查的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
err
实际上,这是两个问题。如果Go返回一个错误,这意味着你调用的方法中的异常已经被提前捕获,这里并没有发生异常,就像在Java中,当try catch捕获到异常并返回null一样。如果你想以类似try catch的方式包含所有方法,那么你调用的方法就不应该捕获异常,或者不应该返回错误,而是应该直接panic()抛出异常,这取决于Func的设计。
作为开发者,你有责任思考代码中可能出错的一切情况,并决定采取正确的应对措施。
Go语言通过要求你单独处理每个错误,使这一点变得非常容易。虽然对于一个小型示例项目来说,这可能看起来有些繁琐,但对于其他任何情况来说,这绝对是必要的。
当你只是将错误向上抛出调用链时,用户会收到一个通用的错误信息,例如“错误:值不能为空”——没有任何指示说明该怎么做,也没有任何关于错误具体发生位置的上下文信息。
典型错误原因
根据我的经验,大多数错误都属于以下三类之一,具体取决于需要采取什么措施来修复它:
-
用户/输入错误:用户尝试了某些操作,除非他做出某些更改,否则这些操作无法成功。他们需要采取特定的行动(例如,登录、更改查询、选择其他产品,因为此产品缺货……)——在这种情况下,你需要向用户提供明确的信息,说明他们需要做什么才能继续(最好附带链接)。
-
临时条件错误:用户只需要重试,很可能就会成功(例如,网络请求失败、由于服务器繁忙导致操作超时、乐观锁异常……)——如果有可能成功,你的代码理想情况下应该立即重试。或者向用户提供明确的信息——例如“请几分钟后再试”。
-
永久性/代码错误:某些东西损坏了,只有技术人员修复后才能再次正常工作(数据库损坏或代码中存在错误)——在这种情况下,用户需要联系支持人员(或者监控服务会自动提醒支持人员)——这些错误需要记录详细信息,以便轻松地重现、分析和修复错误。
实用错误处理示例
token, err := uses.GetRequestToken(c.Request)
if err != nil || token == nil {
// 没有令牌?用户未登录
return nil, ErrNoLoginToken // 重定向到登录页面
}
user, err := dao.GetUserByToken(token)
if err != nil {
// 找不到该令牌对应的用户:会话已过期或存在黑客攻击尝试
return nil, ErrSessionInvalid // 重定向到带有错误信息的登录页面
}
addresss, err := dao.GetUserAddress(user.Id)
if errors.Is(err, sql.ErrNotFound) {
// 显示链接到用户可以向其账户添加地址的页面
return nil, ErrNoAddress
} else if err != nil {
// 通用数据库错误,为错误排查提供上下文
return nil, fmt.Errorf("could not load address for user %+v: %w", user, err)
}
estates, err := dao.GetAllEstates()
if err != nil {
// 通用数据库错误,为错误排查提供上下文
return nil, fmt.Errorf("could not load estates for user: %+v %w", user, err)
}
在Go语言中,可以通过几种方式简化连续的错误检查。以下是几种实用的方法:
1. 使用错误包装器(推荐)
创建一个辅助函数来封装错误检查逻辑:
func checkErr[T any](value T, err error) (T, error) {
if err != nil {
return value, err
}
return value, nil
}
func GetContextUser(c *gin.Context) (*dto.UserInfoDto, error) {
token, err := checkErr(uses.GetRequestToken(c.Request))
if err != nil {
return nil, err
}
user, err := checkErr(dao.GetUserByToken(token))
if err != nil {
return nil, err
}
addresses, err := checkErr(dao.GetUserAddress(user.Id))
if err != nil {
return nil, err
}
estates, err := checkErr(dao.GetAllEstates())
if err != nil {
return nil, err
}
return &dto.UserInfoDto{
Info: *user,
Addresses: addresses,
Config: dto.Config{
Estates: estates,
},
}, nil
}
2. 使用闭包和延迟错误处理
func GetContextUser(c *gin.Context) (res *dto.UserInfoDto, err error) {
var token string
var user *model.User
var addresses []model.Address
var estates []model.Estate
defer func() {
if err != nil {
// 可以在这里添加日志记录或其他清理操作
}
}()
token, err = uses.GetRequestToken(c.Request)
if err != nil {
return
}
user, err = dao.GetUserByToken(token)
if err != nil {
return
}
addresses, err = dao.GetUserAddress(user.Id)
if err != nil {
return
}
estates, err = dao.GetAllEstates()
if err != nil {
return
}
res = &dto.UserInfoDto{
Info: *user,
Addresses: addresses,
Config: dto.Config{
Estates: estates,
},
}
return
}
3. 使用自定义错误类型和链式调用
type Result struct {
err error
}
func (r *Result) Check(fn func() error) *Result {
if r.err == nil {
r.err = fn()
}
return r
}
func (r *Result) Error() error {
return r.err
}
func GetContextUser(c *gin.Context) (*dto.UserInfoDto, error) {
var token string
var user *model.User
var addresses []model.Address
var estates []model.Estate
result := &Result{}
result.Check(func() error {
var err error
token, err = uses.GetRequestToken(c.Request)
return err
}).Check(func() error {
var err error
user, err = dao.GetUserByToken(token)
return err
}).Check(func() error {
var err error
addresses, err = dao.GetUserAddress(user.Id)
return err
}).Check(func() error {
var err error
estates, err = dao.GetAllEstates()
return err
})
if err := result.Error(); err != nil {
return nil, err
}
return &dto.UserInfoDto{
Info: *user,
Addresses: addresses,
Config: dto.Config{
Estates: estates,
},
}, nil
}
4. 使用第三方库
如果项目允许使用第三方库,可以考虑使用类似 github.com/pkg/errors 或 golang.org/x/sync/errgroup:
import "golang.org/x/sync/errgroup"
func GetContextUser(c *gin.Context) (*dto.UserInfoDto, error) {
var (
token string
user *model.User
addresses []model.Address
estates []model.Estate
)
g := new(errgroup.Group)
g.Go(func() error {
var err error
token, err = uses.GetRequestToken(c.Request)
return err
})
g.Go(func() error {
var err error
user, err = dao.GetUserByToken(token)
return err
})
g.Go(func() error {
var err error
addresses, err = dao.GetUserAddress(user.Id)
return err
})
g.Go(func() error {
var err error
estates, err = dao.GetAllEstates()
return err
})
if err := g.Wait(); err != nil {
return nil, err
}
return &dto.UserInfoDto{
Info: *user,
Addresses: addresses,
Config: dto.Config{
Estates: estates,
},
}, nil
}
第一种方法(错误包装器)是最简洁且符合Go语言习惯的方式,它保持了代码的可读性同时减少了重复的错误检查代码。

