panic和recover的使用规则
转自个人博客 chinazt.cc
在上一节中,我们介绍了defer的使用。 这一节中,我们温习一下panic和recover的使用规则。
在golang当中不存在tye ... catch 异常处理逻辑。在golang当中使用defer, panic和recover来控制程序执行流程,借此来达到处理异常的目的。
Panic是一个可以停止程序执行流程的内置函数。 假设当前F函数当中某处代码触发panic函数,则F函数停止后面代码的执行,转而执行F函数内部的defer函数(如果已经声明了defer函数的话...),然后结束F函数,将当前处理权转给F的调用函数。
对于F的调用方M来说,F是调用panic函数结束的,而不是执行return结束的。这一点很重要,因为调用panic函数结束是没有返回值的。
对于F的调用方M来说,F调用结束并且已经退出。所以引出第一个规则:
调用panic后,调用方函数执行从当前调用点退出
我们通过下面的代码来解释这个问题:
package main
import "fmt"
func main() {
f()
fmt.Println("Returned normally from f.")
}
func f() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
fmt.Println("Calling g.")
g(0)
fmt.Println("Returned normally from g.")
}
func g(i int) {
if i > 3 {
fmt.Println("Panicking!")
panic(fmt.Sprintf("%v", i))
}
defer fmt.Println("Defer in g", i)
fmt.Println("Printing in g", i)
g(i + 1)
}
这段函数里面,当i大于3之后,触发panic函数。 在i小于3之前,一切正常。 引出我们来分析一下执行逻辑。
首先f()里面,没有调用g()函数之前,没有异常。因此会输出"Calling g."。而后开始调用g(),当输入的参数小于3之前,会一直输出"Printing in g",(为什么不输出"Defer in g"请参考上一篇文章 (defer的使用规则))。
当i4时,开始触发panic函数,输出Panicking!。需要记住panic之后,调用方直接从调用点退出。 因为g()里面是迭代调用,因此当i4时触发panic时,本质是g(i+1)这一行触发的panic函数。 因此g()后续的函数不再继续执行,因为存在defer函数了,所以连续输出三个"Defer in g"。
此时g(0)函数执行完毕,f()函数退出。因为f()函数里面存在defer函数,因此会调用defer 输出"Recoverd in f"。
所以最终的输出结果将是:
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.
聊完执行顺序的问题后,我们来看为什么会输出"Recovered in f 4". 这就是规则二:
规则二 通过panic可以设定返回值
panic存在的意义,不仅可以控制异常处理流程,还可以用来返回异常原因。如果panic不给调用方返回异常原因,那么调用方就无从下手处理问题。 因此在调用panic时,一般来说都是返回一个字符串,用来标示失败原因。例如上面代码中的"XXX值不得大于3"。
panic的返回值,通过recover函数来获取。 recover函数也是一个内置函数,专门用来接收panic函数返回值。当panic函数没有被调用或者没有返回值时,recover返回Nil.
在使用recover函数时,就需要下面的规则三:
规则三 recover函数只有在defer代码块中才会有效果
注意此处的有效果不等于执行。言外之意,recover函数可以在任何地方执行,但只有在defer代码块中执行才能真正达到处理异常的目的。
我们假设,将recover函数移除defer代码块,如下:
func f() int {
fmt.Println("Calling g.")
m := g(0)
r := recover()
if r != nil {
fmt.Println("Recovered in f", r)
}
fmt.Println("Returned normally from g.", m)
return m
}
因此recover函数在外,因此执行结果如下:
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
panic: 4
goroutine 1 [running]:
[stack trace omitted]
可以看到recover函数并未生效,其实是没有执行,因为根据规则一,当前执行流程会跳转到defer函数中。因此只有将recover函数定义到defer之中才会真正被执行。
那么问题来了,recover函数应该定义在哪一级的defer中。 golang是逐级查找的,最终会查找到main函数。 如果main函数中的defer还没有recover函数,golang这会像上面那样抛出最终的异常信息。
本节内容不多,但个人感觉还都是干货。 毕竟我们不能保证自己的写的代码一定就没有问题,有问题不可怕,可怕的是代码不受控制。所以学好defer,panci和recover,有助于写出健壮的代码。
panic和recover的使用规则的更多相关文章
- GOLANG错误处理最佳方案errors wrap, Defer, Panic, and Recover
Simple error handling primitives: https://github.com/pkg/errors Defer, Panic, and Recover: ...
- Golang 高效实践之defer、panic、recover实践
前言 我们知道Golang处理异常是用error返回的方式,然后调用方根据error的值走不同的处理逻辑.但是,如果程序触发其他的严重异常,比如说数组越界,程序就要直接崩溃.Golang有没有一种异常 ...
- 【Go入门教程3】流程(if、goto、for、switch)和函数(多个返回值、变参、传值与传指针、defer、函数作为值/类型、Panic和Recover、main函数和init函数、import)
这小节我们要介绍Go里面的流程控制以及函数操作. 流程控制 流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑.Go中流程控制分三大类:条件判断,循环控制和 ...
- 【GoLang】panic defer recover 深入理解
唉,只能说C程序员可以接受go的错误设计,相比java来说这个设计真的很差劲! 我认为知乎上说的比较中肯的: 1. The key lesson, however, is that errors ar ...
- go语言中使用defer、panic、recover处理异常
go语言中的异常处理,没有try...catch等,而是使用defer.panic.recover来处理异常. 1.首先,panic 是用来表示非常严重的不可恢复的错误的.在Go语言中这是一个内置函数 ...
- Go基础系列:defer、panic和recover
defer关键字 defer关键字可以让函数或语句延迟到函数语句块的最结尾时,即即将退出函数时执行,即便函数中途报错结束.即便已经panic().即便函数已经return了,也都会执行defer所推迟 ...
- Golang错误处理函数defer、panic、recover、errors.New介绍
在默认情况下,当发生错误(panic)后,程序就会终止运行 如果发生错误后,可以捕获错误,并通知管理人员(邮件或者短信),程序还可以继续运行,这当然无可厚非 errors.New("错误信息 ...
- [日常] Go语言圣经-Panic异常,Recover捕获异常习题
Go语言圣经-Panic异常1.当panic异常发生时,程序会中断运行,并立即执行在该goroutine中被延迟的函数(defer 机制)2.不是所有的panic异常都来自运行时,直接调用内置的pan ...
- Golang异常处理-panic与recover
Golang异常处理-panic与recover 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 在程序设计中,容错是相当重要的一部分工作,在 Go中它是通过错误处理来实现的,err ...
随机推荐
- JS执行事件
先贴出几个名词: 同步任务: 在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务 异步任务: 不进入主线程,而进入"任务队列"的任务,只有任务队列通知主线程, ...
- Hibernate中调用带有underscore的Column Name
Hibernate中默认的NamingStrategy不支持调用带有下划线的column name.在hibernate的bean中必须使用camel case.使用ImprovedNamingStr ...
- 解决selenium驱动Firefox跳转页慢慢慢的问题
首先我给自己定义为是一个更新偏执狂.不知道从哪个版本开始,使用selenium驱动打开Firefox浏览器时,会跳转到官网指定页,这个过程真是慢得要死. 为了解决这个问题,我是查了很多资料,解决方案是 ...
- Javascript版-显示相应图片的详细信息
Hi All, 分享一个通过JS来显示相应图片的详细信息. 需求:进入页面时,动态加载图片信息:当鼠标移动到某一图片上时,则显示该图片的大图片并显示相应说明信息:当鼠标移开图片时,清除新创建的元素. ...
- Lock(一)认识v$LOCK
v$lock列出了数据库当前拥有的锁及未完成的锁请求. Column Description ADDR 被锁对象的地址 KADDR 锁的地址 SID session id(这里特指正在锁定对象或请求去 ...
- 分享一款在线less转css的神器
大多数web开发的程序员都了解和使用过Less, LESS是一门 CSS 预处理语言,它扩充了 CSS 语言,增加了诸如变量.混合(mixin).函数等功能,让 CSS 更易维护.方便制作 ...
- 源代码安装软件-MySQL
一.源码安装 1.经典的源代码安装三步曲: 1.编译前的配置 ./configure 2.编译 make 3.安装 make install 2.源代码软件安装步骤: 1.下载软件包 2.校验软件包 ...
- sql还原(.mdf文件还原)
第一步: 把备份文件放到目录:C:\Program Files\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\DATA (默认)(这里用 MyDB.md ...
- android 定时器(Handler Timer Thread AlarmManager CountDownTimer)
Android实现定时任务一般会使用以上(Handler Timer Thread AlarmManager CountDownTimer)五种方式.当然还有很多组合使用(比如Handler+Thre ...
- javascript痛点之三闭包
先来看看第一节的例子 'use strict'; function num(){ //用var声明一个变量num1 var num1 = 15; } alert(num1);//num1 is not ...