蹒跚学Go第五天-异常处理

异常处理

所谓的异常:当GO检测到一个错误时,程序就无法继续执行了,出现了一些错误的提示,这就是所谓的”异常”,所以为了保证程序的健壮性,要对异常的信息进行处理。
错误处理是现代化编程语言必须慎重考虑的一个话题。几种主流的语言如C++、Java等都引入了错误处理的概念,比如异常(exception)的概念和try-catch关键字等。Go语言在错误处理上考虑得更为深远,漂亮的错误处理规范是Go语言最大的亮点之一。
因为Go语言并没有像Java那样提供异常机制,所以Go语言不能抛出异常,取而代之的是提供了panic和recover机制。

  • 定义的方式
type error interface {
    Error() string
}
  • 异常处理例子
func test(a,b int) (result int ,err error){
    err = nil
    if b == 0{
        err = errors.New("不能为0")
    }else{
        result  = a / b
    }
    return
}

func main(){
    //在Test( )函数中,判断变量b的取值,如果有误,返回错误信息。
    // 并且在main( )中接收返回的错误信息,并打印出来。
    result,err := test(3,0)
    if err != nil{
        fmt.Println("err=",err)
    }else{
        fmt.Println(result)
    }
}
输出值:
err= 不能为0

panic函数

panic(恐慌)是一个内建函数,可以中断原有的控制流程,进入一个令人“恐慌”的流程中(表示程序运行遇到问题,不知道该怎么办,所以很慌张)。当函数Func1调用panic时,函数的执行就会被中断,但是Func1中的defer函数会正常执行,然后Funcl返回到调用它的地方。在调用Func1的地方,panic会继续蔓延,继续向外围扩散,直到panic的goroutine中所有调用的函数返回,此时程序才会退出。错误信息将被报告,包括在调用panic)函数时传入的参数,这个过程称为错误处理流程。

  • 引发panic有两种情况,一种是程序主动调用panic函数,另一种是程序产生运行时错误,由运行时检测并抛出。

  • 发生panic后,程序会从调用panic的函数位置或发生panic的地方立即返回,逐层向上执行函数的defer 语句,然后逐层打印函数调用堆栈,直到被recover捕获或运行到最外层函数而退出。

应用性场景

在通常情况下,向程序使用方报告错误状态的方式可以是返回一个额外的error类型值。
但是,当遇到不可恢复的错误状态的时候,如数组访问越界、空指针引用等,这些运行时错误会引起painc异常。这时,上述错误处理方式显然就不适合了。反过来讲,在一般情况下,我们不应通过调用panic函数来报告普通的错误,而应该只把它作为报告致命错误的一种方式。当某些不应该发生的场景发生时,我们就应该调用panic

什么情况下主动调用panic 函数抛出panic?

为了保证程序的健壮性,需要主动在程序的分支流程上使用recover)拦截运行时错误。Go提供了两种处理错误的方式,一种是借助panic和recover的抛出捕获机制,另一种是使用error 错误类型。

  • (1)程序遇到了无法正常执行下去的错误,主动调用panic函数结束程序运行。

  • (2)在调试程序时,通过主动调用panic实现快速退出,panic打印出的堆栈能够更快地定位错误。

  • (3)区别使用panicerror两种方式,导致关键流程出现不可修复性错误的情况使用panic,其他情况使用error。

func test1(){
    fmt.Println("func test1()")
}
func test2(x int){
    //panic("func test2() -> panic")    //主动的抛出异常
    var a [10]int
    a[x] = 222  //x值为11时候,数组越界
}
func test3(){
    fmt.Println("func test3()")
}
func main(){
    test1()
    test2(11)   //发生异常主动崩溃
    test3()
}
//所以,我们在实际的开发过程中并不会直接调用panic( )函数,
// 但是当我们编程的程序遇到致命错误时系统会自动调用该函数来终止整个程序的运行
  • 例子二
func main(){
    test()
}
func test(){
    defer func() {
        if r:= recover();r!=nil{
            log.Printf("异常捕获:%v",r)
        }
    }()
    defer func() {
        panic("第二个错误")
    }()
    panic("第一个错误")
}
输出值:
2019/01/25 18:26:05 异常捕获:第二个错误

recover

recover是一个内建的函数,可以让进入令人恐慌的流程中的goroutine恢复过来。如果当前的goroutine陷入恐慌,调用recover可以捕获到panic的输入值,并且恢复正常的执行。由于recoverO函数用于终止错误处理流程,所以一般情况下,recover)仅在defer语句的“函数”中有效,以有效截取错误处理流程,recover0只有在defer的“函数”内直接调用才会终止错误,否则总是返回nil。如果在没有发生异常的goroutine中明确调用recoverO函数,会导致该goroutine所属的进程打印异常信息后直接退出。

  • 定义的方式
func recover() interface{}  //注意:recover只有在defer调用的函数中有效
  • Go语言为我们提供了专用于“拦截”运行时panic的内建函数——recover。它可以是当前的程序从运行时panic的状态中恢复并重新获得流程控制权。

  • 如果调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recover,recover会返回nil。

func test1(){
    fmt.Println("func test1()")
}
func test2(x int){
    //设置recover
    defer func(){
        fmt.Println(recover()) //打印错误信息
        recover() //防止程序奔溃
        //以上在正常范围返回为nil,如果想在的范围内不超出范围不显示则可以加判断
        if err:= recover(); err !=nil {
            fmt.Println(err)
        }
    }()
    var a [10] int
    a[x] = 111  //当x超越20后数组越界 -> 产生panic -> 程序崩溃
}
func test3(){
    fmt.Println("func test3()")
}
func main(){
    test1()
    test2(11)   //异常终止程序
    test3()
}
输出值:
func test1()
runtime error: index out of range
func test3()
//通过以上程序,我们发现虽然TestB( )函数会导致整个应用程序崩溃,但是由于在改函数中调用了recover( )函数,所以整个函数并没有崩溃。
  • recover的使用
func test() {
    defer func(){   //有效 -> 在defer语句的匿名函数中调用
        fmt.Println(recover())
    }()
    defer func(){   //无效 -> 间接调用recover(),返回nil
        func(){
            recover()
        }()
    }()
    defer fmt.Println(recover())    //无效 -> recover()相当于直接调用然后被外部函数打印返回nil
    defer recover() //无效,相当于直接电泳recover()返回nil
    panic("异常报错")
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注