一: panic和recover
作用:panic 用来主动抛出错误; recover 用来捕获 panic 抛出的错误。
概述: 1,引发panic有两种情况 1)程序主动调用panic函数 2)程序产生运行时错误,由运行时检测并抛出 过程: ! 发生 panic 后,程序会从调用 panic的函数位置或发生panic 的地方立即返回,逐层向上执行函数的defer语句, 然后逐层打印函数调用堆栈,直到被 recover 捕获或运行到最外层函数而退出。 ! panic的参数是一个空接口类型 interface{},所以任意类型的变量都可以传递给 panic(xxx) ! panic 不但可以在函数正常流程中抛出,在 defer 逻辑里也可以再次调用 panic 或抛出 panic a defer 里面的 panic 能够被后续执行的 defer 捕获。 recover()用来捕获 panic,阻止panic继续向上传递recover()和defer一起使用 ,但是recover() 只 有在defer后面的函数体内被直接调用才能捕获panic终止异常否则返回 nil,异常继续向外传递
//这会获取失败defer recover()defer fmt.println(recover())//嵌套两层也会获取失败defer func() { func(){ println("defer inner") recover() //无效 }()}
以下场景会获取成功
package mainfunc f(){ defer func() { println("defer inner") recover() }()}func except() { recover()}func test(){ defer except() panic("test panic")}func main() { f() except() test()}
可以有多个panic被抛出,连续多个panic的场景只能出现在延迟调用里面,否则不会出现多个panic被抛出的场景。但只有最后一次panic能被捕获
package mainimport "fmt"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")}
包中 in it 函数引发的 panic 只能在 in it 函数中捕获,在 main 中无法被捕获,原因是 in it
数先于 main 执行,函数并不能捕获内部新启动的 goroutine 所抛出的 panic 。package mainimport ( "fmt" "time")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) }}func main() { fmt.Println(do) fmt.Println(da) fmt.Println(db)}
示例
package mainimport "fmt"//系统抛异常func test01() { a := [5]int{ 0, 1, 2, 3, 4} //a[10] = 123 index := 10 a[index] = 123}func main() { //panic: runtime error: index out of range //test01() //test02() //test03() test04()}//自己抛异常func test02() { getCircleArea(-5)}func getCircleArea(radius float32) (area float32) { if radius < 0 { //抛异常 panic("半径不能为负数") } return 3.14 * radius * radius}func test03() { //延时执行匿名函数 //延时到什么时候?要么正常结束,要么出异常 //recover()是复活的意思 defer func() { if err := recover(); err != nil { fmt.Println(err) } }() //会报错 getCircleArea(-5) //下句话没有打印 fmt.Println("这里有没有执行?")}func test04() { test03() fmt.Println("GAME OVER")}
返回异常
package mainimport ( "errors" "fmt")//算半径func getCircleArea(radius float32) (ret float32, err error) { if radius < 0 { //创建异常 err = errors.New("沙雕,半径不能为负数") return } ret = 3.14 * radius * radius return}func main() { ret, err := getCircleArea(5) if err != nil { fmt.Println(err) } else { fmt.Println("ret=", ret) }}
使用场景:
(1)程序遇到了无法正常执行下去的错误,主动调用 panic 函数结束程序运行(2)在调试程序时,通过主动调用 panic 实现快速退出, panic 打印出的堆枝能够更快地定位错误。
为了保证程序的健壮性,需要主动在程序的分支流程上使用 recover()拦截运行时错误。
Go 提供了两种处理错误 方式,一 种是借助 panic和 recover 的抛出捕获机制,另一种使用error 错误类型
error:
go 语言内置错误接口类型任何类型只要实现 error() string 方法,都可以传递 eηor接口类型变量。 Go 语言典型的错误处理方式是将error作为函数最后一个返回值 在调用函数通过检测其返回的error值是否为nil来进行错误处理。
type error interface{ Error() string}
• 在多个返回值的函数中,error 通常作为函数最后一个返回值• 如果一个函数返回error 类型变量 ,则先用if语句处理 error != nil 异常场景,正常逻辑放到 if 语句块的后面,保持代码平坦。• defer 吾句应该放到四判断的后面,不然有可能产生 panic• 在错误逐级向上传递的过程中,错误信息应该不断地丰富和完善,而不是简单地抛出下层调用的错误。这在错误日志分析 非常有用和友好。
错误和异常
广义上的错误: 发生非期望的行为。狭义的错误:发生非期望的己知行为,这里的己知是指错误的类型是预料并定义好的。异常: 发生非期待的未知行为。这里的未知是指错误的类型不在预先定义的范围内。异常又被称为未捕获的错误( untrapped error )。程序在执行时发生未预先定义的错误,程序编译器和运行时都没有及时将其捕获处理。而是由操作系统进行异常处理,比如 语言程序里面经常出现的 Segmentation Fault (段异常错误),这个就属于异常范畴。
Go 是一门类型安全的语言,其运行时不 出现这种编译器和运行时都无法捕获的错 ,也就是说,
不会出现 untrapped error ,所以从这个角度来说, Go 语言不存在所谓的异常,出现的“异常”全是错误Go 程序需要处理的这些错误可 分为两类1 一类是运行时错误( runtime errors ),此类错误语言的运行时能够捕获,并采取措施一一隐式或显式地抛出 panic2 一类是程序逻辑错误:程序执行结果不符合预期,但不会引发运行时错误对于运行时错误程序员无法完全避免其发生,只能尽量减少其发生的概率,并在不影响程序主功能的分支流程上“ rcover ”这些 panic,避免其因为一个panic引发整个程序的崩溃。
Go 对于错误提供了两种处理机制:
(1) 通过函数返回错误类型的值来处理错误。(2) 通过 panic 打印程序调用枝,终止程序执行来处理错误。所以对错误的处理也有两种方法, 一种是通过返回 个错误类型值来处理错误, 另一种是直接调用 panic 抛出错误,退出程序。Go 是静态强类型语言,程序的大部分错误是可以在编译器检测到的,但是有些错误行为需要在运行期才能检测出来。此种错误行为将导致程序异常退出 。其表现出的行为就和直接调用panic 一样 打印出函数调用技信息,并且终止程序执行在实际的编程中,error和panic 的使用应该遵循如下三条原则:1)程序局部代码的执行结果不符合预期,但此种行为不是运行时错误范围内预定义的错误,此种非期望的行为不会导致程序无法提供服务,此类场景应该使用函数返回 rror 类型变量进行错误处理。2)程序执行过程中发生错误,且该种错误是运行时错误范围内预定义的错误,此时 Go语言默认的隐式处理动作就是调用 panic ,如果此种 panic 发生在程序的分支流程不影响主要更能,则可以在发生 panic 的程序分支上游处使用 recove 进行捕获,避免引发整个程序的崩溃。3)程序局部代码执行结果不符合预期,此种行为虽然不是运行时错误范围内预定义的错误,但此种非期望的行为会导致程序无法继续提供服务,此类场景在代码中应该主动调用 panic终止程序的执行。进一步浓缩为两条规则(1)程序发生的错误导致程序不能容错继续执行,此时程序应该主动调用 panic 或由运行时抛出 panic(2)程序虽然发生错误 但是程序能够容错继续执行,此时应该使用错误返回值的方式处理错误,或者在可能发生运行时错误的非关键分支上使用 recover 捕获 panic