Panic、Defer 和 Recover
本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 57 课,共 107 课。
虽然 Go 强调返回错误,但有些情况确实无法恢复。针对这些情况,Go 提供了 panic、defer 和 recover,这是一种处理特殊情况的机制。
panic 会立即停止正常执行。它专用于访问越界索引等编程错误,而不用于无效用户输入等预期会发生的错误:
func MustGetConfig(key string) string {
value, exists := config[key]
if !exists {
panic("missing required config: " + key)
}
return value
}defer 会安排一个函数在外层函数返回时运行,无论是正常返回还是因 panic 而返回。延迟调用会按相反的顺序执行(后进先出):
func ProcessFile() {
fmt.Println("Opening file")
defer fmt.Println("Closing file")
fmt.Println("Processing...")
// 输出:Opening file, Processing..., Closing file
}recover 捕获 panic 并恢复正常执行。它只能在延迟函数中使用:
func SafeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
riskyOperation()
return nil
}这种模式可以将 panic 转换为常规错误,让程序继续运行。请谨慎使用:对于预期会发生的失败情况,优先返回错误;只有在状态损坏或无法恢复等真正特殊的情况下,才使用 panic/recover。
挑战
简单让我们构建一个安全的除法计算器,演示如何使用 panic、defer 和 recover 优雅地处理异常情况。计算器会尝试执行有风险的操作,并将 panic 转换为可以正常处理的常规错误。
你需要将代码分成两个文件:
calculator.go:编写除法逻辑,并在发生 panic 时进行恢复。实现一个
Divide函数,接收两个整数。如果除数为零,就使用消息"division by zero"触发 panic。否则,返回除法的整数结果。实现一个
SafeDivide函数,接收两个整数,并同时返回一个整数结果和一个错误。这个函数应该:- 使用
defer配合匿名函数,从任何 panic 中恢复 - 如果恢复了 panic,就将其转换为错误,格式为
"calculation error: [panic message]" - 在内部调用
Divide函数 - 如果成功,就返回结果和
nil;如果恢复了 panic,则返回零和错误
- 使用
main.go:从输入中读取两个整数,并使用SafeDivide安全地进行计算。还要通过按正确顺序打印清理消息来演示defer的用法。你的主函数应该:
- 在开头打印
Starting calculation - 使用
defer安排打印Cleanup complete - 再使用一个
defer安排打印Releasing resources - 使用输入的值调用
SafeDivide - 根据结果打印
Result: [value]或Error: [error message]
- 在开头打印
输入内容如下:
- 第 1 行:第一个整数(被除数)
- 第 2 行:第二个整数(除数)
请记住,延迟调用会按相反的顺序执行(后进先出),因此清理消息的显示顺序应与安排它们的顺序相反。
例如,输入 20 和 4 时,输出应为:
Starting calculation
Result: 5
Releasing resources
Cleanup complete输入 10 和 0 时,输出应为:
Starting calculation
Error: calculation error: division by zero
Releasing resources
Cleanup complete亲手试试
package main
import (
"fmt"
)
func main() {
// 读取输入
var dividend, divisor int
fmt.Scanln(÷nd)
fmt.Scanln(&divisor)
// 打印开始消息
fmt.Println("Starting calculation")
// TODO:使用 defer 安排输出 "Cleanup complete" 消息
// TODO:使用 defer 安排输出 "Releasing resources" 消息
// 记住:延迟调用按 LIFO 顺序执行(后进先出)
// TODO:用输入值调用 SafeDivide
// TODO:打印 "Result: [value]" 或 "Error: [error message]"
// 具体取决于是否返回错误
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Go 在线编译器