Go实现try-catch-finally机制
前言
许多主流语言诸如:Java、Python都实现了try-catch-finally机制,而Go处理错误的方式却与前两种语言不同。关于Go处理异常的方式是好是坏仁者见仁智者见智,笔者还是更喜欢try-catch-fianlly的写法,这里便和大家分享一个Go实现的try-catch-finally机制。下面先贴部分代码的讲解,完整代码将在文章的末尾中给出。
Try-Catch-Finally
这里我们先来看一段代码:
type CatchHandler interface {
Catch(e error, handler func(err error)) CatchHandler
CatchAll(handler func(err error)) FinalHandler
FinalHandler
}
type FinalHandler interface {
Finally(handlers ...func())
}
func Try(f func()) CatchHandler {
//返回一个实现CatchHandler接口的对象
f() //调用f函数
……
}
CatchHandler接口需要实现三个方法,一个是Catch(),这个方法用于接收一个特定类型的error以及error的处理器,返回还是CatchHandler类型,CatchAll()接收一个error处理器用于处理所有的错误,并返回一个FinalHandler类型的接口,而FinalHandler接口只实现一个方法Finally(),这个方法可接收多个处理器,用于在执行完try-catch块之后执行。
这里有一些地方需要注意,按照我们的接口设计:Try函数返回CatchHandler接口对象,可以调用Catch()、CatchAll()、Finally()这三个方法,Catch()函数同样返回CatchHandler接口对象,而轮到CatchAll()的时候返回的FinallyHandler接口对象,只有调用一个Finally()。综上所述,未来我们的代码将实现成这样:
type Err1 struct {
error
}
type Err2 struct {
error
}
func main() {
Try(func() {
//业务代码
}).Catch(Err1{}, func(err error) {
//处理Err1类型的错误
}).Catch(Err2{}, func(err error) {
//处理Err2类型的错误
}).CatchAll(func(err error) {
//处理其他错误
}).Finally(func() {
//处理完try-catch块之后最终将会执行的的代码
})
}
上面的设计也符合传统try-catch-finally的写法。
现在,我们给出Try函数的实现:
func Try(f func()) CatchHandler {
t := &catchHandler{}
defer func() {
defer func() { //<2>
r := recover()
if r != nil {
t.err = r.(error)
}
}()
f() //<1>
}()
return t
}
上面的代码,在<1>处,我们将f()函数在一个defer块中调用。同时在<2>处,又声明了一个defer块,用于捕捉异常。<1>处的defer块会在Try()函数正常返回后(即return之后)调用,再执行返回后剩下的代码。然后我们返回了结构体catchHandler的指针,说明结构体catchHandler必须实现接口CatchHandler所需要的函数。CatchHandler除了自身的两个函数Catch()和CatchAll()之外,还继承了FinalHandler的Finally()函数。所以统共需要实现的三个函数至少有三个。
下面,我们来看下catchHandler结构体的实现:
type catchHandler struct {
err error
hasCatch bool
}
//<1>RequireCatch函数有两个作用:一个是判断是否已捕捉异常,另一个是否发生了异常。如果返回false则代表没有异常,或异常已被捕捉。
func (t *catchHandler) RequireCatch() bool {
if t.hasCatch { //<2>如果已经执行了catch块,就直接判断不执行
return false
}
if t.err == nil { //<3>如果异常为空,则判断不执行
return false
}
return true
}
func (t *catchHandler) Catch(e error, handler func(err error)) CatchHandler {
if !t.RequireCatch() {
return t
}
//<4>如果传入的error类型和发生异常的类型一致,则执行异常处理器,并将hasCatch修改为true代表已捕捉异常
if reflect.TypeOf(e) == reflect.TypeOf(t.err) {
handler(t.err)
t.hasCatch = true
}
return t
}
func (t *catchHandler) CatchAll(handler func(err error)) FinalHandler {
//<5>CatchAll()函数和Catch()函数都是返回同一个对象,但返回的接口类型却不一样,也就是CatchAll()之后只能调用Finally()
if !t.RequireCatch() {
return t
}
handler(t.err)
t.hasCatch = true
return t
}
func (t *catchHandler) Finally(handlers ...func()) {
//<6>遍历处理器,并在Finally函数执行完毕之后执行
for _, handler := range handlers {
defer handler()
}
err := t.err
//<7>如果异常不为空,且未捕捉异常,则抛出异常
if err != nil && !t.hasCatch {
panic(err)
}
}
catchHandler有两个字段:err和hasCatch,分别用于保存Try块中函数执行之后返回的异常,以及异常是否被捕捉。
- <1>RequireCatch函数有两个作用:一个是判断是否已捕捉异常,另一个是否发生了异常。如果返回false则代表没有异常,或异常已被捕捉。
- <2>如果已经执行了catch块,就直接判断不执行。
- <3>如果异常为空,则判断不执行。
- <4>如果传入的error类型和发生异常的类型一致,则执行异常处理器,并将hasCatch修改为true代表已捕捉异常。
- <5>CatchAll()函数和Catch()函数都是返回同一个对象,但返回的接口类型却不一样,也就是CatchAll()之后只能调用Finally()。
- <6>遍历处理器,并在Finally函数执行完毕之后执行。
- <7>如果异常不为空,且未捕捉异常,则抛出异常。
现在,让我们来执行下下面的main()函数:
type Err1 struct {
error
}
type Err2 struct {
error
}
func main() {
//Try1
Try(func() {
fmt.Println("Try1 start")
panic(Err1{error: errors.New("error1")})
}).Catch(Err1{}, func(err error) {
fmt.Println("Try1 Err1 Catch:", err.Error())
}).Catch(Err2{}, func(err error) {
fmt.Println("Try1 Err2 Catch:", err.Error())
}).Finally(func() {
fmt.Println("Try1 done")
})
//Try2
Try(func() {
fmt.Println("Try2 start")
panic(Err2{error: errors.New("error2")})
}).Catch(Err1{}, func(err error) {
fmt.Println("Try2 Err1 Catch:", err.Error())
}).CatchAll(func(err error) {
fmt.Println("Try2 Catch All:", err.Error())
}).Finally(func() {
fmt.Println("Try2 done")
})
//Try3
Try(func() {
fmt.Println("Try3 start")
}).Catch(Err1{}, func(err error) {
fmt.Println("Try3 Err1 Catch:", err.Error())
}).Finally(func() {
fmt.Println("Try3 done")
})
}
运行结果:
[root@bogon ]# go run main.go
Try1 start
Try1 Err1 Catch: error1
Try1 done
Try2 start
Try2 Catch All: error2
Try2 done
Try3 start
Try3 done
可以看到,这里确实实现了类似传统try-catch-finally机制。另外有两点需要注意:
- 强烈建议这里基于Go实现的Try-Catch机制都调用一下CatchAll(),即有可能出现Catch()函数无法匹配到的异常。
- 强烈建议这里的Try-Catch机制在不调用CatchAll()的情况下都要调用Finally(),即便你调用了Finally()也可以不塞任何处理函数进去。如果发生了异常,却没有捕捉到,传统的try-catch-finally机制会在finally执行完毕后抛出异常,如果没有finally块则直接抛出异常,而这里需要执行Finally()函数后才能抛出,在Finally()函数中我们判断有异常且异常未被捕捉,则会panic()出异常,如果发生异常未被捕捉,又没有调用Finally(),那异常只能消失在历史的洪流里啦!
最后,贴出完整代码:
package main import (
"reflect"
"fmt"
"errors"
) type CatchHandler interface {
Catch(e error, handler func(err error)) CatchHandler
CatchAll(handler func(err error)) FinalHandler
FinalHandler
} type FinalHandler interface {
Finally(handlers ...func())
} func Try(f func()) CatchHandler {
t := &catchHandler{}
defer func() {
defer func() {
r := recover()
if r != nil {
t.err = r.(error)
}
}()
f()
}()
return t
} type catchHandler struct {
err error
hasCatch bool
} func (t *catchHandler) RequireCatch() bool { //<1>判断是否有必要执行catch块,true为需要执行,false为不执行
if t.hasCatch { //<2>如果已经执行了catch块,就直接判断不执行
return false
}
if t.err == nil { //<3>如果异常为空,则判断不执行
return false
}
return true
}
func (t *catchHandler) Catch(e error, handler func(err error)) CatchHandler {
if !t.RequireCatch() {
return t
}
//<4>如果传入的error类型和发生异常的类型一致,则执行异常处理器,并将hasCatch修改为true代表已捕捉异常
if reflect.TypeOf(e) == reflect.TypeOf(t.err) {
handler(t.err)
t.hasCatch = true
}
return t
} func (t *catchHandler) CatchAll(handler func(err error)) FinalHandler {
//<5>CatchAll()函数和Catch()函数都是返回同一个对象,但返回的接口类型却不一样,也就是CatchAll()之后只能调用Finally()
if !t.RequireCatch() {
return t
}
handler(t.err)
t.hasCatch = true
return t
} func (t *catchHandler) Finally(handlers ...func()) {
//<6>遍历处理器,并在Finally函数执行完毕之后执行
for _, handler := range handlers {
defer handler()
}
err := t.err
//<7>如果异常不为空,且未捕捉异常,则抛出异常
if err != nil && !t.hasCatch {
panic(err)
}
} type Err1 struct {
error
}
type Err2 struct {
error
} func main() { //Try1
Try(func() {
fmt.Println("Try1 start")
panic(Err1{error: errors.New("error1")})
}).Catch(Err1{}, func(err error) {
fmt.Println("Try1 Err1 Catch:", err.Error())
}).Catch(Err2{}, func(err error) {
fmt.Println("Try1 Err2 Catch:", err.Error())
}).Finally(func() {
fmt.Println("Try1 done")
}) //Try2
Try(func() {
fmt.Println("Try2 start")
panic(Err2{error: errors.New("error2")})
}).Catch(Err1{}, func(err error) {
fmt.Println("Try2 Err1 Catch:", err.Error())
}).CatchAll(func(err error) {
fmt.Println("Try2 Catch All:", err.Error())
}).Finally(func() {
fmt.Println("Try2 done")
}) //Try3
Try(func() {
fmt.Println("Try3 start")
}).Catch(Err1{}, func(err error) {
fmt.Println("Try3 Err1 Catch:", err.Error())
}).Finally(func() {
fmt.Println("Try3 done")
}) }
Go实现try-catch-finally机制的更多相关文章
- ASP.NET(C#)中的try catch异常处理机制
在开发一个Umbraco平台系统的过程中,遇到了问题. 写的代码如下 fileUrl = MediaHelper.GetMediaUrl(Convert.ToInt32(publishedConten ...
- Objective-C try/catch异常处理机制原理。
try-catch-finaly finally在任何情况下都会执行(不管有没有异常),属于整个体系的附属. 基本思想是跳到捕获锚点,重新执行. http://www.cnblogs.com/mark ...
- try throw catch异常处理机制
/*本程序实现分块查找算法 又称索引顺序查找 需要注意的是分块查找需要2次查找 先对块查找 再对块内查找 2013.12.16 18:44*/ #include <io ...
- 初步体验javascript try catch机制
javascript在ECMAScript3中引入了try catch finally机制,大致原理和其他语言一样. 我们也可以自定义错误事件. 但是事先声明:我们自定义的错误事件,只支持对name. ...
- JS魔法堂:深究JS异步编程模型
前言 上周5在公司作了关于JS异步编程模型的技术分享,可能是内容太干的缘故吧,最后从大家的表情看出"这条粉肠到底在说啥?"的结果:(下面是PPT的讲义,具体的PPT和示例代码在h ...
- [转]SQL Server 存储过程 一些常用用法(事物、异常捕捉、循环)
最新更新请访问: http://denghejun.github.io Transact-SQL中的存储过程,非常类似于Java语言中的方法,它可以重复调用.当存储过程执行一次后,可以将语句缓存中 ...
- Erlang初学
这篇文章主要介绍了Erlang初学:Erlang的一些特点和个人理解总结,本文总结了函数式编程.一切都是常量.轻量进程.进程端口映射及典型缺点等内容,需要的朋友可以参考下 我对 Erlang 编程理念 ...
- SQL Server 2012 T-SQL 新特性
序列 Sequence SQL Server 现在将序列当成一个对象来实现,创建一个序列的例子语法如下: CREATE SEQUENCE DemoSequence START WITH 1 INCRE ...
- node.js整理 06异步编程
回调 异步编程依托于回调来实现,但不能说使用了回调后程序就异步化了 function heavyCompute(n, callback) { var count = 0, i, j; for (i = ...
- [转]七天学会NodeJS
转:http://nqdeng.github.io/7-days-nodejs/ NodeJS基础 什么是NodeJS JS是脚本语言,脚本语言都需要一个解析器才能运行.对于写在HTML页面里的JS, ...
随机推荐
- April 22 2017 Week 16 Saturday
Fear is an essential part of our survival, it keeps us alert. 恐惧是生存的重要部分,它让我们保持警惕. Fear and pain are ...
- Mysql在字符串类型的日期上加上10分钟并和如今的日期做比較
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/ufo2910628/article/details/32092869 SELECT id FROM ...
- LA 3635 派
题目链接:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_ ...
- 1012: A MST Problem
1012: A MST Problem 时间限制: 1 Sec 内存限制: 32 MB提交: 63 解决: 33[提交][状态][讨论版][命题人:外部导入] 题目描述 It is just a ...
- 复杂链表的复制 -python编写
题目描述 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head.(注意,输出结果中请不要返回参数中的节点引用,否 ...
- 第36章 SDIO—SD卡读写测试—零死角玩转STM32-F429系列
第36章 SDIO—SD卡读写测试 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/f ...
- runit git-daemon-run 等错误
正在处理用于 man-db (2.7.5-1) 的触发器 ... 正在设置 runit (2.1.2-3ubuntu1) ... start: 无法连接到 Upstart: Failed to con ...
- angular2中一种换肤实现方案
思路:整体思路是准备多套不同主题的css样式.在anguar项目启动时,首先加载的index.html中先引入一套默认的样式.当我们页面有动作时再切换css. 可以通过url传参触发,也可以通过bu ...
- 【学时总结】◆学时·VI◆ SPLAY伸展树
◆学时·VI◆ SPLAY伸展树 平衡树之多,学之不尽也…… ◇算法概述 二叉排序树的一种,自动平衡,由 Tarjan 提出并实现.得名于特有的 Splay 操作. Splay操作:将节点u通过单旋. ...
- lintcode_114_不同的路径
不同的路径 描述 笔记 数据 评测 有一个机器人的位于一个 m × n 个网格左上角. 机器人每一时刻只能向下或者向右移动一步.机器人试图达到网格的右下角. 问有多少条不同的路径? 注意事项 n和 ...