Golang错误异常处理

那些看似不起波澜的日复一日,会突然在某一天让人看到坚持的意义。

error

对于Go语言(golang)的错误设计,相信很多人已经体验过了,它是通过返回值的方式,来强迫调用者对错误进行处理,要么你忽略,要么你处理(处理也可以是继续返回给调用者),对于golang这种设计方式,我们会在代码中写大量的if判断,以便做出决定。

1
2
3
4
5
6
7
8
func main() {
conent,err:=ioutil.ReadFile("filepath")
if err !=nil{
//错误处理
}else {
fmt.Println(string(conent))
}
}

error 接口

error其实一个接口,内置的,我们看下它的定义

1
2
3
4
5
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
Error() string
}

它只有一个方法 Error,只要实现了这个方法,就是实现了error。现在我们自己定义一个错误试试。

1
2
3
4
5
6
type fileError struct {
}

func (fe *fileError) Error() string {
return "文件错误"
}

自定义 error

自定义了一个fileError类型,实现了error接口。现在测试下看看效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
conent, err := openFile()
if err != nil {
fmt.Println(err)
} else {
fmt.Println(string(conent))
}
}

//只是模拟一个错误
func openFile() ([]byte, error) {
return nil, &fileError{}
}

我们运行模拟的代码,可以看到文件错误的通知。

在实际的使用过程中,我们可能遇到很多错误,他们的区别是错误信息不一样,一种做法是每种错误都类似上面一样定义一个错误类型,但是这样太麻烦了。我们发现Error返回的其实是个字符串,我们可以修改下,让这个字符串可以设置就可以了。

1
2
3
4
5
6
7
type fileError struct {
s string
}

func (fe *fileError) Error() string {
return fe.s
}

恩,这样改造后,我们就可以在声明fileError的时候,设置好要提示的错误文字,就可以满足我们不同的需要了。

1
2
3
4
//只是模拟一个错误
func openFile() ([]byte, error) {
return nil, &fileError{"文件错误,自定义"}
}

恩,可以了,已经达到了我们的目的。现在我们可以把它变的更通用一些,比如修改fileError的名字,再创建一个辅助函数,便于我们创建不同的错误类型。

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

type errorString struct {
s string
}

func (e *errorString) Error() string {
return e.s
}

panic

error 返回的是一般性的错误,但是 panic 函数返回的是让程序崩溃的错误。

也就是当遇到不可恢复的错误状态的时候,如数组访问越界、空指针引用等,这些运行时错误会引起 painc 异常,在一般情况下,我们不应通过调用 panic 函数来报告普通的错误,而应该只把它作为报告致命错误的一种方式。当某些不应该发生的场景发生时,我们就应该调用 panic。

一般而言,当 panic 异常发生时,程序会中断运行。随后,程序崩溃并输出日志信息。日志信息包括 panic value 和函数调用的堆栈跟踪信息。

当然,如果直接调用内置的 panic 函数也会引发 panic 异常;panic 函数接受任何值作为参数。

直接调用 panic 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func TestA() {
fmt.Println("func TestA()")
}
func TestB() {
panic("throw error")
}
func TestC() {
fmt.Println("func TestC()")
}

func main() {
TestA()
TestB()
TestC()
}

我们在实际的开发过程中并不会直接调用 panic ( ) 函数,但是当我们编程的程序遇到致命错误时,系统会自动调用该函数来终止整个程序的运行,也就是系统内置了 panic 函数。

数组下标越界的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func TestA() {
fmt.Println("func TestA()")
}
func TestB(x int) {
var a [10] int
a[x] = 222 // x 值为11时候,数组越界
}
func TestC() {
fmt.Println("func TestC()")
}

func main() {
TestA()
TestB(11) // TestB() 发生异常 中断程序
TestC()
}

recover

运行时 panic 异常一旦被引发就会导致程序崩溃。这当然不是我们愿意看到的,因为谁也不能保证程序不会发生任何运行时错误。

Go 语言为我们提供了专用于 “拦截” 运行时 panic 的内建函数 ——recover。它可以是当前的程序从运行时 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
package main

import "fmt"

func TestA() {
fmt.Println("func TestA()")
}
func TestB(x int) {
// 设置recover
defer func() {
recover()
}() // 自调用改匿名函数
var a [10] int
a[x] = 111 // 当x为20 的时候,导致数组越界,产生pani,导致程序崩溃
}
func TestC() {
fmt.Println("func TestC()")
}

func main() {
TestA()
TestB(11) // TestB() 发生异常 中断程序
TestC()
}

通过以上程序,我们发现虽然 TestB () 函数会导致整个应用程序崩溃,但是由于在改函数中调用了 recover () 函数,所以整个函数并没有崩溃。虽然程序没有崩溃,但是我们也没有看到任何的提示信息,那么怎样才能够看到相应的提示信息呢?

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
package main

import "fmt"

func TestA() {
fmt.Println("func TestA()")
}
func TestB(x int) {
// 设置recover
defer func() {
if err := recover(); err != nil { // 产生了panic 异常
fmt.Println(err)
}
}() // 自调用改匿名函数
var a [10] int
a[x] = 111 // 当x为20 的时候,导致数组越界,产生panic,导致程序崩溃
}
func TestC() {
fmt.Println("func TestC()")
}

func main() {
TestA()
TestB(1111) // TestB() 不让他越界
TestC()
}

https://xuhy.top/posts/develop/go/go-lesson/go-lesson07/
https://learnku.com/docs/qianxi-golang/2020/section-2-panic-function/6444#ed4f2f
https://www.flysnow.org/2019/01/01/golang-error-handle-suggestion.html