Go 语言中的错误处理
通过模块化组件的方式,学习 Go 语言的微服务
Go 语言的错误处理
Go 语言中的 error 定义如下
1 | |
在 Go 1.13 版本之前,我们通常使用的是 errors.New() 来进行返回一个 error 对象指针,相应的源码如下所示:
1 | |
关于 Go 中的指针与 C 中指针大致相同,都是指向某块内存地址值,可以通过 * 解引用的方式进行使用。至于此处为什么错误类型需要返回一个指针变量,如下解释:
主要是防止因为相同的字符串进行匹配,导致错误类型区别不准而导致的问题
可能第一次听会难以理解,我们通过代码来解释,来看以下的代码:
1 | |
以上代码输出的是 Named Type Error,因为 var ErrorStructType = errors.New("EOF") 与 if ErrorStructType == errors.New("EOF") 虽然字符串的值相同,但是因为返回的是一个指针地址,地址不同的指针进行比较,当然也不同。所以这就是为什么要返回指针的原因。
其中 panic 与 error 的区别,主要是 error 类型定义仅仅只作为值类型进行处理,其抛出错误,证明业务进行到此处,有错误,但是不影响全局,程序员在后续能进行处理。
而 panic 一般表示不可恢复类的错误,比如栈溢出、索引越界等程序员在后续无法进行处理的问题,必须修改代码才能解决问题。
错误类型
一般错误类型有以下几种:
1、Sentinel Error 预定义的错误,如 io.EOF、syscall.ENOENT。使用预定义的错误相当的不方便,所以尽可能避免使用预定义的错误,如果实在要使用,需要联系资深的程序员,进行 code review 。
2、Error types 使用 error interface 接口的自定义类型,定义后可以包装底层的错误,提供更多的上下文与堆栈的信息,方便程序员进行调试、定位问题。但是使用这种 interface 接口进行实现的 API 需要将 error 变成 public 字段,会导致和调用者产生强耦合,从而导致 API 变得脆弱。
所以还是得尽量避免使用 error types
3、Opaque errors 最为灵活的错误处理,主要是只返回错误,但是错误内部不透明,一般建议 API 的设计,对外只暴露一个 error 类型的返回值即可!
go 1.13 版本前的错误处理
一般的错误处理模式基本上是 if err != nil {} 这种常见的模式。
所以此处博主只写,博主建议的事情:
1、无第三方库协作的时候,简单使用 errors.New() 或者 errors.Errorf() 两个函数进行方法的处理。
2、如果需要配合 github 开源第三方包以及公司中台组件内部的包,使用 errors.Wrap() 与 errors.Wrapf() 保存堆栈信息,最终集中化输出所有日志,而不是遇到错误就打印日志。
go 1.13 版本后的错误处理
go 在 1.13 版本给 errors 包新增了两个函数方法:
errors.Is() 与 errors.As()
并在 fmt 包中也新增了 fmt.Errorf() 函数方法。
用于向错误中添加错误字段,占位符为 %w,用于支持以上两个 errors 方法。具体使用案例如下
1 | |
go 日志错误处理注意事项
1、关于 log.Fatal() 使用只推荐在 go 项目 init 函数中使用,或者main 函数内初始化配置失败时候使用!因为 log.Fatal() 函数会调用 os.Exit(1),此种情况下会导致程序直接退出,defer 函数都不会执行,不利于程序排查问题!
uber_go_guide 错误处理
1、若调用者需要自己处理其中抛出的错误,采用 error.As() 或 error.Is() 函数合适。
2、错误如果不需要匹配,则如果是静态字符串,则使用 errors.New 进行抛出即可,若是动态字符串信息,则使用 fmt.Errorf 或者自定义的错误。
| 错误匹配? | 错误消息 | 指导 |
|---|---|---|
| No | static | errors.New |
| No | dynamic | fmt.Errorf |
| Yes | static | top-level var with errors.New |
| Yes | dynamic | custom error type |