从Golang中的panic、defer和recover到错误处理的理解方式

从Golang中的panic、defer和recover到错误处理的理解方式

Golang中的异常,不是用try-catch来处理的。很多Go的函数会返回多个参数,一般最后一个参数为error类型。

在Golang的设计哲学里:

  • 异常应该用对应的逻辑去处理,无法处理使程序无法继续的情况返回error。
  • error不是异常,是需要被记录到log中的使程序无法运行的情况
  • error位置的返回值为nil即认为没有错误。
  • panic会打断程序,输出错误日志,recover会取消panic的打断继续运行。

panic

panic可以直接从代码初始化:当程序不能继续运行时,可以使用panic函数产生一个中止程序的runtime错误。

panic接收一个做任意类型的参数,通常是字符串,在程序死亡时被打印出来。Go运行时负责中止程序并给出调试信息。

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
fmt.Println("Starting the program")
panic("A severe error occurred: stopping the program!")
fmt.Println("Ending the program")
}

输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
Starting the program
panic: A severe error occurred: stopping the program!
panic PC=0x4f3038
runtime.panic+0x99 /go/src/pkg/runtime/proc.c:1032
runtime.panic(0x442938, 0x4f08e8)
main.main+0xa5 E:/Go/GoBoek/code examples/chapter 13/panic.go:8
main.main()
runtime.mainstart+0xf 386/asm.s:84
runtime.mainstart()
runtime.goexit /go/src/pkg/runtime/proc.c:148
runtime.goexit()
---- Error run E:/Go/GoBoek/code examples/chapter 13/panic.exe with code Crashed
---- Program exited with code -1073741783

当一段程序被封装成函数,出现的error应该被作为返回值传递上去,交由调用的那部分程序处理。

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

import "fmt"

func aFun() (int,error){
res:=doSth()
if res==0{
return 0,error.New("The result shouldn't be 0")
}else{
return 0,nil //everything is fine
}

}

func main(){
res,err=aFun()
if err!=nil{
panic(err)//or do sth else to handle the error
}else{
fmt.Println("Yeah,it's ok",res)
}
}

说白了error就是用来传递错误的一个字符串,panic就是用来打印错误的一种print函数。

defer

defer是个关键字。每个defer用来指定一个函数,这个函数将会在函数结束后执行。属于函数结束的情况有:

  • 函数执行了return语句
  • 运行到函数结尾自动返回
  • 对应的goroutine panic(也就是上面说的那种)

对于函数执行结束的例子:

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

import "os"

func main() {
f, err := os.Open(filename)
if err != nil {
return err
}
/**
* 这里defer要写在err判断的后边而不是os.Open后边
* 如果资源没有获取成功,就没有必要对资源执行释放操作
* 如果err不为nil而执行资源执行释放操作,有可能导致panic
*/
defer f.Close()
}

对于函数抛出panic的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
defer func() {
fmt.Println("b")
}()
panic("a")
}
/* 结果:
b
panic: a

goroutine 1 [running]:
main.main()
/xxxxx/src/xxx.go:50 +0x39
exit status 2
*/

如果有多个defer函数,调用顺序类似于栈,越后面的defer函数越先被执行(后进先出)

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
defer fmt.Println(4)
}
/* 结果:
4
3
2
1
*/

recover

最后说道recover,recover会被写在defer的函数里,用来阻止当前函数内的panic报错,无论是本身的还是调用的其他函数里的panic,并获取到panic里error里的字符串。recover所在的函数会正常继续执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func G() {
defer func() {
fmt.Println("G函数defer")
}()
F()
fmt.Println("继续执行")
}

func F() {
defer func() {
if err := recover(); err != nil {
fmt.Println("捕获异常:", err)
}
fmt.Println("捕获结束")
}()
panic("异常str")
}
/* 结果:
捕获异常: 异常str
捕获结束
继续执行
G函数defer
*/

如果一直没有recover,抛出的panic到当前goroutine最上层函数时,程序直接异常终止。

recover都是在当前的goroutine里进行捕获的,这就是说,对于创建goroutine的外层函数,如果goroutine内部发生panic并且内部没有用recover,外层函数是无法用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
27
28
29
30
31
func G() {
defer func() {
//goroutine外进行recover
if err := recover(); err != nil {
fmt.Println("捕获异常:", err)
}
fmt.Println("c")
}()
//创建goroutine调用F函数
go F()
time.Sleep(time.Second)
}

func F() {
defer func() {
fmt.Println("F函数defer")
}()
//goroutine内部抛出panic
panic("F函数Panic")
}
/* 结果
F函数defer
panic: F函数Panic

goroutine 5 [running]:
main.F()
/xxxxx/src/xxx.go:67 +0x55
created by main.main
/xxxxx/src/xxx.go:58 +0x51
exit status 2
*/

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×