Go Academy · 课时

panic、recover 与 defer

了解何时应触发 panic,以及如何优雅地恢复

第 4 / 4 课12 个步骤

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 反馈 — 无需本地设置。

此课程中的所有课时

  1. error 接口
  2. 创建自定义错误
  3. 错误包装与拆包
  4. panic、recover 与 defer
← 返回 Go Academy