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

た 入场券 2022-01-30 18:21 424阅读 0赞

有的同学看到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的异常处理机制来说明这一点。

  1. import java.io.IOException;
  2. public class Main {
  3. public static void main(String[] args) {
  4. try
  5. {
  6. boolean ioException = false;
  7. if (ioException) {
  8. throw new IOException("ioexception");
  9. } else {
  10. throw new Exception("exception");
  11. }
  12. }
  13. catch (IOException e) {
  14. System.err.println(e);
  15. }
  16. catch (Exception e) {
  17. System.out.println(e);
  18. }
  19. finally
  20. {
  21. System.out.println("finally");
  22. }
  23. }
  24. }

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

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

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

  1. package main
  2. import "fmt"
  3. func main(){
  4. // 处理异常的函数
  5. defer func(){
  6. fmt.Println("开始处理异常")
  7. // 获取异常信息
  8. if err:=recover();err!=nil{
  9. // 输出异常信息
  10. fmt.Println("error:",err)
  11. }
  12. fmt.Println("结束异常处理")
  13. }()
  14. exceptionFun()
  15. }
  16. func exceptionFun(){
  17. fmt.Println("exceptionFun开始执行")
  18. panic("异常信息")
  19. fmt.Println("exceptionFun执行结束")
  20. }

实现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语句的基本原理,下面给出完整的实现代码。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type ExceptionStruct struct {
  6. Try func()
  7. Catch func(Exception)
  8. Finally func()
  9. }
  10. type Exception interface{}
  11. func Throw(up Exception) {
  12. panic(up)
  13. }
  14. func (this ExceptionStruct) Do() {
  15. if this.Finally != nil {
  16. defer this.Finally()
  17. }
  18. if this.Catch != nil {
  19. defer func() {
  20. if e := recover(); e != nil {
  21. this.Catch(e)
  22. }
  23. }()
  24. }
  25. this.Try()
  26. }
  27. func main() {
  28. fmt.Println("开始执行...")
  29. ExceptionStruct{
  30. Try: func() {
  31. fmt.Println("try...")
  32. Throw("发生了错误")
  33. },
  34. Catch: func(e Exception) {
  35. fmt.Printf("exception %v\n", e)
  36. },
  37. Finally: func() {
  38. fmt.Println("Finally...")
  39. },
  40. }.Do()
  41. fmt.Println("结束运行")
  42. }

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

image.png

增强版的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函数,然后再调用。下面就是完整的实现代码。

  1. package main
  2. import (
  3. "log"
  4. )
  5. type Exception struct {
  6. Id int // exception id
  7. Msg string // exception msg
  8. }
  9. type TryStruct struct {
  10. catches map[int]ExceptionHandler
  11. try func()
  12. }
  13. func Try(tryHandler func()) *TryStruct {
  14. tryStruct := TryStruct{
  15. catches: make(map[int]ExceptionHandler),
  16. try: tryHandler,
  17. }
  18. return &tryStruct
  19. }
  20. type ExceptionHandler func(Exception)
  21. func (this *TryStruct) Catch(exceptionId int, catch func(Exception)) *TryStruct {
  22. this.catches[exceptionId] = catch
  23. return this
  24. }
  25. func (this *TryStruct) Finally(finally func()) {
  26. defer func() {
  27. if e := recover(); nil != e {
  28. exception := e.(Exception)
  29. if catch, ok := this.catches[exception.Id]; ok {
  30. catch(exception)
  31. }
  32. finally()
  33. }
  34. }()
  35. this.try()
  36. }
  37. func Throw(id int, msg string) Exception {
  38. panic(Exception{id,msg})
  39. }
  40. func main() {
  41. exception.Try(func() {
  42. log.Println("try...")
  43. // 指定了异常代码为2,错误信息为error2
  44. exception.Throw(2,"error2")
  45. }).Catch(1, func(e exception.Exception) {
  46. log.Println(e.Id,e.Msg)
  47. }).Catch(2, func(e exception.Exception) {
  48. log.Println(e.Id,e.Msg)
  49. }).Finally(func() {
  50. log.Println("finally")
  51. })
  52. }

执行结果如下图所示。

image.png

这个实现与Java中的try…catch…finally的唯一区别就是必须要调用Finally函数,因为处理异常的代码都在Finally函数中。不过这并不影响使用,如果finally部分没什么需要处理的,那么就设置一个空函数即可。

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

  1. package main
  2. import (
  3. "exception"
  4. "log"
  5. )
  6. func main() {
  7. exception.Try(func() {
  8. log.Println("try...")
  9. exception.Throw(2,"error2")
  10. }).Catch(1, func(e exception.Exception) {
  11. log.Println(e.Id,e.Msg)
  12. }).Catch(2, func(e exception.Exception) {
  13. log.Println(e.Id,e.Msg)
  14. }).Finally(func() {
  15. log.Println("finally")
  16. })
  17. }

发表评论

表情:
评论列表 (有 0 条评论,424人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java全局异常捕捉

    1.场景还原      在项目开发中,对于各种异常都有不同的报错提示或策略,无疑对于用户或者开发来说,是一个很好的用户体验;如何在java定义一个全局异常捕捉呢? 

    相关 Java异常捕捉机制

    Java异常捕捉机制 1、简介 异常指的是程序运行时出现的非正常情况,Java中的异常捕捉最经常使用到的是try和catch两个代码块。本博客讲解的程序是工程训练基