Go应用中优雅处理Error的技巧总结

 更新时间:2021年09月08日 09:49:29   作者:周浩  
在程序员中,尤其是go新手,经常听到的一个讨论话题是:如何处理错误,这篇文章主要给大家介绍了关于Go应用中优雅处理Error的一些相关技巧,需要的朋友可以参考下

前言

Go语言很强大并且现在也十分流行 — 许多项目都是用Go语言来实现的,如Kubernetes。Go语言的一个有趣特性是它的多值返回功能提供了一种与其他编程语言不同的错误处理方法。 Go将error视为具有预定义类型的值,其本身是一个interface类型。然而,编写多层体系结构应用程序并使用api暴露应用的特性需要有包含更多上下文信息的error处理,而不仅仅是一个值。 本文我们将探讨如何封装Go的error类型以在应用程序中带来更大的价值。

用户自定义类型

我们将重写的Go里自带的error类型,首先从一个自定义的错误类型开始,该错误类型将在程序中识别为error类型。因此,我们引入一个封装了Go的 error的新自定义Error类型。

type GoError struct {
   error
}

上下文数据

当我们在Go中说error是一个值时,它是字符串值 - 任何实现了Error() string函数的类型都可以视作error类型。将字符串值视为error会使跨层的error处理复杂化,因此处理error字符串信息并不是正确的方法。所以我们可以把字符串和错误码解耦:

type GoError struct {
   error
   Code    string
}

现在对error的处理将基于错误码Code字段而不是字符串。让我们通过上下文数据进一步对错误字符串进行解耦,在上下文数据中可以使用i18n包进行国际化。

type GoError struct {
   error
   Code    string
   Data    map[string]interface{}
}

Data包含用于构造错误字符串的上下文数据。错误字符串可以通过数据模板化:

//i18N def
"InvalidParamValue": "Invalid parameter value '{{.actual}}', expected '{{.expected}}' for '{{.name}}'"

在i18N定义文件中,错误码Code将会映射到使用Data构建的模板化的错误字符串中。

原因(Causes)

error可能发生在任何一层,有必要为每一层提供处理error的选项,并在不丢失原始error值的情况下进一步使用附加的上下文信息对error进行包装。GoError结构体可以用Causes进一步封装,用来保存整个错误堆栈。

type GoError struct {
   error
   Code    string
   Data    map[string]interface{}
   Causes  []error
}

如果必须保存多个error数据,则causes是一个数组类型,并将其设置为基本error类型,以便在程序中包含该原因的第三方错误。

组件(Component)

标记层组件将有助于识别error发生在哪一层,并且可以避免不必要的error wrap。例如,如果service类型的error组件发生在服务层,则可能不需要wrap error。检查组件信息将有助于防止暴露给用户不应该通知的error,比如数据库error:

type GoError struct {
   error
   Code      string
   Data      map[string]interface{}
   Causes    []error
   Component ErrComponent
}

type ErrComponent string
const (
   ErrService  ErrComponent = "service"
   ErrRepo     ErrComponent = "repository"
   ErrLib      ErrComponent = "library"
)

响应类型(ResponseType)

添加一个错误响应类型这样可以支持error分类,以便于了解什么错误类型。例如,可以根据响应类型(如NotFound)对error进行分类,像DbRecordNotFound、ResourceNotFound、UserNotFound等等的error都可以归类为 NotFound error。这在多层应用程序开发过程中非常有用,而且是可选的封装:

type GoError struct {
   error
   Code         string
   Data         map[string]interface{}
   Causes       []error
   Component    ErrComponent
   ResponseType ResponseErrType
}

type ResponseErrType string

const (
   BadRequest    ResponseErrType = "BadRequest"
   Forbidden     ResponseErrType = "Forbidden"
   NotFound      ResponseErrType = "NotFound"
   AlreadyExists ResponseErrType = "AlreadyExists"
)

重试

在少数情况下,出现error会进行重试。retry字段可以通过设置Retryable标记来决定是否要进行error重试:

type GoError struct {
   error
   Code         string
   Message      string
   Data         map[string]interface{}
   Causes       []error
   Component    ErrComponent
   ResponseType ResponseErrType
   Retryable    bool
}

GoError 接口

通过定义一个带有GoError实现的显式error接口,可以简化error检查:

package goerr

type Error interface {
   error

   Code() string
   Message() string
   Cause() error
   Causes() []error
   Data() map[string]interface{}
   String() string
   ResponseErrType() ResponseErrType
   SetResponseType(r ResponseErrType) Error
   Component() ErrComponent
   SetComponent(c ErrComponent) Error
   Retryable() bool
   SetRetryable() Error
}

