panic 和 recover
panic相当于throw exception,recover相当于try catch。
所以经过recover接收的异常被处理后,不会让程序crash。
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println(r)
}
}()
panic("异常信息")
}
// 异常信息
panic 和 recover 的函数签名如下:
panic(interface{})
revover()interface{}
panic的引发
1.程序主动调用panic函数。
2.程序产生运行时错误,由运行时检测并抛出。
发生panic后,程序会从调用panic的函数位置或发生panic的地方立即返回,逐层向上执行函数的defer语句,然后逐层打印函数调用堆栈,直到被recover捕获或运行到最外层函数而退出。
不仅如此,在defer逻辑里也可以再次调用panic或抛出panic。defer 里面的 panic 能够被后续执行的 defer 捕获。
recover
recover()用来捕获panic,阻止panic 继续向上传递。recover()和defer一起使用,但是recover()只有在defer后面的函数体内被直接调用才能捕获panic终止异常,否则返回nil,异常继续向外传递。
以下场景会捕获失败:
// 这个会捕获失败
defer recover()
// 这个会捕获失败
defer fmt.Println(recover())
// 这个嵌套两层也会捕获失败
defer func() {
func() {
println("defer inner")
recover() // 无效
} ()
} ()
以下场景会捕获成功:
defer func() {
println("defer inner")
recover()
} ()
func except() {
recover()
}
func test() {
defer except()
panic("test panic")
}
连续多个panic抛出
可以有连续多个panic被抛出,连续多个panic的场景只能出现在defer调用里面,否则不会出现多个panic被抛出的场景。但只有最后一次panic能被捕获。例如:
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
} ()
// 只有最后一次panic调用能够被捕获
defer func() {
panic("first defer panic")
} ()
defer func() {
panic("second defer panic")
} ()
panic("main body panic")
}
// 结果
first defer panic
新goroutine中抛出的panic
函数并不能捕获内部新启动的 goroutine 所抛出的 panic ,所以比如init函数引发的panic只能在init函数中捕获,在main中无法捕获。
例如:
func do () {
// 这里不能捕获da函数中的panic
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
} ()
go da()
go db()
time.Sleep(3 * time.Second)
}
func da() {
panic("panic da")
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}
func db() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}
error
Go语言内置错误接口类型error。任何类型只要实现Error() string 方法,都可以传递error 接口类型变量。Go语言典型的错误处理方式是将error作为函数最后一个返回值。在调用函数时,通过检测其返回的error值是否为nil来进行错误处理。
type error interface {
Error() string
}
错误处理的最佳实践:
1.在多个返回值的函数,error通常作为函数最后一个返回值。
2.如果一个函数返回 error 类型变量,则先用 if 语句处理 error != nil 的异常场景,正常逻辑放到if语句块的后面,保持代码平坦。
3.defer语句应该放到err判断的后面,不然有可能产生panic。
4.在错误处理逐级向上传递的过程中,错误信息应该不断地丰富和完善,而不是简单地抛出下层调用的错误。这在错误日志分析时非常有用和友好。
错误和异常
广义上的错误:发生非期望的行为。
狭义的错误:发生非期望的已知行为,这里的已知是指错误的类型是预料并定义好的。
异常:发生非期待的未知行为。这里的未知是指错误的类型不在预先定义的范围内。又被称为未捕获的错误 (untrapped error) 。,程序编译器和运行时都没有及时将其捕获处理。而是由操作系统进行异常处理。
错误分类关系如图
Go是一门类型安全的语言,不会出现 untrapped error ,所以Go语言不存在所谓的异常,出现的"异常"全是错误。
Go语言的两种错误处理机制
1.通过函数返回错误类型(error)的值来处理错误。
2.通过panic打印程序调用栈,终止程序执行来处理错误。
对应的也有两条错误处理规则:
1.程序发生的错误导致程序不能容错继续执行,此时程序应该主动调用panic或由运行时抛出 panic 。
2.程序虽然发生错误,但是程序能够容错继续执行,此时应该使用错误返回值的方式处理错误,或者在可能发生运行时错误的非关键分支.上使用recover捕获panic。
go的整个错误处理过程如下所示:
参考资料:
《go语言核心编程》