Go 语言中的错误处理

通过模块化组件的方式,学习 Go 语言的微服务

Go 语言的错误处理

Go 语言中的 error 定义如下

1
2
3
type error interface {
Error() string
}

在 Go 1.13 版本之前,我们通常使用的是 errors.New() 来进行返回一个 error 对象指针,相应的源码如下所示:

1
2
3
4
5
6
7
8
func New(text string) error {
return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
s string
}

关于 Go 中的指针与 C 中指针大致相同,都是指向某块内存地址值,可以通过 * 解引用的方式进行使用。至于此处为什么错误类型需要返回一个指针变量,如下解释:

主要是防止因为相同的字符串进行匹配,导致错误类型区别不准而导致的问题

可能第一次听会难以理解,我们通过代码来解释,来看以下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"errors"
"fmt"
)

type errorString string

func (e errorString) Error() string {
return string(e)
}

func New(text string) error {
return errorString(text)
}

var ErrorName = New("EOF")
var ErrorStructType = errors.New("EOF")

func main() {
if ErrorName == New("EOF") {
fmt.Println("Named Type Error")
}
if ErrorStructType == errors.New("EOF") {
fmt.Println("Struct Type Error")
}
}

以上代码输出的是 Named Type Error,因为 var ErrorStructType = errors.New("EOF")if ErrorStructType == errors.New("EOF") 虽然字符串的值相同,但是因为返回的是一个指针地址,地址不同的指针进行比较,当然也不同。所以这就是为什么要返回指针的原因。

其中 panicerror 的区别,主要是 error 类型定义仅仅只作为值类型进行处理,其抛出错误,证明业务进行到此处,有错误,但是不影响全局,程序员在后续能进行处理。

panic 一般表示不可恢复类的错误,比如栈溢出、索引越界等程序员在后续无法进行处理的问题,必须修改代码才能解决问题。

错误类型

一般错误类型有以下几种:

1、Sentinel Error 预定义的错误,如 io.EOFsyscall.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
2
3
err := fmt.Errorf("access denied: %w", ErrPermission)

if errors.Is(err, ErrPermission)

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

参考 Uber Go 语言编码规范


Go 语言中的错误处理
https://chaggle.github.io/2022/04/19/go/microservice/error/
作者
chaggle
发布于
2022年4月19日
许可协议