zoukankan      html  css  js  c++  java
  • 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机制。另外有两点需要注意

    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")
    	})
    
    }
    

      

  • 相关阅读:
    Windows的VNC客户端连接Linux无法复制粘贴
    iText中输出中文
    POI写docx文件table中的单元格水平、垂直对齐
    OpenOffice将MS docx转换成pdf文件偶数页眉不显示问题解决办法
    Servlet下载文件迅雷不支持问题真相之一
    Java处理JSON的工具类(List、Map和JSON之间的转换)——依赖jsonlib支持Map嵌套
    EasyUI的combobox组件Chrome浏览器不兼容问题解决办法
    Tomcat5内存简单优化
    jQuery使用动态渲染表单功能完成ajax文件下载
    POI导出Word插入复选框
  • 原文地址:https://www.cnblogs.com/beiluowuzheng/p/10263724.html
Copyright © 2011-2022 走看看