Tian Jiale's Blog

Go 的异常处理

前言

Go 语言追求简洁优雅,所以,Go 语言不支持传统的 try … catch … finally 这种异常,因为 Go 语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱。因为开发者很容易滥用异常,甚至一个小小的错误都抛出一个异常。在 Go 语言中,使用多值返回来返回错误。不要用异常代替错误,更不要用来控制流程。在极个别的情况下,也就是说,遇到真正的异常的情况下(比如除数为 0 了)。才使用 Go 中引入的 Exception 处理:defer, panic, recover。

这几个异常的使用场景可以这么简单描述:Go 中可以抛出一个 panic 的异常,然后在 defer 中通过 recover 捕获这个异常,然后正常处理。

代码示例

package main

import "fmt"

func main() {
  // 必须要先声明 defer,否则不能捕获到 panic 异常
  // 解释:出现异常后直接跳过 return,执行 defer 代码
  defer func() {
    fmt.Println("d")
    if err := recover(); err != nil {
      fmt.Println(err)
    }
  fmt.Println("e")
  }()
  fmt.Println("a")
  panic(55)
  fmt.Println("b")
  fmt.Println("c")
}

输出:

a
d
55
e

defer

defer 的意思是延迟,意为其后的代码延迟执行,在 Go 中,defer 的代码将在 panic 和 return 后执行;类似于 C++的析构函数,在函数结束后执行,但 defer 语句是动态的,在上面那个例子中,如果 panic 出现在 defer 之前,defer 语句并未被执行也就没有相应的错误处理。

defer 的三大特性:

  1. defer 语句声明时,其参数立即求值

    A deferred function’s arguments are evaluated when the defer statement is evaluated

  2. defer 函数在其后代码执行完之后按照先进后出的顺序执行

    Deferred function calls are executed in Last In First Out order after the surrounding function returns.

  3. defer 语句可读取并修改函数的命名返回值

    Deferred functions may read and assign to the returning function’s named return values.

defer 的其他使用场景:

  1. 释放一个信号量

      mu.Lock()
      defer mu.Unlock()
    
  2. 输出底部信息

      printHeader()
      defer printFooter()
    

panic

Panic 是一个内置的函数,它停止普通的控制流并开始恐慌。当函数 F 调用 panic 时,F 的执行停止,F 中的任何延迟函数被正常执行,然后 F 返回给它的调用者。对于调用者来说,F 的行为就像对 panic 的调用。这个过程继续在堆栈中进行,直到当前 goroutine 中的所有函数都返回,这时程序就会崩溃。除非在 defer 函数中调用了 recover 函数处理了 panic,这时恐慌将不会继续向上传递。恐慌可以通过直接调用 panic 来启动。它们也会由运行时错误引起,如越界数组访问。

recover

recover 是一个内置的函数,可以重新获得对一个恐慌的 goroutine 的控制。Recover 只在延迟函数中有用。在正常执行过程中,对 recover 的调用将返回 nil,没有其他影响。如果当前的 goroutine 处于恐慌状态,对 recover 的调用将捕获给定的恐慌值并恢复正常执行。