10 panic 和 error
[TOC]
panic 和 error
异常 panic
运行时错误panic使用场景
程序遇到无法正常执行下去的错误,主动调用panic函数结束程序运行。
在调试程序时,通过主动调用panic实现快速退出,panic打印出的堆栈能够更快地定位错误。
异常是发生了未知的行为,错误的类型不在预定义的范围之内。
异常又称为未捕获的错误。程序在执行时发生未预先定义的错误,程序编译器和运行时都没有及时将其捕获处理,而是由操作系统进行异常处理。
无法完全避免运行时错误的发生,只能尽量减少其发生的概率,避免因为一个panic引发整个程序崩溃。
panic与recover
Go运行时错误主要通过panic()和recover()两个函数处理,panic()主动抛出错误,recover()捕获panic()抛出的错误。
panic 函数签名
panic(v interface{})
recover 函数签名
引发panic的两种情况,程序主动调用panic函数或者产生运行时错误由运行时检测并抛出。
panic发生后,程序会从调用panic的函数位置或发生panic的地方立即返回,逐层向上执行函数的defer语句,然后逐层打印函数调用栈,直到被recover捕获或运行到最外层函数而退出。
panic不但可以在函数正常流程中抛出,在defer逻辑里也可以再次调用panic或抛出panic。defer里面的panic能够被后续执行的defer捕获。
defer 和 recover()配合使用捕获异常
recover用来捕获panic,阻止panic继续向上传递。
recover只有在defer后面的函数体内被直接调用才能捕获panic终止异常,否则返回nil,panic继续向上传递。
无法捕获,defer 直接调用recover()
正确捕获,recover()写在defer的函数体内
多个panic抛出,只有最后一个panic会被捕获
延迟抛出多个panic,只有最后一个panic被捕获,但是其它panic也不会继续向上传递导致程序终止,也就是只需要一个recover就能让函数按照正常流程执行。
案例中只捕获了最后抛出的错误1
一个函数调用栈中先进的才可以捕获后进的panic
main()函数无法捕获init()函数的panic
init()函数中抛出的panic无法在main()函数中捕获,因为init()函数不是main()函数调用的。而init()函数也无法捕获main()函数中的panic,因为main()函数也不是init()函数调用的。
属于一个函数调用栈中先进的捕获后进的panic
test()函数是main()函数调用的,因此main()函数可以捕获test()函数中的panic
函数无法捕获协程(goroutine)内的panic
开启的协程中出现panic,开启协程的函数是无法捕获的
错误 error
为保证程序的健壮性,需要主动在程序的分支流程上使用recover()拦截运行时错误。Go除了panic和recover异常处理方式外还有使用error错误类型。
接口类型 error
Go内置错误接口类型error,任何类型只要实现Error() string 方法,都可以传递error接口类型变量。
Go中常见错误处理方式是将error作为函数最后一个返回值,在调用函数时通过检测其返回的error值是否为nil来进行错误处理。
标准库函数封装实现error接口
Go标准库中提供两个函数返回实现了error接口的具体类型实例,一般的错误可以使用两个函数进行封装,遇到复杂的错误再自定义错误类型。
函数1:errors.New(string)
函数2:fmt.Errorf(string,...interface{})
自定义error
自定义错误比较,比较的是Error() string 返回值,字符串比较。
error 陷阱
错误的自定义 error 返回值
例子:
输出结果:
调用 fun2() 函数以后 err 就不是 nil,错误地进入了 if,但是 fun2() 函数的返回值是 nil
错误1:这是一种代码编写不规范引起的错误,
func fun2() *myError应改为func fun2() error,返回值可以是具体实现。错误2:直接调用 fun2() 函数,却不会错误地进入 if,???,这就是坑,因此编写代码要规范,错误返回类型一律是 error,返回值可以是具体实现。
Last updated