Go - panic
原则
- 不要相信任何
外部输入
的参数- 函数需要对所有输入的参数进行
合法性
的检查 - 一旦发现问题,
立即终止
函数的执行,返回预设的错误值
- 函数需要对所有输入的参数进行
- 不要忽略任何一个
错误
显式检查
这些函数调用返回的错误值- 一旦发现错误,要及时终止函数执行,防止错误继续传播
- 不要假定
异常
不会发生异常不是错误
- 错误是
可预期
的,也会经常发生,有对应的公开错误码和错误处理方案 - 异常是
不可预期
的,通常指的是硬件异常、操作系统异常、语言运行时异常、代码 Bug(数组越界访问)等
- 错误是
- 异常是
小概率
事件,但不能假定异常不会发生- 根据函数的角色和使用场景,考虑是否要在函数内设置
异常捕获
和恢复
的环节
- 根据函数的角色和使用场景,考虑是否要在函数内设置
panic
在 Go 中,由
panic
来表达异常
的概念
- panic 指的是 Go 程序在
运行时
出现的一个异常情况- 如果异常出现了,但没有被
捕获
并恢复
,则 Go 程序的执行会被终止
- 即便出现异常的位置不在
主 goroutine
- 如果异常出现了,但没有被
- panic 来源:
Go 运行时
/ 开发者通过panic
函数主动触发
- 当 panic 被触发,后续的执行过程称为
panicking
手动调用
panic
函数,主动触发panicking
- 当函数 F 调用 panic 函数,函数 F 的执行将
停止
- 函数 F 中已进行求值的
deferred
函数都会得到正常执行
- 执行完所有
deferred
函数后,函数 F 才会把控制权
返回给其调用者
- 函数 F 的调用者
- 函数 F 之后的行为就如同调用者
自己调用 panic 函数
一样 -递归
- 函数 F 之后的行为就如同调用者
- 该
panicking
过程将继续在栈上
进行,直到当前 goroutine
中的所有函数
都返回为止 - 最后 Go 程序
崩溃退出
1 | package main |
recover
recover 是 Go 内置的专门用于
恢复 panic
的函数,必须被放在一个defer
函数中才能生效
1 | package main |
defer
函数类似于function close hook
每个函数都 defer + recover:
心智负担
+性能开销
经验
评估程序对 panic 的
忍受度
,对于关键系统
,需要在特定位置捕获
并恢复
panic,以保证服务整体的健壮性
- Go http server,每个
客户端连接
都使用一个单独的 goroutine
进行处理的并发处理模型 - 客户端一旦与 http server 连接成功,http server 就会为这个连接新建一个 goroutine
- 并在该 goroutine 中执行对应连接的 serve 方法,来处理这条连接上客户端请求
- panic 危害
- 无论哪个 goroutine 中发生
未被恢复
的 panic,整个 Go 程序
都将崩溃退出
- 无论哪个 goroutine 中发生
- 需要保证某一客户端连接的 goroutine 出现 panic 时,不影响 http server
主 goroutine
的运行- serve 方法在一开始就设置了 defer 匿名函数,在 defer 匿名函数中
捕获
并恢复
了可能出现的 panic
- serve 方法在一开始就设置了 defer 匿名函数,在 defer 匿名函数中
并发
程序的异常处理策略:局部不影响整体
1 | // Serve a new connection. |
提示
潜在的 Bug
- 触发了非预期
的执行路径
在 Go标准库
中,大多数panic
的使用都是充当类似断言
的作用
1 | // phasePanicMsg is used as a panic message when we end up with something that |
1 | func (w *reflectWithString) resolve() error { |
不要混淆
异常
和错误
- 在 Java 标准类库中的
checked exception
,类似于 Go 的哨兵错误值
- 都是
预定义
的,代表特定场景
下的错误状态
- 都是
- Java checked exception 用于一些
可预见
的,经常发生的错误场景- 针对 checked exception 所谓的异常处理,本质上是针对这些场景的
错误处理预案
- 即对 checked exception 的定义、使用、捕获等行为都是
有意而为之
- 必须要被
上层代码
处理:捕获 or 重新抛给上层
- 针对 checked exception 所谓的异常处理,本质上是针对这些场景的
- 在 Go 中,通常会引入大量第三方包,而
无法确定
这些第三方 API 包中是否会引发panic
- API 的使用者不会逐一了解 API 是否会引发 panic,也没有义务去处理引发的 panic
- 一旦 API 的作者
将异常当成错误
,但又不强制 API 使用者处理,会引入麻烦 - 因此,不要将
panic
当成error
返回给 API 的调用者,大部分应该返回error
,即 Java checked exception
Java | Go |
---|---|
Checked Exception |
error |
RuntimeException / Error |
panic |
Java 发生 RuntimeException,JVM 只会停止对应的线程;而 Go 发生 panic,会整个程序崩溃
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.