用Go语言异常机制模拟TryCatch异常捕捉

有的同窗看到Go和TryCatch一块儿出现,内心可能会说,难道Go语言升级了,加入了try...catch语句。哈哈,其实Go语言从建立之初就没打算加入try...catch语句,由于建立Go的那帮大爷认为try...catch挺烦人的,若是滥用,会形成程序混乱,因此就不打算加入try...catch(之后加不加入很差说)。java

既然Go语言中并无try...catch语句,那么为什么文章标题说要使用TryCatch呢?其实Go语言中只是没有try...catch语句,并非没有异常处理机制。Go语言中的异常处理机制就是著名的异常三剑客:panic、defer和recover。经过这3个家伙,是彻底能够模拟出try...catch语句效果的,对了,后面还应该有个finally。在正式模拟try...catch语句以前,先来回顾下Go语言中的异常处理机制是如何玩的。ide

Go语言中的异常处理机制

在前面提到,Go语言经过panic、defer和recover来处理异常的,那么这3个东西是什么呢?函数

不论是什么异常处理机制,核心的原理都是同样的,一般来说,一个完善的异常处理机制须要由下面3部分组成。this

  • 抛出异常
  • 处理异常的代码段
  • 获取异常信息

下面先用Java的异常处理机制来讲明这一点。code

import java.io.IOException;

public class Main {

    public static void main(String[] args) {
        try
        {
            boolean ioException = false;
            if (ioException) {
                throw new IOException("ioexception");
            } else {
                throw new Exception("exception");
            }
        }
        catch (IOException e) {
            System.err.println(e);
        }
        catch (Exception e) {
            System.out.println(e);
        }
        finally
        {
            System.out.println("finally");
        }
    }
}

上面的代码是标准的Java异常处理机制,try部分的throw用于抛出异常,而catch部分的代码段用于处理特定的异常,经过catch子句的参数e能够获取异常信息。因此对于Java来讲,上述的3个异常重要的组成部分都有。blog

对于Go语言来讲,panic、defer和recover也分别对应了这3部分。其中panic是一个函数,用于抛出异常,至关于Java中的throw函数。defer是一个关键字,用于修饰函数,用defer修饰的函数,在抛出异常时会自动调用。recover是一个函数,用于获取异常信息,一般在用defer修饰的函数中使用。string

下面是一段用Go语言处理异常的代码。it

package main

import "fmt"

func main(){
    //  处理异常的函数
    defer func(){
        fmt.Println("开始处理异常")
        // 获取异常信息
        if err:=recover();err!=nil{
            //  输出异常信息
            fmt.Println("error:",err)
        }
        fmt.Println("结束异常处理")
    }()
    exceptionFun()
}

func exceptionFun(){
    fmt.Println("exceptionFun开始执行")
    panic("异常信息")
    fmt.Println("exceptionFun执行结束")
}

实现Go版的TryCatch

如今已经了解了Go语言的异常处理机制,那么接下来使用异常处理机制来模拟try...catch...finally语句。io

如今来分析一下若是模拟。模拟的过程须要完成下面的工做。class

  • try、catch和finally这3部分都有各自的代码段,因此为了模拟try...catch...finally,须要用3个Go函数来分别模拟try、catch和finally部分的代码段。这3个Go函数是Try、Catch和Finally。
  • 要肯定这3个函数在什么地方调用。Try是正常执行的代码,因此在要首先调用Try函数。而Catch函数只有在抛出异常时调用,因此应该在用defer修饰的函数中调用,并且须要在Catch函数中获取异常信息,因此应该在使用cover函数获取异常信息后再调用Catch函数,一般会将异常信息直接做为参数传递给Catch函数。不论是否抛出异常,Finally函数都必须调用,因此应该用defer修饰Finally函数,并且是第1个用defer修饰的函数。这样,在当前函数结束以前必定刚回调用Finally函数。
  • 触发异常,这就很是简单了,直接用panic函数便可。

上面清楚地描述了用Go语言的异常处理机制模拟try...catch...finally语句的基本原理,下面给出完整的实现代码。

