panic
panic
_, err := os.Create("/tmp/file")
if err != nil {
panic(err)
}
该函数接受一个任意类型的实参(一般为字符串
// 用牛顿法计算立方根的一个玩具实现。
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))
}
实际的库函数应避免
var user = os.Getenv("USER")
func init() {
if user == "" {
panic("no value for $USER")
}
}
Recover | 恢复
当函数
func main(){
defer func(){
if r := recover();r != nil{
fmt.Println(r)
}
}()
panic([]int{12312})
}
当
调用
局部容错
func server(workChan <-chan *Work) {
for work := range workChan {
go safelyDo(work)
}
}
func safelyDo(work *Work) {
defer func() {
if err := recover(); err != nil {
log.Println("work failed:", err)
}
}()
do(work)
}
在此例中,若
避免在非defer 语句中调用recover
在非defer
语句中执行recover
调用是初学者常犯的错误
func main() {
if r := recover(); r != nil {
log.Fatal(r)
}
panic(123)
if r := recover(); r != nil {
log.Fatal(r)
}
}
上面程序中两个recover
调用都不能捕获任何异常。在第一个recover
调用执行时,函数必然是在正常的非异常执行流程中,这时候recover
调用将返回nil
。发生异常时,第二个recover
调用将没有机会被执行到,因为panic
调用会导致函数马上执行已经注册defer
的函数后返回。
其实recover
函数调用有着更严格的要求:我们必须在defer
函数中直接调用recover
。如果defer
中调用的是recover
函数的包装函数的话,异常的捕获工作将失败!比如,有时候我们可能希望包装自己的MyRecover
函数,在内部增加必要的日志信息然后再调用recover
,这是错误的做法:
func main() {
defer func() {
// 无法捕获异常
if r := MyRecover(); r != nil {
fmt.Println(r)
}
}()
panic(1)
}
func MyRecover() interface{} {
log.Println("trace...")
return recover()
}
同样,如果是在嵌套的defer
函数中调用recover
也将导致无法捕获异常:
func main() {
defer func() {
defer func() {
// 无法捕获异常
if r := recover(); r != nil {
fmt.Println(r)
}
}()
}()
panic(1)
}
defer
函数中直接调用recover
和defer
函数中调用包装的MyRecover
函数一样,都是经过了recover
函数,这个时候
如果我们直接在defer
语句中调用MyRecover
函数又可以正常工作了:
func MyRecover() interface{} {
return recover()
}
func main() {
// 可以正常捕获异常
defer MyRecover()
panic(1)
}
但是,如果defer
语句直接调用recover
函数,依然不能正常捕获异常:
func main() {
// 无法捕获异常
defer recover()
panic(1)
}
必须要和有异常的栈帧只隔一个栈帧,recover
函数才能正常捕获异常。换言之,recover
函数捕获的是祖父一级调用函数栈帧的异常(刚好可以跨越一层defer
函数
当然,为了避免recover
调用者不能识别捕获到的异常nil
为参数抛出异常
func main() {
defer func() {
if r := recover(); r != nil { ... }
// 虽然总是返回nil, 但是可以恢复异常状态
}()
// 警告: 用`nil`为参数抛出异常
panic(nil)
}
当希望将捕获到的异常转为错误时,如果希望忠实返回原始的信息,需要针对不同的类型分别处理:
func foo() (err error) {
defer func() {
if r := recover(); r != nil {
switch x := r.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = fmt.Errorf("Unknown panic: %v", r)
}
}
}()
panic("TODO")
}
基于这个代码模板,我们甚至可以模拟出不同类型的异常。通过为定义不同类型的保护接口,我们就可以区分异常的类型了:
func main {
defer func() {
if r := recover(); r != nil {
switch x := r.(type) {
case runtime.Error:
// 这是运行时错误类型异常
case error:
// 普通错误类型异常
default:
// 其他类型异常
}
}
}()
// ...
}
不过这样做和
适当的异常处理
通过恰当地使用恢复模式,
// Error 是解析错误的类型,它满足 error 接口。
type Error string
func (e Error) Error() string {
return string(e)
}
// error 是 *Regexp 的方法,它通过用一个 Error 触发Panic来报告解析错误。
func (regexp *Regexp) error(err string) {
panic(Error(err))
}
// Compile 返回该正则表达式解析后的表示。
func Compile(str string) (regexp *Regexp, err error) {
regexp = new(Regexp)
// doParse will panic if there is a parse error.
defer func() {
if e := recover(); e != nil {
regexp = nil // 清理返回值。
err = e.(Error) // 若它不是解析错误,将重新触发Panic。
}
}()
return regexp.doParse(str), nil
}
若
通过适当的错误处理,
if pos == 0 {
re.error("'*' illegal at start of expression")
}
尽管这种模式很有用,但它应当仅在包内使用。
顺便一提,这种重新触发