【GoLang那点事】深入Go的异常错误处理机制(二)

#### 开篇词 * 上一篇文章分享了Go的异常,错误处理使用,未读过的可以点击回顾一下,我们知道程序运行中,有异常,有错误,那么什么是异常,什么是错误,和其他语言相比,Go的异常错误机制有什么优点,缺点?我们如何更好的理解,如何用Go写出更健壮的程序,今天来聊一聊这些问题。 #### 异常和错误 * 关于异常和错误每个人都有自己的理解,很多人往往把这个混为一谈,认为他们是等价的,这里我们从Java 和Go两种语言异常错误体系的设计分析来试图回答这个问题。 * Java中,Throwable是所有错误(Error)和异常 (Exception) 的基类,整的来说,它们都是程序运行过程中可能出现的问题,区别在哪里呢? Exception是可预料的,而Error是不可预料的,举个例子,工程队要盖一栋楼,我们把盖这栋楼的过程比作程序一段运行的过程,在盖楼的过程中,建筑人员对于对于瓷砖损坏,下雨,断电,设计错误等都是在意料之内的,并且在发生这些问题时是由恢复,补救措施的,而对于地震,地面塌陷,狂风这些问题是不可预知,意料之外的,并且这些问题真的发生了,也是无法补救,恢复的,只能重新来过。Exception我理解为在程序运行中正常情况下意料之中发生的事,是可以被程序员处理,补救,有机会回到正常处理流程的,而Error在程序运行中非正常成矿下发生后是无法被处理,恢复,比如内存溢出,栈溢出等。 * Go的异常错误设计体系只有Error,任何一切都在方法返回值中返回可能发生的错误,那么go有没有运行过程中意料之外的错误呢,答案是有呢,panic和defer以及recover共同组成了这个体系,但这个体系最终还是被返回Error所处理,什么含义呢,就是在意料之外的panic发生时,在defer中通过recover捕获这个恐慌,转化为错误通过方法返回值告诉方法调用者,看到这里,其实从字面意思,Go中弱化了异常,一切皆错误,都被包装成类似Code,Message的形式返回方法调用者,直到有调用者去处理, 这也是Go的设计精髓,简化没必要存在的。 #### 从编码上看看Go和Java的异常设计思想 * 如果你看过Go的许多代码,那么 `if err != ni`, 应该随处可见,尤其是业务代码开发中,是的,Go没有类似 `try catch` 这样的语句,Go对错误的价值观是可编程,我们看下面的代码: ```go result1,err := func1() if err != nil{ return nil,,err } result2,err := func2() if err != nil{ return nil,err } result3,err := func3() if err != nil{ return nil,err } return result1+result2+result3,nil ``` * * * ```java try{ result1 = method1(); reuslt2 = method2(); result3 = method3(); return result1+result2+result3; }cache(Exception e){ log.error(e); return null; } ``` * 上面的代码我想了解Java和Go的人都知道,我们从多个方面来看看上面的代码,第一:从顺序角度来看,是否Go更能被理解,每一个方法都有一个结果值和一个可能发生的错误值,而Java需要更多的语法,意味着需要更多理解,思考;第二:从对异常错误处理角度来看,Go中程序员对err有更多的操作空间,有更多的可编程性,而Java中相对可编程性弱化了许多;第三:也是最直观的,代码量我们发现Go的代码量比Java多了将近一半,而这种代码并没有什么技术量,重复的代码到处都是,是的,这也是许多开发者对Go不满意的地方,但这种是可以通过开发者代码设计去规避的,这里暂且不讨论 * * * * Java中通过`throw new` 抛出一个异常,通过 `try cache`捕获,而Go中通过`panic`抛出一个恐慌,通过`defer和recover`来处理,我们来看看代码,在分析 ```go func test() (err error){ defer func(){ if e:=recover(); e != nil{ err = e.(error) } }() panic("发生恐慌了") return err } ``` * * * ```java publis Result test(){ try{ throw new RuntimeException("发生异常了") }cache(Exception e){ //处理错误 1 打印错误,忽略 2 throw e,继续抛出 //转化为code,message 3 new Result(code,e.message) } } ``` * 我们分析上面两段代码,当异常或者恐慌发生时,我们可以看到Go中在defer里对通过recover捕获panic,将其转化为一个错误,通过返回值的形式返回,而在Java中异常发生时,捕获以后处理方式为要么打印,要么throw出去抛给上层的方法调用者,站在方法全局来看,当你是一个调用者时,你期望的是什么?如果你有接口交互的开发经验,我想你不会给调用接口的人抛出一个exception 或者 panic,他会不高兴的,同样你也不希望接口返回的是一堆堆栈信息,那么 在上面Java的cache中最终是返回一个Result,包含code,message,这样去看,我们对Java异常包装后是否和Go的错误设计有异曲同工之妙,同样的Go通过panic和defer,recover也可以为try,cache, throw这样的处理,但语言层面的设计的本质是不一样的(记住哦),相互的转化只是人为的在包装而已,请不要偏离正轨哦。 #### 聊聊exception和panic给Java和Go带来了什么 * 不管是Go还是Java,我们知道当程序启动后,对操作系统而言都是一个进程,Java中,在一个进程中可以启动多个线程,线程是Java的最小单位,Go中,一个进程中依然会有多个线程,但这不是最小单位,协程是Go的最小单位; * Exception在Java中的作用域是当前线程,也就是说当Exception中发生时,只会影响到当前线程的执行,终止的是当前线程。 * Panic在Go中的作用域是整个进程,当Panic发生时,如果当前协程没捕获,则整个Go的进程就会终止,这是非常可怕的。 * 所以需要开发人员在go的错误处理时需要谨慎,需要手工处理所有的err,尤其在对panic可能发生的地方需要捕获,这稍微增加了开发人员的心智负担 * 同样的,我们能看到Go的程序需要更多的严谨性,健壮性,所以在开发阶段,快速试错,让尽可能多的错误立刻出现,然后修复。 * 提醒的是,尤其是在设计一些底层框架,方法时,一定需要对panic处理,转化为err返回,否则框架抛出的panic会导致整个Go的程序结束 #### 总结 * 在我看来,并没有绝对,Java中对异常和错误有一个比较清晰的边界,通过类继承体系进行隔离,错误并不在程序员的考虑范围之内,通过异常体系和控制流程来实现业务逻辑,往往也容易被滥用;而Go中并没有,且弱化了异常的概念,并提供了将异常转化为错误的方法。一切皆错误,拥有更好的可编程性,但同时也带来诸如 `if err != nil `的这样的代码到处都是,不同的编程语言对异常错误体系设计不一样,也代表不同开发者的思想,没有对与错,个人认为都能解决特定的问题,同时也会带来一定的困扰,一定要理解这种异常错误体系设计在当前编程语言中的设计思想,才能更好的使用,写出更优雅的代码。 > Go认为: * 让程序员更直接的接触错误,从而处理 * 错误是一种可编程的值 * 强调的是,无论何时,检查错误都是至关重要的,而不是如何避免检查错误 > 事实上关于Go的错误处理也有一些最佳实践,有自己的思想,后续我也会整理,就不再这边文章中发布了,大家可以看看下面链接的文章 1. https://blog.golang.org/errors-are-values 2. https://blog.golang.org/error-handling-and-go 3. https://blog.golang.org/defer-panic-and-recover **欢迎大家关注微信公众号:“golang那点事”,更多精彩期待你的到来** ![](https://static.studygolang.com/190721/c55fa00b6c19806beda719ee62847c9f.jpg)

0 个评论

要回复文章请先登录注册