Defer, Panic, and Recover
发布时间:2020-12-16 18:20:12 所属栏目:大数据 来源:网络整理
导读:工作之后两年多没写过博客了,给自己找了一个忙的借口,快过年了,有点时间,最近学习go语言,习惯于学习官方文档,发现 https://blog.golang.org/defer-panic-and-recover 这篇文章没有翻译版本,可能会延长学习的时间成本,就翻译了一下,翻译的不妥请大家
工作之后两年多没写过博客了,给自己找了一个忙的借口,快过年了,有点时间,最近学习go语言,习惯于学习官方文档,发现 https://blog.golang.org/defer-panic-and-recover 这篇文章没有翻译版本,可能会延长学习的时间成本,就翻译了一下,翻译的不妥请大家指正
Defer,Panic,and Recover Go语言有着通用的流程控制机制:if, for, switch,goto。同样有在独立go程中运行代码的机制。我们这里讨论一个相对于前两者不那么常用的机制:defer, panic和recover。 defer表达式将函数调用压进一个线性表中(理解为堆栈)。在所有上层函数返回后(即当前层次调用的所有函数返回后,并且当前函数调用return),线性表中的调用开始执行。defer一般被用来简化需要进行一些清理操作的函数。 举个例子,我们来看一个执行文件内容拷贝操作的函数,即打开两个文件,并将其中一个文件的内容拷贝到另一个文件中 func CopyFile(dstName,srcName string) (written int64,err error) { src,err := os.Open(srcName) if err != nil { return } dst,err := os.Create(dstName) if err != nil { return } written,err = io.Copy(dst,src) dst.Close() src.Close() return } 这个函数可以执行,但是有个bug,如果os.Create调用失败,那么函数将会返回,但是不会close源文件。这个问题只要在第二个return语句之前补一个src.Close就可以修复。但是如果这种情况发生在一个复杂逻辑中,这一问题可能并没有这么容易被发现并修复。通过引入defer表达式,我们可以保证文件close总是被调用: func CopyFile(dstName,err := os.Open(srcName) if err != nil { return } defer src.Close() dst,err := os.Create(dstName) if err != nil { return } defer dst.Close() return io.Copy(dst,src) } Defer语句允许我们在刚刚打开文件的时候就考虑如何关闭文件。无论函数有多少return语句,只要保证这一点,文件就会被close。 defer语句的行为是直接并且可以预测的,有三个简单的原则: 1.defer语句一旦运行,那么被执行defer操作的函数的参数值就被确定,举个例子: func a() { i := 0 defer fmt.Println(i) i++ return } 在这个例子中,变量i在Println被defer调用时候就被计算出来,因此在函数返回后,被defer的调用会打印0而不是1 2. 在外层函数返回后,被defer的函数按照后进先出(LIFO,因此我理解为堆栈)的顺序执行,比如下面函数最终打印“3210”: func b() { for i := 0; i < 4; i++ { defer fmt.Print(i) } } 3. 被defer的函数可能读取并且赋值给正在返回函数的具备名称的返回值。 func c() (i int) { defer func() { i++ }() return 1 } 在这个例子中, 一个defer的函数在外层函数返回后对返回值i执行自增操作,因此,这个函数返回后i的值为2.这有利于修复函数的错误返回值,稍后将会看到一个例子。 panic是一个结束正常的控制流程,并且启动panicking(不知道怎么翻)机制的内建方法。当函数F调用panic时,F的执行结束,所有F中被defer的函数开始执行,然后F返回到调用者。对于调用者而言,F接下来的行为像一个对panic的调用。进程持续退栈操作直到当前go程的多有方法返回,在这一点程序失败。panic可以通过直接引入panic状态开始。他们可以被运行时错误导致,例如数组的越界访问。 recover是一个恢复对panicking状态go程控制的内建方法。recover只有在被defer的函数或方法中才有效。在正常执行过程中,对recover的调用将会返回nil,不会有其他影响。如果当前go程正处于panicking状态,对recover的调用将会捕捉传入panic的值并且恢复正常执行。 下面是一个用来阐明panic和defer的例程: 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) } 函数g入参为int类型的i,如果i大于3则panic,否则使用i+1递归调用函数自身。函数f对一个调用了recover并且打印被恢复的值(如果部位nil的话)的函数进行的defer操作。再继续阅读之前,尝试描述程序可能的输出。 程序将会输出: 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. 如果我们从f中将被defer的函数移除,那么panic就没有被recover,在达到go程调用栈顶端之后,终止程序,输出会如下所示: 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 panic PC=0x2a9cd8 [stack trace omitted] 对于实际使用的panic和recover的例子,需要查看go 标准库的json包。它使用一系列的递归和循环函数解析使用json编码的数据。在遇到畸形的json数据时,解析器调用panic释放栈空间直到最高一级的函数调用,这一函数调用从panic恢复并且返回一个合理的error 值(详见 decode.go中解码状态类型'error' 和'unmarshal' 方法) go标准库的约定是,虽然包内部使用了panic,他的外部接口依然要返回的明确error值。 其他defer用法(除了早先提到的file.Close的例子)包括释放一个互斥量 mu.Lock() defer mu.Unlock() 打印页脚 printHeader() defer printFooter() 以及其他的更多使用。 综上所述,defer语句(无论是否和panic与recover一起使用)提供了一套不同寻常但是有力的流程控制机制。这一机制可以用来规范其他语言的一大批使用特殊结构完成的功能,可以尝试一下。
英文版原作者:Andrew Gerrand (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
相关内容
- [golang]自己动手实现ini文件读取
- java – 访问服务层和控制器中的spring用户 – Spring 3.
- 如何在其他列之间插入一列(Perl Spreadsheet :: WriteExcel
- Delphi XE2 之 FireMonkey 入门(39) - 控件基础: TScrollBo
- Perl 语言实现一个窗体
- delphi – 如何执行特定HID设备的硬件复位?
- spring – JodaTime和BeanPropertySqlParameterSource
- [bigdata-074] java + maven + mysql + jdbc 示例
- COM与.NET交互简单示例--追忆VB6
- go的异常处理机制