转自个人博客 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的使用规则的更多相关文章

  1. GOLANG错误处理最佳方案errors wrap, Defer, Panic, and Recover

    Simple error handling primitives:        https://github.com/pkg/errors Defer, Panic, and Recover:    ...

  2. Golang 高效实践之defer、panic、recover实践

    前言 我们知道Golang处理异常是用error返回的方式,然后调用方根据error的值走不同的处理逻辑.但是,如果程序触发其他的严重异常,比如说数组越界,程序就要直接崩溃.Golang有没有一种异常 ...

  3. 【Go入门教程3】流程(if、goto、for、switch)和函数(多个返回值、变参、传值与传指针、defer、函数作为值/类型、Panic和Recover、main函数和init函数、import)

    这小节我们要介绍Go里面的流程控制以及函数操作. 流程控制 流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑.Go中流程控制分三大类:条件判断,循环控制和 ...

  4. 【GoLang】panic defer recover 深入理解

    唉,只能说C程序员可以接受go的错误设计,相比java来说这个设计真的很差劲! 我认为知乎上说的比较中肯的: 1. The key lesson, however, is that errors ar ...

  5. go语言中使用defer、panic、recover处理异常

    go语言中的异常处理,没有try...catch等,而是使用defer.panic.recover来处理异常. 1.首先,panic 是用来表示非常严重的不可恢复的错误的.在Go语言中这是一个内置函数 ...

  6. Go基础系列:defer、panic和recover

    defer关键字 defer关键字可以让函数或语句延迟到函数语句块的最结尾时,即即将退出函数时执行,即便函数中途报错结束.即便已经panic().即便函数已经return了,也都会执行defer所推迟 ...

  7. Golang错误处理函数defer、panic、recover、errors.New介绍

    在默认情况下,当发生错误(panic)后,程序就会终止运行 如果发生错误后,可以捕获错误,并通知管理人员(邮件或者短信),程序还可以继续运行,这当然无可厚非 errors.New("错误信息 ...

  8. [日常] Go语言圣经-Panic异常,Recover捕获异常习题

    Go语言圣经-Panic异常1.当panic异常发生时,程序会中断运行,并立即执行在该goroutine中被延迟的函数(defer 机制)2.不是所有的panic异常都来自运行时,直接调用内置的pan ...

  9. Golang异常处理-panic与recover

    Golang异常处理-panic与recover 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 在程序设计中,容错是相当重要的一部分工作,在 Go中它是通过错误处理来实现的,err ...

随机推荐

  1. linux三剑客之sed命令

    一.前言 我们都知道,在Linux中一切皆文件,比如配置文件,日志文件,启动文件等等.如果我们相对这些文件进行一些编辑查询等操作时,我们可能会想到一些vi,vim,cat,more等命令.但是这些命令 ...

  2. 10、借助POI实现Java生成并打印excel报表(1)

    10.1.了解 Apache POI 实际开发中,用到最多的是把数据库中数据导出生成报表,尤其是在生产管理或者财务系统中用的非常普遍.生成报表格式一般是EXCEL或者PDF .利用Apache  PO ...

  3. Unix Shortcuts

    find . -name "*.java" -type f find all the files within a director and its sub-directory e ...

  4. springcloud(六):配置中心(一)

    随着线上项目变的日益庞大,每个项目都散落着各种配置文件,如果采用分布式的开发模式,需要的配置文件随着服务增加而不断增多.某一个基础服务信息变更,都会引起一系列的更新和重启,运维苦不堪言也容易出错.配置 ...

  5. Java基础知识二次学习-- 第一章 java基础

    基础知识有时候感觉时间长似乎有点生疏,正好这几天有时间有机会,就决定重新做一轮二次学习,挑重避轻 回过头来重新整理基础知识,能收获到之前不少遗漏的,所以这一次就称作查漏补缺吧!废话不多说,开始! 第一 ...

  6. GoodReads: Machine Learning (Part 3)

    In the first installment of this series, we scraped reviews from Goodreads. In thesecond one, we per ...

  7. python爬虫从入门到放弃(四)之 Requests库的基本使用

    什么是Requests Requests是用python语言基于urllib编写的,采用的是Apache2 Licensed开源协议的HTTP库如果你看过上篇文章关于urllib库的使用,你会发现,其 ...

  8. 总结scala(一)

    由于笔记太多,分为了几部分,进入我的博客,查看其它的笔记 scala:面向对象,函数式编程 一.声明变量 1.变量的类型 Byte,Char,Short,Int,Long,Float,Double,B ...

  9. iOS 原生模块 给 Javascript(ReactNative) 发送事件 (通知监听)

    官方中文文档是这样描述的:   就给我们这几句话 就打发我们了. 按照上面的写法,根本不知道  - (void)calendarEventReminderReceived:(NSNotificatio ...

  10. cef3和duilib简单仿有道词典学习

    由于最近换工作的原因,也没啥事,就简单学习了一下cef3和duilib,楼主之前是做MFC框架下的windows开发的,对界面库和新的客户端开发模式也有所了解,现在的大部分客户端都是基本的客户端框架下 ...