mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
4.2 KiB
4.2 KiB
Panic 与 Recover 模式
来源:Effective Go
Panic 指南
panic 创建一个运行时错误来停止程序。仅在真正不可恢复的情况下使用。
何时 Panic
真正的库函数应避免 panic。如果问题可以被掩盖或绕过,让程序继续运行,而不是让整个程序崩溃。
// 可接受:真正不可能的情况
func CubeRoot(x float64) float64 {
z := x/3
for i := 0; i < 1e6; i++ {
prevz := z
z -= (z*z*z-x) / (3*z*z)
if veryClose(z, prevz) {
return z
}
}
// 百万次迭代仍未收敛;出了问题。
panic(fmt.Sprintf("CubeRoot(%g) did not converge", x))
}
初始化中的 Panic
例外:如果库在 init() 期间确实无法完成初始化,panic 可能是合理的:
var user = os.Getenv("USER")
func init() {
if user == "" {
panic("no value for $USER")
}
}
何时 Panic 是可接受的
除了初始化之外,panic 在以下窄泛场景中是可接受的:
- API 误用——类似于核心语言对越界访问的 panic。
reflect包使用了这种方法。 - 带有匹配
recover的内部实现细节在包边界处。Panic 简化了深层嵌套的控制流,而公共 API 仍然返回 error(下方的 Parse/parseInt 模式)。 panic("unreachable")在log.Fatal之后,当编译器无法检测到不可达代码时。
Parse/parseInt 模式
在内部使用 panic 来回退复杂的递归,但始终在包边界处转换为 error:
func parseInt(in string) int {
n, err := strconv.Atoi(in)
if err != nil {
panic(&syntaxError{"not a valid integer"})
}
return n
}
func Parse(in string) (_ *Node, err error) {
defer func() {
if p := recover(); p != nil {
sErr, ok := p.(*syntaxError)
if !ok {
panic(p) // 不是我们的——重新 panic
}
err = fmt.Errorf("syntax error: %v", sErr.msg)
}
}()
// ... 内部调用 parseInt
}
关键:类型检查 p.(*syntaxError) 确保只捕获我们的 panic。意外的 panic(nil 指针等)正常传播。
Recover 模式
recover 重新获得对正在 panic 的 goroutine 的控制。它只在延迟函数中有效。
基本恢复模式
func safelyDo(work *Work) {
defer func() {
if err := recover(); err != nil {
log.Println("work failed:", err)
}
}()
do(work)
}
服务器 Goroutine 保护
在服务器中将 panic 隔离到各个 goroutine:
func server(workChan <-chan *Work) {
for work := range workChan {
go safelyDo(work) // 每个 worker 都受保护
}
}
如果 do(work) panic,结果会被记录,goroutine 干净退出而不影响其他 goroutine。
包内部的 Panic/Recover
在内部使用 panic 但在 API 边界处转换为 error:
// Error 是一个解析错误类型
type Error string
func (e Error) Error() string { return string(e) }
// 内部:使用 Error 类型 panic
func (regexp *Regexp) error(err string) {
panic(Error(err))
}
// 外部 API:将 panic 转换为 error 返回
func Compile(str string) (regexp *Regexp, err error) {
regexp = new(Regexp)
defer func() {
if e := recover(); e != nil {
regexp = nil
err = e.(Error) // 如果不是我们的 Error 类型则重新 panic
}
}()
return regexp.doParse(str), nil
}
要点:
- 延迟函数可以修改命名返回值
- 类型断言
e.(Error)对意外错误类型重新 panic - 绝不向客户端暴露 panic——始终在 API 边界处转换
快速参考
| 模式 | 描述 |
|---|---|
| 基本恢复 | defer func() { if err := recover(); err != nil { ... } }() |
| 服务器保护 | 将每个 goroutine 处理器包装在 safelyDo 中 |
| 包内部 | 内部 panic,在 API 边界处 recover 并返回 error |
| 类型安全恢复 | 使用类型断言对意外错误重新 panic |
何时使用
- Panic:仅用于真正不可恢复的情况或初始化失败
- Recover:服务器处理器、包内部错误简化
- 绝不:跨包边界暴露 panic——始终转换为 error