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

有的同学看到Go和TryCatch一起出现,心里可能会说,难道Go语言升级了,加入了try...catch语句。哈哈,其实Go语言从创建之初就没打算加入try...catch语句,因为创建Go的那帮大爷认为try...catch挺烦人的,如果滥用,会造成程序混乱,所以就不打算加入try...catch(以后加不加入不好说)。

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

Go语言中的异常处理机制

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

不管是什么异常处理机制,核心的原理都是一样的,通常来讲,一个完善的异常处理机制需要由下面3部分组成。

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

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

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个异常重要的组成部分都有。

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

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

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语句。

现在来分析一下如果模拟。模拟的过程需要完成下面的工作。

  • 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结构体中。然后调用方式就与前面的描述的一致了。执行这段代码,会输出如下图的信息。

增强版的TryCatch

到现在为止,其实已经完整地实现了try...catch...finally语句,d但细心的同学会发现,这个实现有一点小问题。通常的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")
    })
}

执行结果如下图所示。

这个实现与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获得源代码。

原文地址:https://blog.51cto.com/androidguy/2397112

时间: 2024-08-04 02:09:19

用Go语言异常机制模拟TryCatch异常捕捉的相关文章

C++ 异常机制分析(C++标准库定义了12种异常,很多大公司的C++编码规范也是明确禁止使用异常的,如google、Qt)

阅读目录 C++异常机制概述 throw 关键字 异常对象 catch 关键字 栈展开.RAII 异常机制与构造函数 异常机制与析构函数 noexcept修饰符与noexcept操作符 异常处理的性能分析 正文 回到顶部 C++异常机制概述 异常处理是C++的一项语言机制,用于在程序中处理异常事件.异常事件在C++中表示为异常对象.异常事件发生时,程序使用throw关键字抛出异常表达式,抛出点称为异常出现点,由操作系统为程序设置当前异常对象,然后执行程序的当前异常处理代码块,在包含了异常出现点的

C++ 异常机制分析

C++异常机制概述 异常处理是C++的一项语言机制,用于在程序中处理异常事件.异常事件在C++中表示为异常对象.异常事件发生时,程序使用throw关键字抛出异常表达式,抛出点称为异常出现点,由操作系统为程序设置当前异常对象,然后执行程序的当前异常处理代码块,在包含了异常出现点的最内层的try块,依次匹配catch语句中的异常对象(只进行类型匹配,catch参数有时在catch语句中并不会使用到).若匹配成功,则执行catch块内的异常处理语句,然后接着执行try...catch...块之后的代码

JAVA基础篇三(Java,C++中的异常机制)

由于C++和JAVA有很多相似之处,又有很多细微的差别,所以在学习JAVA的过程中对两种语言进行对比学习. 1.C++的异常机制 C++中处理异常的过程是这样的:在执行程序发生异常,可以不在本函数中处理,而是抛出一个错误信息,把它传递给上一级的函数来解决,上一级解决不了,再传给其上一级,由其上一级处理.如此逐级上传,直到最高一级还无法处理的话,运行系统会自动调用系统函数terminate,由它调用abort终止程序.这样的异常处理方法使得异常引发和处理机制分离,而不在同一个函数中处理.这使得底层

java中的异常机制(编译时异常)

/ * 1 异常机制的原理 * 异常是什么:就是错误的另外一种说法; * 在java中,有一个专门模拟所有异常的类,所有的异常都必须继承这个类:Throwable; * 本质是:当程序出错以后,jvm会隐性的创建一个对象,获取错误信息,返回给程序调用处.这个是我们程序员在写程序的时候对某些高风险的操作规定了一个提示机制.我们提示这个机制,相对于系统就是一个处理异常的触发机制 * 异常机制和正常程序机制是两条平行线,一条是我们看到的main()函数里面的调用线.还有一条我们看不见的,如果程序正常情

C#OOP之十 异常机制及其处理

异常简介 索引越界.文件I/O出错.堆栈溢出.除零运算问题.数据库无法使用等一个个问题,对于我们来说如何保证系统容错和程序规范,异常机制是不可或缺的重要因素和手段.良好的系统设计必定有良好的异常处理机制来保证程序的健壮性和容错机制. 不可靠的程序含有很多"臭虫"(也叫Bug),含有臭虫的代码我们称之为有"臭味"(BadShell).软件领域的Bug是无处不在的,所以,不要轻言自己的程序已经完美了.其实,只是你现在还没发现Bug,或者说还没有能力来发现Bug而已,当然

Java的异常机制

一.什么是异常:非正常的,不同寻常的,不是语法错误. 生活中,医生说你身体某个部位异常,该部位和正常相比,有点不同功能可能受损. 张三开车去上班,正常情况下,顺利到达公司 非正常情况下,车子坏了,走路去公司 异常指的 不是语法错误 ,语法错误编译通不过,不会产生字节码文件,根本不能运行 程序中:代码出现错误,程序就会停止运行. 异常处理是衡量一门语言是否成熟的标准之一.主流的java c++ c# 都有异常处理机制. 异常处理可以让程序有更好的容错性,使代码更健壮. 传统的c语言是没有异常处理的

Java必知必会:异常机制详解

一.Java异常概述 在Java中,所有的事件都能由类描述,Java中的异常就是由java.lang包下的异常类描述的. 1.Throwable(可抛出):异常类的最终父类,它有两个子类,Error与Exception. Throwable中常用方法有: getCause():返回抛出异常的原因.如果 cause 不存在或未知,则返回 null. getMeage():返回异常的消息信息. printStackTrace():对象的堆栈跟踪输出至错误输出流,作为字段 System.err 的值.

异常机制

1. 异常机制       异常机制是指当程序出现错误后,程序如何处理.具体来说,异常机制提供了程序退出的安全通道.当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器. 传统的处理异常的办法是,函数返回一个特殊的结果来表示出现异常(通常这个特殊结果是大家约定俗称的),调用该函数的程序负责检查并分析函数返回的结果.这样做有如下的弊端:例如函数返回-1代表出现异常,但是如果函数确实要返回-1这个正确的值时就会出现混淆:可读性降低,将程序代码与处理异常的代码混爹在一起:由调用函数的程序

Java语言程序设计 上机实验5 异常

Java语言程序设计 上机实验5 实验目的: 理解异常的概念,掌握Java的异常处理机制.常见异常的捕获方法.自定义异常及其抛出和捕获方法. 实验内容: 常见异常的捕获: 编写一个程序,分别生成ArrayIndexOutOfBoundsException类型和NumberFormatException类型的异常:然后改写程序,对这两种类型的异常进行捕获并合理地处理. 另外,思考并回答问题:能否0同时抛出ArrayIndexOutOfBoundsException类型和NumberFormatEx