上一篇讲了golang的错误处理,这篇讲讲异常处理。
错误和异常的区别是,
错误是业务逻辑的一部分,异常不是。而且异常有可能终止业务逻辑。在golang中,异常不要用来代替错误,更不要用来控制流程。
golang提供了panic, recover和defer来实现异常的抛出和捕捉。
具体流程是这样的:
函数中调用panic()抛出异常后,程序会马上中止当前的运行,跳转到延时调用(defer语句),如果defer中调用了recover(),则异常被捕捉,程序继续执行;否则,当前函数的defer执行完后返回到上一个调用函数执行defer,如果到最顶层函数都没遇到recover(),程序崩溃打印出调试信息并终止。
下面分别简单介绍defer, panic, recover
defer
defer能让语句推迟到函数返回时再执行。如:
-
package main
-
-
import (
-
"fmt"
-
)
-
-
func fun() {
-
defer fmt.Println("defer 1")
-
-
fmt.Println("fun")
-
}
-
-
func main() {
-
fun()
-
}
执行:
-
# go run defer/main.go
-
fun
-
defer 1
当有多个defer语句时,将以栈(即先进后出)的形式执行,把上面的fun()函数修改为:
-
func fun() {
-
defer fmt.Println("defer 1")
-
defer fmt.Println("defer 2")
-
-
fmt.Println("fun")
-
}
执行结果:
-
# go run defer/main.go
-
fun
-
defer 2
-
defer 1
panic
该函数的参数为interface{}类型,这意味着传入参数可以是任意类型。
当panic()被执行时,参数会被传递给recover()函数,如果没遇到recover()函数,程序崩溃时参数会作为调试信息打印出来。
recover
recover()用来返回捕捉异常,返回panic()时传入的参数。如果之前没执行panic(),recover()总返回nil
看几种异常的情形:
1. 未捕捉异常
-
package main
-
-
import "fmt"
-
-
func main() {
-
f()
-
fmt.Println("Returned normally from f.")
-
}
-
-
func f() {
-
fmt.Println("Calling g(0).")
-
g(0)
-
fmt.Println("Returned normally from g(0).")
-
}
-
-
func g(i int) {
-
defer fmt.Println("Defer in g", i)
-
-
fmt.Println("Panic!")
-
panic("panic in g")
-
fmt.Println("Printing in g", i)
-
}
运行
-
# go run recover/panic.go
-
Calling g(0).
-
-
Defer in g 0
-
panic: panic in g
-
-
goroutine 1 [running]:
-
panic(0x4b8ee0, 0xc82000a300)
-
/usr/local/go/src/runtime/panic.go:464 +0x3e6
-
main.g(0x0)
-
/root/go/src/github.com/vv1133/golang_example/recover/panic.go:20 +0x292
-
main.f()
-
/root/go/src/github.com/vv1133/golang_example/recover/panic.go:12 +0xeb
-
main.main()
-
/root/go/src/github.com/vv1133/golang_example/recover/panic.go:6 +0x1c
-
exit status 2
程序执行时遇到了panic,然后执行defer,没遇到recover,向上层函数返回,直到退出,打印出panic信息和出错时的栈信息。
2. 捕捉异常
将函数g()改为如下:
-
func g(i int) {
-
defer func() {
-
if r := recover(); r != nil {
-
fmt.Println("Recovered in g", r)
-
}
-
}()
-
-
defer fmt.Println("Defer in g", i)
-
-
fmt.Println("Panic!")
-
panic("panic in g")
-
fmt.Println("Printing in g", i)
-
}
运行
-
# go run recover/recover.go
-
Calling g(0).
-
-
Defer in g 0
-
Recovered in g panic in g
-
Returned normally from g(0).
-
Returned normally from f.
可见,异常被defer中的recover()捕捉了。此时,程序会继续执行。需要注意,程序继续执行的点是recover()函数,而不是出现panic()的地方。
3. 在上一层函数捕捉异常
-
package main
-
-
import "fmt"
-
-
func main() {
-
f()
-
fmt.Println("Returned normally from f.")
-
}
-
-
func f() {
-
defer fmt.Println("Defer in f1")
-
defer func() {
-
if r := recover(); r != nil {
-
fmt.Println("Recovered in f", r)
-
}
-
}()
-
defer fmt.Println("Defer in f2")
-
-
fmt.Println("Calling g(0).")
-
g(0)
-
fmt.Println("Returned normally from g(0).")
-
}
-
-
func g(i int) {
-
defer fmt.Println("Defer in g", i)
-
-
fmt.Println("Panic!")
-
panic("panic in g")
-
fmt.Println("Printing in g", i)
-
}
运行
-
# go run recover/recover2.go
-
Calling g(0).
-
-
Defer in g 0
-
Defer in f2
-
Recovered in f panic in g
-
Defer in f1
-
Returned normally from f.
结果显示异常在函数f()中被捕捉了。
另外,标准库中有一些以Must开头的函数,这些函数出错时,会调用panic
golang中panic的使用类似于C语言中的assert,常用于不可修复性错误或是遇到问题时立刻停止运行的时候,例如init()函数中。
对于业务逻辑处的panic,可以使用recover捕捉,并转换成错误后返回。
示例代码可以在查看。
阅读(6097) | 评论(0) | 转发(0) |