048-panic 异常

2023年1月27日13:26:38

大多数编程语言都提供了异常处理机制,恰恰相反,Go 延续了 C 语言的风格,并未提供异常处理机制。但在 Go 里,提供了 panic 异常,从某种意义上说,它也非常接近其它语言的异常处理。

1. panic 异常

Go 语言在编译期就能捕获大量异常,但是有些异常只会发生在运行期。典型比如运行期的除 0 错误,数组越界错误。一旦发生这种错误,程序就会引发 panic 异常导致 crash.

然而上面这种错误通过加一些判断就可以避免的,也就是说,大多数 panic 异常都是程序员编码时的错误导致的。

除了程序本身的问题导致 panic 异常外,也可以直接在程序里调用 panic 函数引发 panic 异常。panic 函数原型如下:

// 直接调用它就会引发程序 panic 异常。
func panic(v interface{})

下面这个例子会引发 panic 异常:

package main

import "fmt"

func f(x int) {
    fmt.Printf("f(%d)\n", x+0/x) // 当参数为 0 时,引发 panic 异常
    defer fmt.Printf("%d\n", x)
    f(x - 1)
}

func main() {
    f(3)
    fmt.Println("end")
}

当然了,这是一个不好的例子,程序员自己没有对输入做正常的判断。不过为了引发 panic 异常,我们也就拼了吧。



048-panic 异常
图1 除 0 引发的 panic 异常

这个程序能很好说明 panic 异常发生时,程序会中断运行,并执行发生 panic 异常中的所有 deferred 函数。当程序执行 f(0) 时,引发除 0 错误,此时外层调用 f(0) 的函数f(1) 里会执行 defer,当然了,panic 异常会一直向外传递,直到所有的 deferred 函数全部执行完。

2. recover 函数

如果程序 panic 异常了,也有办法不让程序退出,从错误中恢复过来,具体方法就是使用 recover 函数。recover 函数非常像其他语言的 try … catch 中的 catch 部分,它能捕获到程序中的 panic 异常。相对而言,panic 就有点像 throw error 那个部分。

具体的说:如果程序引发了 panic 异常,只要再调用一下 recover 函数,程序就能从错误中恢复。这从侧面中反映了一个事实,要想在程序发生 panic 后还能执行 recover 函数,那就只能在 deferred 函数中调用 recover 了。

当 recover 返回 nil 时,表明程序没有发生任何异常,否则会返回 error.

仍然使用前面的例子:

package main

import "fmt"

func f(x int) {
    // 类似 try catch 里的 catch
    defer func() {
        if p := recover(); p != nil {
            fmt.Printf("%v\n", p)
        }
    }()

    fmt.Printf("f(%d)\n", x+0/x)
    defer fmt.Printf("%d\n", x)
    f(x - 1)
}

func main() {
    f(3)
    fmt.Println("end")
}



048-panic 异常
图2 recover 函数让程序从错误中恢复

然而,大多数情况,你不应该试图去恢复 panic 错误。

3. 使用 panic 还是返回 error ?

你写的代码,如果遇到错误,应该调用 panic 函数,还是返回 error ? 还这是大多数程序员纠结的话题。

其实在第 1 节中我们就说了,大多数 panic 异常,都是由于程序员的失误导致的。这一类错误往往可以通过一些手段避免。举几个例子:

  • 数组越界,可以避免。在访问数组下标时,应该保证下标不超出数组索引范围。
  • 除 0 错误,这种是可以避免的。
  • 正则表达式编写错误。如果程序员编辑了一个不合法的正则表达式,导致正则表达式引擎 compile 会发生 panic 异常。这种完全也是因为程序员粗心导致的错误,不应该容忍。
  • template 编写错误,我们刚学过《模板渲染》。如果你的 template 写的有问题,也应该引发 panic 异常。

当然还有一些其它的错误,比如,你明知道传 nil 参数会导致出问题,却传递 nil 给某个函数,这类你也是可以避免的。

还有另一类错误,这类错误是程序员所控制不了的。例如:

  • 请求网络 IO 错误,比如建立 tcp 连接超时、dns 解析错误等。
  • 打开文件错误,比如文件不存在。因为你根本不知道你打开的文件是不是被某人删除了。

你这一类程序员无法控制的错误,一般都通过返回 error 来告诉外面的调用者。

当然,这不是法则。

4. 总结

  • 知道程序何时会引发 panic 异常
  • 了解 recover 函数使用方法
  • 作者:--Allen--
  • 原文链接:https://allen.blog.csdn.net/article/details/80385271
    更新时间:2023年1月27日13:26:38 ,共 1810 字。