Go项目分层下的最佳error处理方式分享

 更新时间:2023年06月21日 08:26:34   作者:陈明勇  
这篇文章主要来和大家一起探讨 Go 项目分层下的最佳 error 处理方式,准备好了吗?准备一杯你最喜欢的饮料或茶,随着本文一探究竟吧

前言

Go 语言中,对于程序中可能出现的问题,比如数据库连接失败,文件读取错误等,都是使用基于内置的 error 接口类型的值来表示和处理错误。而在分层的项目中,如何最佳处理 error成为众多人关注的问题,本文将探讨 Go 项目分层下的最佳 error 处理方式。准备好了吗?准备一杯你最喜欢的饮料或茶,随着本文一探究竟吧。

常见分层下的 error 处理

以典型的 MVC ( daoservicecontroller/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 分层结构进行介绍,但实际上大多数项目的分层结构可能各不相同,因此在确定错误处理方式和策略时需要考虑具体情况。然而,我相信通过参考本文提出的四点建议和实现示例或其他更好的建议,一定能够确定最佳的错误处理方式。

到此这篇关于Go项目分层下的最佳error处理方式分享的文章就介绍到这了,更多相关Go error处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • go select编译期的优化处理逻辑使用场景分析

    go select编译期的优化处理逻辑使用场景分析

    select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。接下来通过本文给大家介绍go select编译期的优化处理逻辑使用场景分析,感兴趣的朋友一起看看吧
    2021-06-06
  • go语言基于Session和Redis实现短信验证码登录

    go语言基于Session和Redis实现短信验证码登录

    本文介绍了go语言基于Session和Redis实现短信验证码登录,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-11-11
  • Golang使用Gin创建Restful API的实现

    Golang使用Gin创建Restful API的实现

    本文主要介绍了Golang使用Gin创建Restful API的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01
  • Golang中函数(Function)和方法(Method)的区别详解

    Golang中函数(Function)和方法(Method)的区别详解

    在Golang中,大家必然会频繁使用到函数(Function)和方法(Method),但是有的同学可能并没有注意过函数和方法的异同点,函数和方法都是用来执行特定任务的代码块,虽然很相似,但也有很大的区别,所以本文将详细讲解函数和方法的定义以及它们的异同点
    2023-07-07
  • go gin中间件关于 c.next()、c.abort()和return的使用小结

    go gin中间件关于 c.next()、c.abort()和return的使用小结

    中间件的执行顺序是按照注册顺序执行的,中间件可以通过 c.abort() + retrurn 来中止当前中间件,后续中间件和处理器的处理流程, 这篇文章给大家介绍go gin中间件关于 c.next()、c.abort()和return的使用小结,感兴趣的朋友跟随小编一起看看吧
    2024-03-03
  • Go正则表达式匹配字符串,替换字符串方式

    Go正则表达式匹配字符串,替换字符串方式

    介绍了Go语言中使用正则表达式进行字符串匹配和替换的方法,包括匹配单个子字符串和所有子字符串,个人经验分享,旨在为读者提供实用的编程技巧,并鼓励大家支持脚本之家
    2025-02-02
  • Go语言中的格式化输出占位符的用法详解

    Go语言中的格式化输出占位符的用法详解

    在 Go 语言中,格式化输出是一个非常常用的功能,特别是在处理字符串、数字和其他数据类型时,本文将详细介绍 Go 语言中常用的格式化占位符及其用法,希望对大家有所帮助
    2024-10-10
  • 利用Go实现一个简易DAG服务的示例代码

    利用Go实现一个简易DAG服务的示例代码

    DAG的全称是Directed Acyclic Graph,即有向无环图,DAG广泛应用于表示具有方向性依赖关系的数据,如任务调度、数据处理流程、项目管理以及许多其他领域,下面,我将用Go语言示范如何实现一个简单的DAG服务,需要的朋友可以参考下
    2024-03-03
  • 深入探究Go语言的错误策略与异常机制

    深入探究Go语言的错误策略与异常机制

    本文深入探讨了Go语言的错误策略与异常机制,主要介绍了错误处理的重要性,以及Go语言中的错误类型和处理函数,此外还讨论了Go语言的异常机制,包括panic和recover函数的使用,需要的朋友可以参考下
    2024-02-02
  • Golang并发绕不开的重要组件之Goroutine详解

    Golang并发绕不开的重要组件之Goroutine详解

    Goroutine、Channel、Context、Sync都是Golang并发编程中的几个重要组件,这篇文中主要为大家介绍了Goroutine的相关知识,需要的可以参考一下
    2023-06-06

最新评论