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 ...
随机推荐
- Struts2中的JSON问题——后台返回JSON字符串到前台
最近做一个项目遇到一个比较棘手的问题,项目后台采用struts2+Hibernate3+Spring3,前台采用ExtJs4.笔者目前仍是一名大二学生吗,后台框架完全是毫无任何基础,从零学,现学现用. ...
- 【PHP】PHP从入门到精通(一)——想学习PHP的小伙伴的福利来了!
PHP从精通到入门 (一)PHP简介和基本知识 PHP(外文名:PHP: Hypertext Preprocessor,中文名:"超文本预处理器")是一种通用开源脚本语言.语法吸 ...
- Nodejs 进阶:Express 常用中间件 body-parser 实现解析
本文摘录自<Nodejs学习笔记>,更多章节及更新,请访问 github主页地址.欢迎加群交流,群号 197339705. 写在前面 body-parser是非常常用的一个express中 ...
- 3、J2EE学习推荐书籍
3.J2EE学习推荐书籍 J2EE的学习应该循序渐进,一本好书会很快上手和深入.在学习J2EE之前,应该学好SQL,基本上,程序设计都会跟数据库打交道.如果SQL没学好,就如同房子没有基脚 ...
- 使用Dockerfile构建镜像-Docker for Web Developers(5)
1.理解Dockerfile语法 语法命令 命令功能 举例 FROM 所有的dockerfile都必须以FROM命令指定镜像基于哪个基础镜像来制作 FROM ubuntu:14:04 MAINTAIN ...
- redis 清空缓存
redis 清空缓存 Redis 命令: flushall --> 清空整个 Redis 服务器的数据(删除所有数据库的所有 key ) flushdb --> 清空当前数据库中的所有 k ...
- Http学习之使用HttpURLConnection发送post和get请求(1)
最常用的Http请求无非是get和post,get请求可以获取静态页面,也可以把参数放在URL字串后面,传递给servlet,post与get的不同之处在于post的参数不是放在URL字串里面,而是放 ...
- Android 内存泄漏分析与解决方法
在分析Android内存泄漏之前,先了解一下JAVA的一些知识 1. JAVA中的对象的创建 使用new指令生成对象时,堆内存将会为此开辟一份空间存放该对象 垃圾回收器回收非存活的对象,并释放对应的内 ...
- Git添加远程库和从远程库中获取(新手傻瓜式教学)
一. Git添加远程库 1.在本地新建一个文件夹,在该文件夹使用Git工具,运行$ git init,将该文件夹变为本地Git仓库,同时会生成一个隐藏的.git文件夹. 2.在该文件夹中用Not ...
- zabbix监控redis
导入监控模板 点击[configuration]-->[templates]-->[import],导入xml监控模板. 配置客户端key 在被监控的主机上,新建/etc/zabbix/z ...