package main
import (
"fmt"
)
type ExceptionStruct struct {
    Try     func()
    Catch   func(Exception)
    Finally func()
}
type Exception interface{}
func Throw(up Exception) {
    panic(up)
}
func (this ExceptionStruct) Do() {
    if this.Finally != nil {

        defer this.Finally()
    }
    if this.Catch != nil {
        defer func() {
            if e := recover(); e != nil {
                this.Catch(e)
            }
        }()
    }
    this.Try()
}

func main() {
    fmt.Println("开始执行...")
    ExceptionStruct{
        Try: func() {
            fmt.Println("try...")
            Throw("发生了错误")
        },
        Catch: func(e Exception) {
            fmt.Printf("exception %v\n", e)
        },
        Finally: func() {
            fmt.Println("Finally...")
        },
    }.Do()
    fmt.Println("结束运行")
}

上面的代码将Try、Catch、Finally函数都封装在了ExceptionStruct结构体中。而后调用方式就与前面的描述的一致了。执行这段代码,会输出以下图的信息。

image.png

加强版的TryCatch

到如今为止,其实已经完整地实现了try...catch...finally语句,但细心的同窗会发现,这个实现有一点小问题。一般的try...catch...finally语句,try部分有且只有1个,finally部分是可选的,但最多只能有1个,而catch部分也是可选的,能够有0到n个,也就是catch部分能够有任意多个。但前面的实现,Catch函数只能指定一个,若是要指定任意多个应该如何作呢?其实很简单,用一个Catch函数集合保存全部指定的Catch函数便可。不过须要快速定位某一个Catch函数。在Java中,是经过异常类型(如IOException、Exception等)定位特定的catch子句的,咱们也能够模拟这一过程,经过特定的异常来定位与该异常对应的Catch函数,为了方便,能够用int类型的异常代码。那么在调用Catch函数以前,就须要经过异常代码先定位到某一个Catch函数,而后再调用。下面就是完整的实现代码。

package main

import (
    "log"
)

type Exception struct {
    Id int       // exception id
    Msg string   // exception msg
}

type TryStruct struct {
    catches map[int]ExceptionHandler
    try   func()
}

func Try(tryHandler func()) *TryStruct {
    tryStruct := TryStruct{
        catches: make(map[int]ExceptionHandler),
        try: tryHandler,
    }
    return &tryStruct
}

type ExceptionHandler func(Exception)

func (this *TryStruct) Catch(exceptionId int, catch func(Exception)) *TryStruct {
    this.catches[exceptionId] = catch
    return this
}

func (this *TryStruct) Finally(finally func()) {
    defer func() {
        if e := recover(); nil != e {

            exception := e.(Exception)

            if catch, ok := this.catches[exception.Id]; ok {            
                catch(exception)
            }

            finally()
        }
    }()

    this.try()
}

func Throw(id int, msg string) Exception {
    panic(Exception{id,msg})
}

func main() {

    exception.Try(func() {
        log.Println("try...")
               //  指定了异常代码为2,错误信息为error2
        exception.Throw(2,"error2")
    }).Catch(1, func(e exception.Exception) {
        log.Println(e.Id,e.Msg)
    }).Catch(2, func(e exception.Exception) {
        log.Println(e.Id,e.Msg)
    }).Finally(func() {
        log.Println("finally")
    })
}

执行结果以下图所示。

image.png

这个实现与Java中的try...catch...finally的惟一区别就是必需要调用Finally函数,由于处理异常的代码都在Finally函数中。不过这并不影响使用,若是finally部分没什么须要处理的,那么就设置一个空函数便可。

为了方便你们,我已经将该实现封装成了函数库,调用代码以下:

package main
import (
    "exception"
    "log"
)

func main() {

    exception.Try(func() {
        log.Println("try...")
        exception.Throw(2,"error2")
    }).Catch(1, func(e exception.Exception) {
        log.Println(e.Id,e.Msg)
    }).Catch(2, func(e exception.Exception) {
        log.Println(e.Id,e.Msg)
    }).Finally(func() {
        log.Println("finally")
    })
}

得到本文源代码,请关注”极客起源“或”欧瑞科技“公众号,并输入308178得到源代码。

image.png