GOLANG错误处理最佳方案errors wrap, Defer, Panic, and Recover
Simple error handling primitives: https://github.com/pkg/errors
Defer, Panic, and Recover: https://blog.golang.org/defer-panic-and-recover
golang的defer精析: https://studygolang.com/articles/742
example1
func f() (result int) {
defer func() {
result++
}()
return 0
}
example2
func f() (r int) {
t := 5
defer func() {
t = t + 5
}()
return t
}
example3
func f() (r int) {
defer func(r int) {
r = r + 5
}(r)
return 1
}
先不要运行代码,自己在心里跑一遍结果。然后再去验证。如果三个都做对了并且不是蒙的....好吧,不用往下看了,你已经懂defer了。
多空几行确保你先在心里跑过一遍代码,之后验证了,并且存在疑惑......
额,如果example1中你算的是0,你就错了
如果example2中你觉得是10,你又错了...蒙对的不算...
如果example3中觉得得6,你又错了...如果你有算对的,也有算错,好吧...你丫的就是在蒙!
不懂的继续往下看啊.....
首先要明确的是:defer是在return之前执行的
这是官方文档中明确说明的http://golang.org/ref/spec#Defer_statements ,知道就行了,可以无视
然后要了解是的defer的实现方式,我以前有写过http://bbs.mygolang.com/thread-271-1-1.html
大意就是在defer出现的地方插入的指令
CALL runtime.deferproc
然后在函数返回之前的地方,插入指令
CALL runtime.deferreturn
再就是明确go返回值的方式跟C是不一样的,为了支持多值返回,go是用栈返回值的,而C是用寄存器。
最最最重要的一点就是:return xxx 这一句语句并不是一条原子指令!
整个return过程,没有defer之前是,先把在栈中写一个值,这个值被会当作返回值。然后再调用RET指令返回。return xxx语句汇编后是先给返回值赋值,再做一个空的return: ( 赋值指令 + RET指令)
defer的执行是被插入到return指令之前的
有了defer之后,就变成了 (赋值指令 + CALL defer指令 + RET指令)
而在CALL defer函数中,有可能将最终的返回值改写了...也有可能没改写。总之,如果改写了,那么看上去就像defer是在return xxx之后执行的~
这是所有你所想不明白的defer故事发生的根源。
上面的基础知识都有了,然后就可以来说说神奇的defer了。告诉大家一个简单的转换规则大家就再也不为defer迷糊了。
改写规则是将return语句分开成两句写,return xxx会被改写成:
返回值 = xxx
调用defer函数
空的return
先看example1。它可以改写成这样:
func f() (result int) {
result = 0 //return语句不是一条原子调用,return xxx其实是赋值+RET指令
func() { //defer被插入到return之前执行,也就是赋返回值和RET指令之间
result++
}()
return
}
所以这个返回的是1
再看example2。它可以改写成这样:
func f() (r int) {
t := 5
r = t //赋值指令
func() { //defer被插入到赋值与返回之间执行,这个例子中返回值r没被修改过
t = t + 5
}
return //空的return指令
}
所以这个的结果是5
最后看example3。它改写后变成:
func f() (r int) {
r = 1 //给返回值赋值
func(r int) { //这里改的r是传值传进去的r,不会改变要返回的那个r值
r = r + 5
}(r)
return //空的return
}
所以这个例子结果是1
懂了么?
结论:defer确实是在return之前调用的。但表现形式上却可能不像。本质原因是return xxx语句并不是一条原子指令,defer被插入到了赋值 与 RET之前,因此可能有机会改变最终的返回值。
当你觉得迷糊时,可以用我给的这套规则转一下代码。
golang中defer的使用规则
在golang当中,defer代码块会在函数调用链表中增加一个函数调用。这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用。因此,defer通常用来释放函数内部变量。
为了更好的学习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
}
这段代码可以运行,但存在'安全隐患'。如果调用dst, err := os.Create(dstName)失败,则函数会执行return退出运行。但之前创建的src(文件句柄)没有被释放。 上面这段代码很简单,所以我们可以一眼看出存在文件未被释放的问题。 如果我们的逻辑复杂或者代码调用过多时,这样的错误未必会被及时发现。 而使用defer则可以避免这种情况的发生,下面是使用defer的代码:
func CopyFile(dstName, srcName string) (written int64, err error) {
src, 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,我们可以在代码中优雅的关闭/清理代码中所使用的变量。defer作为golang清理变量的特性,有其独有且明确的行为。以下是defer三条使用规则。
规则一 当defer被声明时,其参数就会被实时解析
我们通过以下代码来解释这条规则:
func a() {
i := 0
defer fmt.Println(i)
i++
return
}
上面我们说过,defer函数会在return之后被调用。那么这段函数执行完之后,是不用应该输出1呢?
读者自行编译看一下,结果输出的是0. why?
这是因为虽然我们在defer后面定义的是一个带变量的函数: fmt.Println(i). 但这个变量(i)在defer被声明的时候,就已经确定其确定的值了。 换言之,上面的代码等同于下面的代码:
func a() {
i := 0
defer fmt.Println(0) //因为i=0,所以此时就明确告诉golang在程序退出时,执行输出0的操作
i++
return
}
为了更为明确的说明这个问题,我们继续定义一个defer:
func a() {
i := 0
defer fmt.Println(i) //输出0,因为i此时就是0
i++
defer fmt.Println(i) //输出1,因为i此时就是1
return
}
通过运行结果,可以看到defer输出的值,就是定义时的值。而不是defer真正执行时的变量值(很重要,搞不清楚的话就会产生于预期不一致的结果)
但为什么是先输出1,在输出0呢? 看下面的规则二。
规则二 defer执行顺序为先进后出
当同时定义了多个defer代码块时,golang安装先定义后执行的顺序依次调用defer。不要为什么,golang就是这么定义的。我们用下面的代码加深记忆和理解:
func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}
在循环中,依次定义了四个defer代码块。结合规则一,我们可以明确得知每个defer代码块应该输出什么值。 安装先进后出的原则,我们可以看到依次输出了3210.
规则三 defer可以读取有名返回值
先看下面的代码:
func c() (i int) {
defer func() { i++ }()
return 1
}
输出结果是12. 在开头的时候,我们说过defer是在return调用之后才执行的。 这里需要明确的是defer代码块的作用域仍然在函数之内,结合上面的函数也就是说,defer的作用域仍然在c函数之内。因此defer仍然可以读取c函数内的变量(如果无法读取函数内变量,那又如何进行变量清除呢....)。
当执行return 1 之后,i的值就是1. 此时此刻,defer代码块开始执行,对i进行自增操作。 因此输出2.
掌握了defer以上三条使用规则,那么当我们遇到defer代码块时,就可以明确得知defer的预期结果。
GOLANG错误处理最佳方案errors wrap, Defer, Panic, and Recover的更多相关文章
- Golang 入门系列(十四)defer, panic和recover用法
以前讲过golang 的基本语法.但是,只是讲了一些基础的语法,感兴趣的可以看看以前的文章,https://www.cnblogs.com/zhangweizhong/category/1275863 ...
- 15 Defer, Panic, and Recover
Defer, Panic, and Recover 4 August 2010 Go has the usual mechanisms for control flow: if, for, switc ...
- 6.Go-错误,defer,panic和recover
6.1.错误 Go语言中使用builtin包下error接口作为错误类型 Go语言中错误都作为方法/函数的返回值 自定义错误类型 //Learn_Go/main.go package main imp ...
- Golang错误和异常处理的正确姿势
Golang错误和异常处理的正确姿势 错误和异常是两个不同的概念,非常容易混淆.很多程序员习惯将一切非正常情况都看做错误,而不区分错误和异常,即使程序中可能有异常抛出,也将异常及时捕获并转换成错误.从 ...
- GO_05_2:Golang 中 panic、recover、defer 的用法
函数 defer 1. 它的执行方式类似其他语言中的折构函数,在函数体执行结束后按照调用顺序的 相反顺序 逐个执行 2. 即使函数发生 严重错误 也会被执行,类似于 java 中 try{...} ...
- go语言defer panic recover用法总结
defer defer是go提供的一种资源处理的方式.defer的用法遵循3个原则 在defer表达式被运算的同时,defer函数的参数也会被运算.如下defer的表达式println运算的同时,其入 ...
- Golang错误处理函数defer、panic、recover、errors.New介绍
在默认情况下,当发生错误(panic)后,程序就会终止运行 如果发生错误后,可以捕获错误,并通知管理人员(邮件或者短信),程序还可以继续运行,这当然无可厚非 errors.New("错误信息 ...
- golang 错误处理与异常
原文地址 golang 中的错误处理的哲学和 C 语言一样,函数通过返回错误类型(error)或者 bool 类型(不需要区分多种错误状态时)表明函数的执行结果,调用检查返回的错误类型值是否是 nil ...
- 【GoLang】GoLang 错误处理 -- 官方推荐方式 示例
最严谨的方式,Always检查error,并做相应的处理 项目结构: 代码: common.go: package common import ( "github.com/pkg/error ...
随机推荐
- shell截取字符串的一些简单方法
一.使用${} 1.${var##*/}该命令的作用是去掉变量var从左边算起的最后一个'/'字符及其左边的内容,返回从左边算起的最后一个'/'(不含该字符)的右边的内容.使用例子及结果如下:
- 【前端积累】javascript事件
什么是事件? 事件是一种异步编程的实现方式,本质上是程序各个组成部分之间的通信.就是文档或浏览器窗口发生的一些特定的交互瞬间(某种动作). 1.事件流 事件流描述的是从页面中接收事件的顺序. 1)事件 ...
- linux下 php 安装mysql的扩展模块
1.安装mysql-devel包 [root@DBproxy ~]# yum install mysql-devel 注:该包必须在编译php之前安装好,否则在安装php的mysql扩展模块是会碰到各 ...
- 使用maven项目 转XSD、 WSDL 成Java类
因为项目需要,在网上查找了很多WSDL的使用方式,但是感觉不是特别顺利.现在是利用Maven 项目快速转化的方法. 首先建立一个maven 项目 ,并将下面的pom文件复制进去. <projec ...
- 第二步 (仅供参考) sencha touch 使用cmd打包apk
最新版本的cmd可以直接将sencha touch项目打包成本地应用,不过还有很多不足,本文仅供参考 通过sencha app build native命令可以直接将项目打包成本地应用,不过在命令运行 ...
- PtH(hash传递攻击)原理探秘
背景知识 Windows 横向渗透的两种方式 1.hash传递攻击,通过传递NTLM-Hash,登录机器,简称PtH: 2.ticket传递攻击,通过传递kerberos的ticket,登录机器,简称 ...
- WinDbg远程调试unable to initialize target machine information win32 error 0n87
Debugging Target:Windows XP SP3 32-bit Debugging Host:Windows Server 2012 64-bit 当附加到目标服务器某个进程后,WinD ...
- 静态时序分析基础STA
静态时序分析SAT 1. 背景 静态时序分析的前提就是设计者先提出要求,然后时序分析工具才会根据特定的时序模型进行分析,给出正确是时序报告. 进行静态时序分析,主要目的就是为了提高系统工作主频 ...
- 【CF802C】Heidi and Library (hard) 费用流
[CF802C]Heidi and Library (hard) 题意:有n个人依次来借书,第i人来的时候要求书店里必须有种类为ai的书,种类为i的书要花费ci块钱购入.而书店的容量只有k,多余的书只 ...
- OC开发_整理笔记——多线程之GCD
一.进程和线程 二.各种队列! 1.GCD:Grand Central Dispatch 2.串行队列(Serial) 你可以创建任意个数的串行队列,每个队列依次执行添加的任务,一个队列 ...