定义
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
的使用场景;
Defer
Defer
是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
顺序逆序, 这也是调用栈的本身特点;Panic
panic
是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
触发时, 会将堆栈信息输出;
Recover
recover
用于处理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
的有一个简单的理解;