使用defer和recover()实现捕获panic错误

  在没有捕获错误的前提下,程序出现错误会报panic然后停止继续执行,如下面代码中的divide()函数体里出现了除数为零的情况,程序就会报“runtime error: integer divide by zero”的panic并停止继续执行。

package main

import (
   "fmt"
)

func divide(num1 int, num2 int) {
   result := num1 / num2
   fmt.Printf("%+v / %+v = %+v \n", num1, num2, result)
}

func main() {
   divide(1024, 0) // panic: runtime error: integer divide by zero

   // 重要说明:下面的代码是执行不到的,因为上面出现了panic
   fmt.Println("后续逻辑……")
}



  在绝大多数情况下,程序如果出现错误确实应该停止继续执行,但如果出现的错误不会对业务造成致命影响,开发者可能希望跳过错误并继续执行程序,这时就可以使用defer和recover()实现捕获panic错误,如下面的代码:

package main

import (
   "fmt"
)

func divide(num1 int, num2 int) {
   // 使用defer和recover()实现捕获panic错误
   defer func() {
      err := recover()
      if err != nil {
         fmt.Println("程序粗错辣~~~", err) // 程序粗错辣~~~ runtime error: integer divide by zero
      } else {
         fmt.Println("程序狠正常~~~")
      }
   }()

   result := num1 / num2

   // 重要说明:当上面出现除数为零的情况时,下面的打印操作是不会执行的
   fmt.Printf("%+v / %+v = %+v \n", num1, num2, result)
}

func main() {
   divide(1024, 0)

   // 重要说明:由于divide()函数使用defer和recover()捕获了panic,所以即使除数为零也不会中止程序执行
   fmt.Println("后续逻辑……") // 后续逻辑……
}



  函数多层嵌套下使用defer-recover()示例:

package main

import (
   "errors"
   "fmt"
)

func func1(num1 int64, num2 int64) (result int64, err error) {
   defer func() {
      rec := recover()
      if rec != nil {
         value, ok := rec.(error) // 断言rec变量为error类型
         if ok {
            err = value
         } else {
            err = errors.New("未知错误")
         }
      }
   }()

   result, err = func2(num1, num2)
   return
}

func func2(num1 int64, num2 int64) (result int64, err error) {
   result, err = func3(num1, num2)
   return
}

func func3(num1 int64, num2 int64) (result int64, err error) {
   result = num1 / num2 // 重要说明:当num2为0时会发生panic,并被func1()的defer-recover()捕获
   err = nil
   return result, nil
}

func main() {
   num1 := int64(1024)
   num2 := int64(0)
   result, err := func1(num1, num2)
   if err != nil {
      fmt.Println("程序粗错辣~~~", err) // 程序粗错辣~~~ runtime error: integer divide by zero
   } else {
      fmt.Printf("%d / %d = %d \n", num1, num2, result)
   }
}

// ========== 说明 ========== //
// 1、defer-recover()可以捕获其所在调用链里任意位置发生的错误,如上面虽然是在func1()里写defer-recover(),
//    但是func3()里发生的错误也能捕获到,因为func1()、func2()、func3()是在一个调用链内。
//    正因为defer-recover()有效范围是整个调用链,所以一般只需在调用链顶层实现捕获错误功能即可,不需要到处写defer-recover()。



  关于协程的defer-recover()

package main

import (
   "fmt"
   "time"
)

func main() {
   // 创建父协程
   go func(num1 int64, num2 int64) {
      // 重要说明:父协程里的defer-recover()不能捕获子协程的错误
      defer func() {
         err := recover()
         if err != nil {
            fmt.Println("程序粗错辣~~~", err)
         }
      }()

      // 创建子协程
      go func(num1 int64, num2 int64) {
         fmt.Println(num1 / num2) // panic: runtime error: integer divide by zero
      }(num1, num2)
   }(int64(1024), int64(0))

   time.Sleep(time.Second * 3) // 休眠3秒钟,让协程有充足时间执行
}

// ========== 说明 ========== //
// 1、不管是父子协程还是兄弟协程,每个协程有自己的栈空间和执行路径,也就是每个协程是一个单独的调用链,
//    协程的defer-recover()有效范围仅限自身,不会影响父子或兄弟,即每个协程要写自己的defer-recover()。

Copyright © 2024 码农人生. All Rights Reserved