Go 语言没有像Java那样的 try/catch 异常机制,它不能执行抛异常操作,而是使用了 panic 和 recover 机制。
Go语言中引入两个内置函数panic和recover来触发和终止异常处理流程,同时引入关键字defer来延迟执行defer后面的函数。一直等到包含defer语句的函数执行完毕时,延迟函数(defer后的函数)才会被执行,而不管包含defer语句的函数是通过return的正常结束,还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。
一定要记住,应当把 panic 和 recover 机制作为最后的手段来使用,也就是说,你的代码中应当没有,或者很少 panic 的东西。这是一个强大的工具,请明智地使用它。
一、Panic
panic()是一个内建函数,可以中断原有的控制流程,进入一个令人恐慌(即Java中的异常)的流程中。当函数F调用panic时,函数F的执行被中断,但是F中的延迟函数会正常执行,然后F返回到调用它的地方。在调用的地方,F的行为就像调用了panic。这一过程继续向上,直到发生panic的goroutine中所有调用的函数返回,此时程序退出。
恐慌可以直接调用panic产生。也可以由运行时错误产生,例如访问越界的数组。
panic 可以直接从代码初始化:当错误条件(我们所测试的代码)很严苛且不可恢复,程序不能继续运行时,可以使用 panic 函数产生一个中止程序的运行时错误。panic接收一个做任意类型的参数,通常是字符串,在程序死亡时被打印出来。Go 运行时负责中止程序并给出调试信息。
当发生像数组下标越界或类型断言失败这样的运行错误时,Go 运行时会触发运行时panic,伴随着程序的崩溃抛出一个runtime.Error 接口类型的值。这个错误值有个RuntimeError() 方法用于区别普通错误。
最简单的panic示例代码如下:
func main() {
fmt.Println("Starting the program")
panic("A severe error occurred: stopping the program!")
fmt.Println("Ending the program")
}
这段代码的打印结果因人而异,在win10 64位+go1.7.5的环境下打印结果如下:
Starting the program
panic: A severe error occurred: stopping the program!
goroutine 1 [running]:
panic(0x4951e0, 0xc0420062c0)
D:/Go/src/runtime/panic.go:500 +0x1af
main.main()
E:/testcode/test.go:7 +0xf6
exit status 2
var user = os.Getenv("USER")
func init() {
if user == "" {
panic("no value for $USER")
}
}
二、Recover
recover是一个内建的函数,可以让进入令人恐慌的流程中的goroutine恢复过来。recover仅在延迟函数中有效。在正常的执行过程中,调用recover会返回nil,并且没有其它任何效果。如果当前的goroutine陷入panic,调用recover可以捕获到panic的输入值,并且恢复正常的执行。
一般情况下, recover()应该在一个使用defer关键字的函数中执行以有效截取错误处理流程。如果没有在发生异常的goroutine中明确调用恢复过程(使用recover关键字),会导致该goroutine所属的进程打印异常信息后直接退出。
从程序栈的角度来讲,panic会导致栈被展开直到defer修饰的 recover() 被调用或者程序中止。
func throwsPanic(f func()) (b bool) {
defer func() {
if x := recover(); x!=nil {
b = true
}
}()
f() // 执行函数f,如果f中出现了panic,那么就可以恢复回来
return
}
三、综合示例
一个展示panic,defer和 recover怎么结合使用的完整例子如下:
package main
import (
"fmt"
)
func badCall() {
panic("bad end")
}
func test() {
defer func() {
if e := recover(); e != nil {
fmt.Printf("Panicing %s\n", e)
}
}()
badCall()
fmt.Printf("After bad call\n")
}
func main() {
fmt.Printf("Calling test\n")
test()
fmt.Printf("Test completed\n")
}
打印输出:
Calling test
Panicing bad end
Test completed
四、错误处理机制总结
Go 语言的错误处理流程:当一个函数在执行过程中出现了异常或遇到 panic(),正常语句就会立即终止,然后执行defer语句,再报告异常信息,最后退出 goroutine。如果在defer中使用了 recover()函数,则会捕获错误信息,使该错误信息终止报告。
以下实现一个自定义的Error()方法,并结合panic和recover,集中展示下Go语言的错误处理机制:
package main
import (
"fmt"
)
//自定义错误类型
type ArithmeticError struct {
error
}
//重写Error()方法
func (this *ArithmeticError) Error() string {
return "自定义的error,error名称为算数不合法"
}
//定义除法运算函数Devide
func Devide(num1, num2 int) int {
if num2 == 0 {
panic(&ArithmeticError{}) //当然也可以使用ArithmeticError{}同时recover等到ArithmeticError类型
} else {
return num1 / num2
}
}
func main() {
var a, b int
fmt.Scanf("%d %d", &a, &b)
defer func() {
if r := recover(); r != nil {
fmt.Printf("panic的内容:%v\n", r)
if _, ok := r.(error); ok {
fmt.Println("panic--recover()得到的是error类型")
}
if _, ok := r.(*ArithmeticError); ok {
fmt.Println("panic--recover()得到的是ArithmeticError类型")
}
if _, ok := r.(string); ok {
fmt.Println("panic--recover()得到的是string类型")
}
}
}()
rs := Devide(a, b)
fmt.Println("结果是:", rs)
}
终端执行代码,打印输出:
$ go run test.go
5 2
结果是: 2
$ go run test.go
5 0
panic的内容:自定义的error,error名称为算数不合法
panic--recover()得到的是error类型
panic--recover()得到的是ArithmeticError类型