跳到主要内容

异常处理

defer 语句

在Go语言中,defer语句用于延迟函数的执行,直到包含它的函数返回时才执行。这意味着被 defer 修饰的函数调用会在当前函数的所有其他操作完成后执行,即使遇到 panic 或者直接返回,也会执行。

defer的主要特点:

  1. 延迟执行
    • defer语句的作用就是将一个函数调用延迟到所在函数的结束时执行
  2. 执行顺序
    • 如果一个函数中有多个defer语句,它们会按照后进先出的顺序执行。即最后一个defer语句最先执行,第一个defer语句最后执行
  3. 参数的评估时机
    • defer声明时,函数的参数会立即评估,但函数体不会执行。也就是延迟执行的函数会记住当时的参数值和环境,而不是在执行时再计算参数

使用场景:

  1. 资源释放
    • defer常用于在函数结束时自动执行一些清理操作,如关闭文件、释放锁、回滚事务等,确保资源被正确释放
  2. 异常处理
    • 在处理异常或错误恢复时,defer可以用来在panic之后执行一些恢复操作
  3. 函数退出的操作
    • 一些在函数退出时需要执行的操作,如日志记录、计时等
package main

import "fmt"

func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}

func main() {
x := 1
y := 2
defer calc("AA", x, calc("A", x, y)) // 其内函数会立即求值,calc("A",x,y)会先执行
x = 10
defer calc("BB", x, calc("B", x, y)) // 同理calc("B",x,y)也会先执行
y = 20
}

/* 输出结果:
A 1 2 3
B 10 2 12
BB 10 12 22
AA 1 3 4

本例中,calc("A", x, y) 和 calc("B", x, y) 的结果在 defer 语句声明时就已经计算并保存下来了,即使之后 x 和 y 的值发生了变化。
*/

panic和recover

在Go语言中,panic和recover是用于异常处理的两个关键字,提供了一种机制来应对程序中的严重错误或意外情况。

panic语句用于中止程序的正常执行。它通常在发生无法恢复的错误时使用,或者在程序遇到严重问题时调用。

  • 调用panic后,程序的控制流会立即终端,当前函数的执行也会停止
  • Go语言会沿着调用栈逐层向上传递panic,并在每一层执行defer语句
  • 如果panic传递到main函数,程序将崩溃并退出,并打印panic消息及堆栈跟踪

常用于捕获不可恢复的错误,如数组越界、空指针引用等。

recover是用来从panic中恢复程序执行的。它只在延迟函数(即defer中)内有效。

  • 当panic发生时,如果调用了recover,panic的传播就会停止,程序从引发panic的地方恢复正常执行
  • recover返回panic的值,如果没有panic发生,则返回nil

recover通常用于创建容错机制,使程序能够从不可恢复的错误中恢复过来,而不会崩溃。

在异常源头捕获:

package main

import "fmt"

func funcA() {
defer func() {
//如果程序出出现了panic错误,可以通过recover恢复过来
if err := recover(); err != nil {
fmt.Printf("%v\n", err)
}
}()
panic("出错了")
}

func main() {
fmt.Println("main函数开始执行")
funcA()
fmt.Println("main函数结束执行")
}

// 输出结果
main函数开始执行
出错了
main函数结束执行

如果不在异常抛出的源头-函数funcA()中捕获异常,而在其调用者中捕获,效果也完全相同。因为funcA()中抛出的异常会自动向上传播,所以在main()函数中同样可以捕获。

package main

import "fmt"

func funcA() {
panic("出错了")
}

func main() {
defer func() {
//如果程序出出现了panic错误,可以通过recover恢复过来
if err := recover(); err != nil {
fmt.Printf("%v\n", err)
}
}()

fmt.Println("main函数开始执行")
funcA()
fmt.Println("main函数结束执行")
}

// 输出结果
main函数开始执行
出错了

将defer+recover捕获异常的代码迁移到main()函数中。执行该代码段,同样能够实现异常捕获。但需要注意的是,一旦产生异常,同样会打断程序执行,并马上进入defer执行环节。因此,此处不会输出字符串“main函数结束执行”。

虽然在上层函数中同样可以捕获异常,但也要特别注意它与在异常源头捕获的区别。