前言

许多主流语言诸如: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机制。另外有两点需要注意

  1. 强烈建议这里基于Go实现的Try-Catch机制都调用一下CatchAll(),即有可能出现Catch()函数无法匹配到的异常。
  2. 强烈建议这里的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机制的更多相关文章

  1. ASP.NET(C#)中的try catch异常处理机制

    在开发一个Umbraco平台系统的过程中,遇到了问题. 写的代码如下 fileUrl = MediaHelper.GetMediaUrl(Convert.ToInt32(publishedConten ...

  2. Objective-C try/catch异常处理机制原理。

    try-catch-finaly finally在任何情况下都会执行(不管有没有异常),属于整个体系的附属. 基本思想是跳到捕获锚点,重新执行. http://www.cnblogs.com/mark ...

  3. try throw catch异常处理机制

    /*本程序实现分块查找算法  又称索引顺序查找     需要注意的是分块查找需要2次查找  先对块查找  再对块内查找    2013.12.16    18:44*/ #include <io ...

  4. 初步体验javascript try catch机制

    javascript在ECMAScript3中引入了try catch finally机制,大致原理和其他语言一样. 我们也可以自定义错误事件. 但是事先声明:我们自定义的错误事件,只支持对name. ...

  5. JS魔法堂:深究JS异步编程模型

    前言  上周5在公司作了关于JS异步编程模型的技术分享,可能是内容太干的缘故吧,最后从大家的表情看出"这条粉肠到底在说啥?"的结果:(下面是PPT的讲义,具体的PPT和示例代码在h ...

  6. [转]SQL Server 存储过程 一些常用用法(事物、异常捕捉、循环)

      最新更新请访问: http://denghejun.github.io Transact-SQL中的存储过程,非常类似于Java语言中的方法,它可以重复调用.当存储过程执行一次后,可以将语句缓存中 ...

  7. Erlang初学

    这篇文章主要介绍了Erlang初学:Erlang的一些特点和个人理解总结,本文总结了函数式编程.一切都是常量.轻量进程.进程端口映射及典型缺点等内容,需要的朋友可以参考下 我对 Erlang 编程理念 ...

  8. SQL Server 2012 T-SQL 新特性

    序列 Sequence SQL Server 现在将序列当成一个对象来实现,创建一个序列的例子语法如下: CREATE SEQUENCE DemoSequence START WITH 1 INCRE ...

  9. node.js整理 06异步编程

    回调 异步编程依托于回调来实现,但不能说使用了回调后程序就异步化了 function heavyCompute(n, callback) { var count = 0, i, j; for (i = ...

  10. [转]七天学会NodeJS

    转:http://nqdeng.github.io/7-days-nodejs/ NodeJS基础 什么是NodeJS JS是脚本语言,脚本语言都需要一个解析器才能运行.对于写在HTML页面里的JS, ...

随机推荐

  1. April 22 2017 Week 16 Saturday

    Fear is an essential part of our survival, it keeps us alert. 恐惧是生存的重要部分,它让我们保持警惕. Fear and pain are ...

  2. Mysql在字符串类型的日期上加上10分钟并和如今的日期做比較

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/ufo2910628/article/details/32092869 SELECT id FROM ...

  3. LA 3635 派

    题目链接:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_ ...

  4. 1012: A MST Problem

    1012: A MST Problem 时间限制: 1 Sec  内存限制: 32 MB提交: 63  解决: 33[提交][状态][讨论版][命题人:外部导入] 题目描述 It is just a ...

  5. 复杂链表的复制 -python编写

    题目描述 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head.(注意,输出结果中请不要返回参数中的节点引用,否 ...

  6. 第36章 SDIO—SD卡读写测试—零死角玩转STM32-F429系列

    第36章     SDIO—SD卡读写测试 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/f ...

  7. runit git-daemon-run 等错误

    正在处理用于 man-db (2.7.5-1) 的触发器 ... 正在设置 runit (2.1.2-3ubuntu1) ... start: 无法连接到 Upstart: Failed to con ...

  8. angular2中一种换肤实现方案

    思路:整体思路是准备多套不同主题的css样式.在anguar项目启动时,首先加载的index.html中先引入一套默认的样式.当我们页面有动作时再切换css.  可以通过url传参触发,也可以通过bu ...

  9. 【学时总结】◆学时·VI◆ SPLAY伸展树

    ◆学时·VI◆ SPLAY伸展树 平衡树之多,学之不尽也…… ◇算法概述 二叉排序树的一种,自动平衡,由 Tarjan 提出并实现.得名于特有的 Splay 操作. Splay操作:将节点u通过单旋. ...

  10. lintcode_114_不同的路径

    不同的路径   描述 笔记 数据 评测 有一个机器人的位于一个 m × n 个网格左上角. 机器人每一时刻只能向下或者向右移动一步.机器人试图达到网格的右下角. 问有多少条不同的路径? 注意事项 n和 ...