定义
error 类型是Go内置的错误类型, 其定义为:
1 | type error interface { |
可以发现:
error是一个接口类型,nil通常表示为无错误;- 接口内置方法有且仅有一个:
Error() string.
这种简单的接口类型设计使得error在Go中的使用与处理时很灵活的.
创建方式
在Go中定义了诸多方式, 进行错误的创建与操作, 这里列举几种:
fmt
通过fmt.Errorf方法可以获得一个error类型变量. 例如上面例子中的fmt.Errorf("Something wrong happend"). 其源码如下:
1 | package fmt |
也就是说对于fmt.Errorf方法可以简单分为两类:
fmt.Errorf("format string with wrapped error: %w", err): 获得一个嵌套错误, 它包含了原始错误和新的上下文信息。fmt.Errorf("format string with variable: %v, v): 获得一个普通错误.
errors
errors包是Go内置包, 其提供了错误的基本使用方法:New, Wrap, Is, As...;
通过errors.New()创建一个错误;
1 | import "errors" |
github.com/pkg/errors
github.com/pkg/errors包
github.com/pkg/errors包是Go的第三方包, 其对错误处理的进行了一定的扩展, 最大的特点是: 增加了错误信息的上下文信息.
通过errors.New()创建一个错误;
1 | import "github.com/pkg/errors" |
user define
用户可以在自行实现New方法, 例如:
1 | type customError struct { |
使用场景
Blank identifier :_
_在Go语言中充当匿名占位符的作用, 其使用方式与普通标识符并无差异. _占位符的作用是提供了一种忽略机制, 避免编译器报错; 例如可以在导包时, 通过_作为包名别称, 即使在本文件代码中未使用此包, 编译器也不会报错; 可以在变量赋值时, 通过_接收,表示选择忽略掉此变量的处理逻辑, 编译器不会告警;
将error赋值给_字符, 可以忽略掉对于此错误的处理, 继续下面的逻辑, 例如:
1 | func iterate(a, b int) (int, error) { |
注意: 这并非推荐的写法, 很不安全. 在项目中通常会要求, 必须处理所有的error;
Handler Error With Return Values
代码中最常见的处理方式就是子函数返回错误类型, 父函数进行对应的处理, 例如:
1 | func child() (int, error) { |
上面的child函数中返回了error作为错误信息, 当其值不为nil时说明需要进行适当的错误处理; 例如可以进行重试操作或者进行数据清理操作等; child函数中通过fmt包内的Errorf方法创建了一个错误;, 并嵌入错误信息Something wrong happend.
Defer, panic, recover
Defer, panic, recover 机制是Go语言独有的异常处理机制, 其类似与其他语言中的try, catch机制.一般用于无法修复的错误. 而error也会在此机制中使用, 下面将一步步的简单介绍Defer, panic, recover, 最后会理解error的使用场景;
DeferDefer是Go中的一个关键字, 其作用是将当前的函数调用放到调用栈里面,而不是立即执行. 在主函数执行完毕后, 会按照defer的逆序, 依次执行调用栈里面的函数. 注意panic也是一种执行完毕状态, 依旧会触发defer函数执行;例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package main
import (
"fmt"
)
func A() {
defer fmt.Println("Keep calm!")
B()
}
func B() {
defer fmt.Println("Else...")
C()
}
func C() {
defer fmt.Println("Turn on the air conditioner...")
D()
}
func D() {
defer fmt.Println("If it's more than 30 degrees...")
}
func main() {
A()
}输出为:
1
2
3
4If it's more than 30 degrees...
Turn on the air conditioner...
Else...
Keep calm!main中调用了A函数, 其存在多层defer嵌套, 通过输出可以发现, 其输出顺序与defer顺序逆序, 这也是调用栈的本身特点;Panicpanic是Go中的关键字, 其表示出现了无法修复的错误以至于需要对外暴露出来; 例如在上面的示例中, 我们在最内层的函数D中触发panic: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
26package main
import (
"errors"
"fmt"
)
func A() {
defer fmt.Println("Keep calm!")
B()
}
func B() {
defer fmt.Println("Else...")
C()
}
func C() {
defer fmt.Println("Turn on the air conditioner...")
D()
}
func D() {
defer fmt.Println("If it's more than 30 degrees...")
panic(errors.New("Oh .... Global Warming!!!"))
}
func main() {
A()
}输出为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18If it's more than 30 degrees...
Turn on the air conditioner...
Else...
Keep calm!
panic: Oh .... Global Warming!!!
goroutine 1 [running]:
main.D()
/home/self/cmd/main.go:22 +0xbc
main.C()
/home/self/cmd/main.go:18 +0x85
main.B()
/home/self/cmd/main.go:14 +0x85
main.A()
/home/self/cmd/main.go:10 +0x85
main.main()
/home/self/cmd/main.go:25 +0x25
exit status 2可以看到:
panic触发时,defer调用栈内的函数依旧会执行, 随后执行panic;panic触发时, 会将堆栈信息输出;
Recoverrecover用于处理panic场景, 其可以返回panic调用时的传值.recover必须在defer函数中执行, 否则不会生效; 例如: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
29
30
31
32package main
import (
"errors"
"fmt"
)
func A() {
defer fmt.Println("Keep calm!")
defer func() {
if x := recover(); x != nil {
fmt.Printf("Catch panic: %+v \n", x)
fmt.Printf("Catch panic type: %T \n", x)
}
}()
B()
}
func B() {
defer fmt.Println("Else...")
C()
}
func C() {
defer fmt.Println("Turn on the air conditioner...")
D()
}
func D() {
defer fmt.Println("If it's more than 30 degrees...")
panic(errors.New("Oh .... Global Warming!!!"))
}
func main() {
A()
}输出为:
1
2
3
4
5
6If it's more than 30 degrees...
Turn on the air conditioner...
Else...
Catch panic: Oh .... Global Warming!!!
Catch panic type: *errors.errorString
Keep calm!发现通过
recover函数捕获了panic传值Oh .... Global Warming!!!; 并且发现recover捕获后获得变量类型为*errors.errorString类型, 其实现了error接口定义;那么我们就可以通过
err变量接收panic信息, 并传递给外部, 例如改动下A的函数签名为:func () (err error);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
29
30
31
32
33package main
import (
"errors"
"fmt"
)
func A() (err error) {
defer fmt.Println("Keep calm!")
defer func() {
if e := recover(); e != nil {
err = e.(error)
}
}()
B()
return
}
func B() {
defer fmt.Println("Else...")
C()
}
func C() {
defer fmt.Println("Turn on the air conditioner...")
D()
}
func D() {
defer fmt.Println("If it's more than 30 degrees...")
panic(errors.New("Oh .... Global Warming!!!"))
}
func main() {
err := A()
fmt.Printf("Got error: %v\n", err)
}输出为:
1
2
3
4
5If it's more than 30 degrees...
Turn on the air conditioner...
Else...
Keep calm!
Got error: Oh .... Global Warming!!!Error Wrapping
错误包装允许你将一个错误包装在另一个错误里面,同时保留原始的错误信息和堆栈跟踪。这在调试和错误处理中非常有用。例如:
1 | import "github.com/pkg/errors" |
相关的操作函数有两个: errors.Is errors.As;
errors.Is函数用于检查一个错误值是否等于给定的错误值,或者是否包含给定的错误值。这个函数考虑了错误链中的所有错误,如果链中的任何错误与给定的错误匹配,则返回true。
函数签名如下:
1 | func Is(err, target error) bool |
err是你想要检查的错误。target是你想要检查的特定错误实例。
errors.Is会遍历整个错误链,调用Unwrap方法来获取下一个错误,直到链的末端。如果链中的任何错误与target相等(使用==比较),则Is返回true。
示例:
1 | var ErrNotFound = errors.New("not found") |
errors.As函数用于检查一个错误值是否为特定的错误类型,如果是,则将错误的值赋给一个指定的错误类型变量。这对于处理具有特定结构的错误非常有用,例如自定义错误类型。
函数签名如下:
1 | func As(err error, target interface{}) bool |
err是你想要检查的错误。target是一个指向你想要检查的错误类型变量的指针。
errors.As会遍历错误链,尝试将每个错误值转换为target类型。如果成功,它会将错误值赋给target,并返回true。如果整个链中没有任何错误可以转换为target类型,它会返回false。
示例:
1 | type MyError struct { |
在这个例子中,如果someFunction返回的错误链中包含MyError类型的错误,errors.As会将其赋给myErr变量,并且返回true。如果没有找到MyError类型的错误,则返回false。
errors.Is和errors.As都是错误处理的强大工具,它们使得在错误可能被多次包装的情况下,错误的检查和断言变得更加简单和直接。
总结
本文介绍了error定义, 创建方式, 使用场景等内容; 由于error是非常灵活的类型, 各个项目中一般都会各自进行特性的封装. 而本文介绍了通用场景下的使用, 相信认真读到这里, 可以对error的有一个简单的理解;