抽象error

有了上述的封装方式,更重要的是对error进行抽象,将这些封装保存在同一地方,并提供error函数的可重用性

func ResourceNotFound(id, kind string, cause error) GoError {
   data := map[string]interface{}{"kind": kind, "id": id}
   return GoError{
      Code:         "ResourceNotFound",
      Data:         data,
      Causes:       []error{cause},
      Component:    ErrService,
      ResponseType: NotFound,
      Retryable:    false,
   }
}

这个error函数抽象了ResourceNotFound这个error,开发者可以使用这个函数来返回error对象而不是每次创建一个新的对象:

//UserService
user, err := u.repo.FindUser(ctx, userId)
if err != nil {
   if err.ResponseType == NotFound {
      return ResourceNotFound(userUid, "User", err)
   }
   return err
}

结论

我们演示了如何使用添加上下文数据的自定义Go的error类型,从而使得error在多层应用程序中更有意义。你可以在这里[1]看到完整的代码实现和定义。

到此这篇关于Go应用中优雅处理Error的文章就介绍到这了,更多相关Go优雅处理Error内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

参考资料

[1] 这里: https://gist.github.com/prathabk/744367cbfc70435c56956f650612d64b

相关文章

  • go并发数据一致性事务的保障面试应答

    go并发数据一致性事务的保障面试应答

    这篇文章主要为大家介绍了go并发数据一致性事务的保障面试应答,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • 详解如何使用Go模块进行依赖管理

    详解如何使用Go模块进行依赖管理

    本文将介绍Go语言中的模块(module)概念,以及如何使用Go模块进行依赖管理,我们会探讨模块的基本概念、使用方法、配置和依赖关系管理等方面的内容,需要的朋友可以参考下
    2023-10-10
  • Go 自定义package包设置与导入操作

    Go 自定义package包设置与导入操作

    这篇文章主要介绍了Go 自定义package包设置与导入操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-05-05
  • 深入理解GO语言的面向对象

    深入理解GO语言的面向对象

    相信很多人当初开发go语言的时候就是因为C++的特性太过于繁杂,从而使得很多C++的开发者因为C++的特性而头疼,go语言成功的精简了C++的特性,使其很简洁,很少的特性,却可以完成很多的事情。下面我们就来详细理解下GO语言的面向对象。
    2016-10-10
  • go mod tidy报错:zip: not a valid zip file解决办法

    go mod tidy报错:zip: not a valid zip file解决办法

    这篇文章主要给大家介绍了关于go mod tidy报错:zip: not a valid zip file的解决办法,go mod是进行代码管理,这错误是因为本地分支和远程分支冲突,本文通过代码介绍的非常详细,需要的朋友可以参考下
    2024-01-01
  • 详解go语言判断管道是否关闭的常见误区

    详解go语言判断管道是否关闭的常见误区

    这篇文章主要想和大家一起探讨一下在Go语言中,我们是否可以使用读取管道时的第二个返回值来判断管道是否关闭,文中的示例代码讲解详细,有兴趣的可以了解下
    2023-10-10
  • 教你一分钟配置好Go语言开发环境(多种操作系统)

    教你一分钟配置好Go语言开发环境(多种操作系统)

    在这篇文章中,我们从头到尾一步步指导你配置Golang开发环境,并编写你的第一个"Hello, World!"程序,我们详细解释了在多种操作系统(包括Windows、Linux和macOS)下的安装过程、环境变量设置以及如何验证安装是否成功
    2023-09-09
  • Go语言实战之详细掌握正则表达式的应用与技巧

    Go语言实战之详细掌握正则表达式的应用与技巧

    正则表达式是一种从左到右与主题字符串匹配的模式,正则表达式用于替换字符串中的文本,验证表单,基于模式匹配从字符串中提取子字符串等等,这篇文章主要给大家介绍了关于Go语言实战之详细掌握正则表达式的应用与技巧,需要的朋友可以参考下
    2023-12-12
  • Go语言设计模式之结构型模式

    Go语言设计模式之结构型模式

    本文主要聚焦在结构型模式(Structural Pattern)上,其主要思想是将多个对象组装成较大的结构,并同时保持结构的灵活和高效,从程序的结构上解决模块之间的耦合问题
    2021-06-06
  • Golang Redis连接池实现原理及示例探究

    Golang Redis连接池实现原理及示例探究

    这篇文章主要为大家介绍了Golang Redis连接池实现示例探究,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-01-01

最新评论