• panic和recover的使用规则


    转自个人博客 chinazt.cc

    在上一节中,我们介绍了defer的使用。 这一节中,我们温习一下panic和recover的使用规则。

    在golang当中不存在tye ... catch 异常处理逻辑。在golang当中使用defer, panic和recover来控制程序执行流程,借此来达到处理异常的目的。

    Panic是一个可以停止程序执行流程的内置函数。 假设当前F函数当中某处代码触发panic函数,则F函数停止后面代码的执行,转而执行F函数内部的defer函数(如果已经声明了defer函数的话...),然后结束F函数,将当前处理权转给F的调用函数。

    对于F的调用方M来说,F是调用panic函数结束的,而不是执行return结束的。这一点很重要,因为调用panic函数结束是没有返回值的。

    对于F的调用方M来说,F调用结束并且已经退出。所以引出第一个规则:

    调用panic后,调用方函数执行从当前调用点退出

    我们通过下面的代码来解释这个问题:

    package main
    
    import "fmt"
    
    func main() {
    f()
    fmt.Println("Returned normally from f.")
    }
    
    func f() {
    defer func() {
    if r := recover(); r != nil {
    fmt.Println("Recovered in f", r)
    }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
    }
    
    func g(i int) {
    if i > 3 {
    fmt.Println("Panicking!")
    panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
    }
    

    这段函数里面,当i大于3之后,触发panic函数。 在i小于3之前,一切正常。 引出我们来分析一下执行逻辑。

    首先f()里面,没有调用g()函数之前,没有异常。因此会输出"Calling g."。而后开始调用g(),当输入的参数小于3之前,会一直输出"Printing in g",(为什么不输出"Defer in g"请参考上一篇文章 (defer的使用规则))。

    当i4时,开始触发panic函数,输出Panicking!。需要记住panic之后,调用方直接从调用点退出。 因为g()里面是迭代调用,因此当i4时触发panic时,本质是g(i+1)这一行触发的panic函数。 因此g()后续的函数不再继续执行,因为存在defer函数了,所以连续输出三个"Defer in g"。

    此时g(0)函数执行完毕,f()函数退出。因为f()函数里面存在defer函数,因此会调用defer 输出"Recoverd in f"。

    所以最终的输出结果将是:

    Calling g.
    Printing in g 0
    Printing in g 1
    Printing in g 2
    Printing in g 3
    Panicking!
    Defer in g 3
    Defer in g 2
    Defer in g 1
    Defer in g 0
    Recovered in f 4
    Returned normally from f.
    

    聊完执行顺序的问题后,我们来看为什么会输出"Recovered in f 4". 这就是规则二:

    规则二 通过panic可以设定返回值

    panic存在的意义,不仅可以控制异常处理流程,还可以用来返回异常原因。如果panic不给调用方返回异常原因,那么调用方就无从下手处理问题。 因此在调用panic时,一般来说都是返回一个字符串,用来标示失败原因。例如上面代码中的"XXX值不得大于3"。

    panic的返回值,通过recover函数来获取。 recover函数也是一个内置函数,专门用来接收panic函数返回值。当panic函数没有被调用或者没有返回值时,recover返回Nil.

    在使用recover函数时,就需要下面的规则三:

    规则三 recover函数只有在defer代码块中才会有效果

    注意此处的有效果不等于执行。言外之意,recover函数可以在任何地方执行,但只有在defer代码块中执行才能真正达到处理异常的目的。

    我们假设,将recover函数移除defer代码块,如下:

    func f() int {
    
    fmt.Println("Calling g.")
    m := g(0)
    r := recover()
    if r != nil {
    fmt.Println("Recovered in f", r)
    }
    fmt.Println("Returned normally from g.", m)
    return m
    }
    

    因此recover函数在外,因此执行结果如下:

    Calling g.
    Printing in g 0
    Printing in g 1
    Printing in g 2
    Printing in g 3
    Panicking!
    Defer in g 3
    Defer in g 2
    Defer in g 1
    Defer in g 0
    panic: 4
    
    goroutine 1 [running]:
    [stack trace omitted]
    

    可以看到recover函数并未生效,其实是没有执行,因为根据规则一,当前执行流程会跳转到defer函数中。因此只有将recover函数定义到defer之中才会真正被执行。

    那么问题来了,recover函数应该定义在哪一级的defer中。 golang是逐级查找的,最终会查找到main函数。 如果main函数中的defer还没有recover函数,golang这会像上面那样抛出最终的异常信息。

    本节内容不多,但个人感觉还都是干货。 毕竟我们不能保证自己的写的代码一定就没有问题,有问题不可怕,可怕的是代码不受控制。所以学好defer,panci和recover,有助于写出健壮的代码。

  • 相关阅读:
    单元测试
    软件工程
    使用工具进行单元测试
    关于软件工程的理解
    使用Junit等工具进行单元测试
    目前对软件工程所存在的问题
    二人组-----五子棋
    使用Junit等工具进行单元测试过程记录
    对软件工程的理解以及存在的问题
    软件设计文档及数据流向图
  • 原文地址:https://www.cnblogs.com/vikings-blog/p/7109519.html
Copyright © 2020-2023  润新知