Golang的异常处理和单元测试

  • 1.Golang语言中没有其他语言中的try...catch...语句来捕获异常和异常恢复
  • 2.在Golang中我们通常会使用panic关键字来抛出异常,在defer中使用recover来捕获异常进行具体逻辑处理
  • 3.Golang中我们通常会在函数或方法中返回error结构对象来判断是否有异常出现

注意事项

  • 1.利用recoverpanic指令,defer必须放在panic之前定义(panic会终止其后要执行的代码).
  • 2.recover只有在defer调用的函数中才有效,否则recover无法捕获到panic.
  • 3.recover处理异常后,业务逻辑会跑到defer之后的处理片段中
  • 4.多个defer会形成defer栈
  • 5.panic会等到整个goroutine退出才会报告错误

常规使用

  1. panic以及recover参数类型为空接口(可存储任何类型对象)interface{}

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    /*
    func panic(v interface{})
    func recover() interface{}
    执行顺序:panic()->带recover的defer
    输出结果:
    oh my god!panic.
    解释:
    defer中的recover成功捕获到了panic的异常
    */
    
    package main
    import (
    "fmt"
    )
    func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err)    
        }
    }()
    panic("oh my god!panic.")
    }
  2. 延迟调用中引发的错误,可被后续延迟调用捕获(仅最后一个错误被捕获)

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    /*
    执行顺序:panic()->带panic的defer匿名函数->带recover()的defer匿名函数
    输出结果:
    catch the panic
    解释:
    defer中的recover仅能捕获最后一个错误
    package main
    import (
    "fmt"
    )
    func main() {
    defer func() {
        if err := recover();err != nil {
            fmt.Println("catch the panic")
        }
    }()
    defer func() {
        panic("oh my god! panic.")
    }()
    
    panic("something panic!")
    
    }
  3. 捕获函数recover()只有在defer调用内直接调用才会终止,否则返回nil

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    /*
    代码执行顺序:panic->在匿名函数中嵌套recover的defer函数->带fmt的defer->带recover的defer->在匿名函数中调用recover的defer
    输出结果:
    defer inner
    <nil>
    defer recover panic error
    解释: 多个defer之间形成defer栈,最底部的defer优先执行;第三个defer打印了recover()的零值`nil`,仅有第一个defer成功捕获了最底部的panic("panic error")
    */
    package main
    import "fmt"
    func main() {
    defer func() {
        fmt.Println("defer recover",recover())
    }()
    defer recover()
    defer fmt.Println(recover())
    defer func() {
        func(){
            fmt.Println("defer inner")
            recover()
        }()
    }()
    panic("panic error")
    }      
  4. 将代码块放置在匿名函数中可实现在函数逻辑中进行异常恢复,而不影响主函数

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    /*
    代码执行顺序:匿名函数中的panic语句->匿名函数中i自加运算->匿名函数中的fmt->匿名函数中的defer->主函数中的fmt
    输出结果:
    i is: 2
    解释:panic会终止其之后的执行,因此优先执行匿名函数中的panic之后便被defer中的recover捕获,将i赋值为2,其后匿名函数退出开始继续执行主函数中的fmt.Println语句
    */
    package main
    import "fmt"
    func main() {
    test()
    }
    func test() {
    var i int
    func() {
        defer func(){
            if err := recover();err != nil {
                i = 2
            }
        }()
        panic("something panic!")
        i += 8
        fmt.Println("no panic, i is:",i)
    }()
    fmt.Println("i is:",i)
    }
  5. goroutine中的recover

注意:如果一个没有recovergoroutine发生了panic,那么整个进程都会挂掉

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
sync.WaitGroup用来等待一组goroutine的结束,Add方法用来设置等待的goroutine数量,Done方法表示一个goroutine运行结束,使用Wait方法将全部的goroutine阻塞住,直到全部goroutine执行完毕

代码执行顺序:goroutine中的逻辑->wg.Wait()->fmt.Println
输出结果:
panic recover assignment to entry in nil map
donw
解释:
在goroutine中我们声明了一个info的map[string]string类型,我们都知道在map,slice,channel都是引用类型,需要使用make函数进行初始化操作之后进行赋值。而这里直接使用info["name"] = "BGBiao"进行赋值导致panic,fmt.Println函数就会被终止执行,从而执行带recover的defer,之后执行带wg.Done()的defer并退出goroutine执行主程序逻辑
*/
package main
import (
    "fmt"
    "sync"
)
func main() {
    var wg sync.WaitGroup
    wg.Add(4)
    go func() {
        defer wg.Done()
        defer func() {
            if err := recover();err != nil {
                fmt.Println("panic recover",err)
            }
        }()
        var info map[string]string
        info["name"] = "BGBiao"
        fmt.Println(info)
    }()
    wg.Wait()
    fmt.Println("done")
}

知识星球

公众号