panic、recover 与 defer
了解何时应触发 panic,以及如何优雅地恢复
panic、recover 与 defer 是 CoddyKit 上的免费 Go Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Go Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Go Academy 课程共包含 4 节课。
什么是 panic?
panic 会停止当前协程的正常执行,运行延迟函数并展开调用栈;如果没有恢复,程序就会崩溃:
package main
import "fmt"
func mustPositive(n int) int {
if n <= 0 {
panic(fmt.Sprintf("mustPositive: got %d", n))
}
return n
}
func main() {
fmt.Println(mustPositive(5)) // 5
// fmt.Println(mustPositive(-1)) // panics!
}何时使用 panic
仅在以下情况下使用 panic:
- 确实无法恢复的程序员错误(不变量遭到破坏)
- 表明存在缺陷的不可能情况
- 启动失败(缺少配置、初始化期间连接失败)
绝不要对预期的失败情况使用 panic — 请改用 error。
defer 基础
defer 会安排一个函数调用,在外围函数返回时执行,无论函数以何种方式返回(正常返回、错误返回或 panic):
package main
import "fmt"
func main() {
defer fmt.Println("third")
defer fmt.Println("second") // LIFO order
defer fmt.Println("first")
fmt.Println("running...")
// Output:
// running...
// first
// second
// third
}使用 defer 进行清理
defer 最常见的用途是确保资源得到清理:
package main
import ("fmt"; "os")
func readFile(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", fmt.Errorf("open: %w", err)
}
defer f.Close() // always runs, even if we return early
buf := make([]byte, 1024)
n, _ := f.Read(buf)
return string(buf[:n]), nil
}recover — 捕获 panic
recover 会停止 panic 并返回 panic 值。它必须直接在 defer 函数中调用:
package main
import "fmt"
func safeDiv(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
return a / b, nil // panics if b==0
}
func main() {
r, err := safeDiv(10, 0)
fmt.Println(r, err)
// 0 recovered from panic: runtime error: integer divide by zero
}recover 必须位于延迟函数中
recover 只有在延迟函数中直接调用时才会生效。在其他位置调用时会返回 nil:
package main
import "fmt"
func wrong() {
// recover() here does nothing — not in defer
}
func correct() {
defer func() {
if r := recover(); r != nil {
fmt.Println("caught:", r)
}
}()
panic("oops")
}
func main() { correct() }库代码中的 panic
库代码几乎绝不应让 panic 逃逸到调用方。请在边界处将 panic 转换为错误:
package main
import ("fmt"; "runtime/debug")
func safeCall(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v\n%s", r, debug.Stack())
}
}()
fn()
return nil
}
func main() {
err := safeCall(func() { panic("boom") })
fmt.Println(err != nil) // true
}defer 的 LIFO 顺序与循环陷阱
延迟调用会按照后进先出(LIFO)的顺序执行。在循环中使用 defer 时请格外小心 — 请使用闭包或辅助函数:
package main
import "fmt"
func processFiles(names []string) {
for _, name := range names {
name := name // capture loop variable
defer func() { fmt.Println("close:", name) }()
}
}
func main() {
processFiles([]string{"a.txt", "b.txt", "c.txt"})
// Output (LIFO): close: c.txt, close: b.txt, close: a.txt
}defer 与命名返回值
延迟函数可以读取和修改命名返回值,这对于为错误添加注释很有用:
package main
import "fmt"
func doWork() (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("doWork: %w", err)
}
}()
return fmt.Errorf("something failed")
}
func main() {
err := doWork()
fmt.Println(err) // doWork: something failed
}panic 与错误:决策指南
如何在 panic 和错误之间进行选择:
- 使用错误:预期的失败(文件未找到、网络超时、输入无效)
- 使用 panic:程序错误(访问 nil 映射、越界)、不可能的状态、初始化失败
- 绝不要让 panic 跨越 API 边界 — 调用方无法处理它
- 在服务器的协程顶层恢复 panic,以避免程序崩溃
快速检查
必须在哪里调用 recover() 才能捕获 panic?
回顾:panic、recover 与 defer
总结:
panic:展开调用栈,运行延迟函数;如果未恢复则导致程序崩溃defer:在函数退出时运行(正常退出、发生错误或 panic);按 LIFO 顺序执行recover:停止 panic 并返回其值;必须在defer中使用- 请将 panic 用于程序员错误,而不是预期中的失败
- 在 API 边界处将 panic 转换为 error
用 AI 导师学习 Go — 免费
在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。
- 课程
- 51
- 课程
- 203
常见问题解答
「panic、recover 与 defer」课时是免费的吗?
是的 — 「panic、recover 与 defer」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Go Academy 课程的其余内容,请升级到 CoddyKit PRO。 Go Academy 课程共包含 4 节课。
「panic、recover 与 defer」这节课中我会学到什么?
了解何时应触发 panic,以及如何优雅地恢复 你通过在浏览器中直接运行的动手代码来练习 Go Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Go Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Go Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「panic、recover 与 defer」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Go Academy 课中编写并运行代码吗?
能。每节 Go Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。