错误与异常

错误

Go 语言通过内置的错误接口提供了非常简单的错误处理机制。

error类型是一个接口类型,这是它的定义:

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

error 有了一个签名为 Error() string 的方法。所有实现该接口的类型都可以当作一个错误类型。Error() 方法给出了错误的描述。fmt.Println 在打印错误时,会在内部调用 Error() string 方法来得到该错误的描述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"os"
)

func main() {
// 尝试打开文件
file, err := os.Open("/a.txt")
// 如果打开文件时发生错误 返回一个不等于 nil 的错误
if err != nil {
fmt.Println(err)
return
}
// 如果打开文件成功 返回一个文件句柄 和 一个值为 nil 的错误
fmt.Println(file.Name(), "opened successfully")
}

我们这里没有存在一个文件 a.txt ,所以尝试打开文件将会返回一个不等于 nil 的错误。

自定义错误

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

import (
"errors"
"fmt"
)

func area(a, b int) (int, error) {
if a < 0 || b < 0 {
return 0, errors.New("计算错误, 长度或宽度,不能小于0.")
}
return a * b, nil
}
func main() {
a := 100
b := -10
r, err := area(a, b)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Area =", r)
}

//计算错误, 长度或宽度,不能小于0.

给错误添加更多信息

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 area(a, b int) (int, error) {
if a < 0 || b < 0 {
return 0, fmt.Errorf("计算错误, 长度%d或宽度%d,不能小于0", a, b)
}
return a * b, nil
}
func main() {
a := 100
b := -10
area, err := area(a, b)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Area =", area)
}

// 计算错误, 长度100或宽度-10,不能小于0

异常

错误和异常是两个不同的概念,非常容易混淆。错误指的是可能出现问题的地方出现了问题;而异常指的是不应该出现问题的地方出现了问题。

panic

在有些情况,当程序发生异常时,无法继续运行。在这种情况下,我们会使用 panic 来终止程序。当函数发生 panic 时,它会终止运行,在执行完所有的延迟函数后,程序返回到该函数的调用方。这样的过程会一直持续下去,直到当前协程的所有函数都返回退出,然后程序会打印出 panic 信息,接着打印出堆栈跟踪,最后程序终止。

我们应该尽可能地使用错误,而不是使用 panicrecover 。只有当程序不能继续运行的时候,才应该使用 panicrecover 机制。

panic 有两个合理的用例:

  • 发生了一个不能恢复的错误,此时程序不能继续运行。一个例子就是 web 服务器无法绑定所要求的端口。在这种情况下,就应该使用 panic ,因为如果不能绑定端口,啥也做不了。
  • 发生了一个编程上的错误。假如我们有一个接收指针参数的方法,而其他人使用 nil 作为参数调用了它。在这种情况下,我们可以使用 panic ,因为这是一个编程错误:用 nil 参数调用了一个只能接收合法指针的方法。

触发 panic

下面是内建函数 panic 的签名:

1
func panic(v interface{})

当程序终止时,会打印传入 panic 的参数。

1
2
3
4
5
6
7
8
package main

func main() {
panic("panic error")
}

//打印出传入 panic 函数的信息,并打印出堆栈跟踪:
//panic: panic error

发生 panic 时的 defer

上面已经提到了,当函数发生 panic 时,它会终止运行,在执行完所有的延迟函数后,程序返回到该函数的调用方。这样的过程会一直持续下去,直到当前协程的所有函数都返回退出,然后程序会打印出 panic 信息,接着打印出堆栈跟踪,最后程序终止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func myTest() {
defer fmt.Println("defer myTest")
panic("panic myTest")
}
func main() {
defer fmt.Println("defer main")
myTest()
}

// 输出
/*
defer myTest
defer main
panic: panic myTest
*/

recover

recover 是一个内建函数,用于重新获得 panic 协程的控制。下面是内建函数 recover 的签名:

1
func recover() interface{}

recover 必须在 defer 函数中才能生效,在其他作用域下,它是不工作的。在延迟函数内调用 recover ,可以取到 panic 的错误信息,并且停止 panic 续发事件,程序运行恢复正常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

func outOfArray(x int) {
defer func() {
// recover() 可以将捕获到的 panic 信息打印
if err := recover(); err != nil {
fmt.Println(err)
}
}()
var array [5]int
array[x] = 1
}
func main() {
// 故意制造数组越界 触发 panic
outOfArray(20)
// 如果能执行到这句 说明 panic 被捕获了
// 后续的程序能继续运行
fmt.Println("main...")
}