Java受检异常的一些思考
什么是异常?
要了解受检异常,首先要了解什么是异常。
在Java中,异常是一套能够一致地处理错误和恢复代码运行正常的机制。
在C语言中,他没有异常处理机制。如果一个函数出现了异常情况,例如一个除法的函数,被除数输入了0,这个时候需要把这个异常告诉函数调用者,一般情况下我们会通过返回一个特殊的值来告诉调用者出现异常,如返回-1。这是c语言常规的处理异常手法。这会引发另一个问题:我们需要在每次调用函数的时候,都进行条件判断,看返回值是否出现了异常,这样会让我们的代码出现了非常多的if判断语句。《Java编程思想》一书中讲到:每次调用的时候都必须执行条件测试,以确定会产生何种结果。这使程序难以阅读并且有可能降低运行效率,因此程序员们既不愿意指出,也不愿意处理异常
不仅代码不美观和写很多的代码,而且容易漏掉一些情况没有进行判断,没有安全感。
这个是否需要有一套完整的异常处理机制,当出现问题的时候,把异常交给机制去处理,这样我们就可以把处理异常的代码和正常的代码分开,且不担心出现“漏网之鱼”。因而我们需要:
一套统一的错误报告机制,可以减少代码量、代码简洁的同时,能够把错误进行统一处理,不会有漏网之鱼。
关于这个问题在《Java编程思想》中是这样说的:异常往往能降低错误处理代码的复杂度。如果不使用异常,那么就必须检查特定的错误,并在程序中的许多地方去处理它。而如果使用异常,那就不必在方法调用处进行检查,因为异常机制将保证能够捕获这个错误。理想情况下,只需在一个地方处理错误,即所谓的异常处理程序中。这种方式不仅节省代码,而且把“描述在正常执行过程中做什么事”的代码和“出了问题怎么办”的代码相分离。总之,与以前的错误处理方法相比,异常机制使代码的阅读、编写和调试工作更加井井有条。Java异常机制比常规的错误报告机制还做到了:
我们可以在一个地方集中处理错误,把正常的代码处理逻辑和错误情况处理逻辑分离,使用代码的阅读、编写和调试更加井井有条。
编译时还是运行时?
熟悉Java开发的读者知道Java异常机制拥有两种大类型的异常:运行时异常和编译时异常。
编译时异常是在编译的时候就能被检查出来的异常,也被称为受检异常(Checked Exception)。如IOException。运行时异常就是在运行的时候才会被检查的异常,如空指针异常。
而我们今天要讨论的就是关于运行时异常。这两种异常的本质差别是:检查的时机。一般情况来说,错误能在编译阶段就被检查出来是最好的,编译器帮我们解决错误。然而情况也不一定都是这样,因为处理异常需要我们写出更多的代码。如果每次调用一个方法都需要处理所有的异常,那么我们需要的工作量就太大了。例如空指针,我们知道几乎每个方法都会抛出这个异常,但是java并没有强制我们去捕获他。所以异常处理时机的分类是一个权衡的结果,既想让编译器尽多地帮我们检查错误,又不能让开发者代码编写太多额外的代码,需要在这两者之间找到一个最平衡的点。
“受检异常”究竟可不可取?
这个问题其实争论的历史非常久,新型语言Kotlin中就把受检异常去掉了。这样我们就没必要再去显示捕获任何异常。这在我第一次接触kotlin的时候感觉简直是一个噩梦:
因为我不知道调用的方法是否会抛出异常、抛出什么异常,那么我可能会忽略一些关键的异常没有捕获而导致代码的健壮性降低。
这也确实也是很多支持Java受检异常开发者的心声。有了受检异常,那么编译器会提醒我们我们调用的这个方法会抛出什么异常,我们需要去使用try-catch来捕获他,不会遗漏任何重要的异常。但事实真的如此吗?
受检异常真的能够避免所有的异常吗?不是的,我们依旧会出现空指针异常、非法状态异常等,我们并不会去显示捕获这一类的异常,他在java中称为运行时异常。那有读者可能会说:受检异常是可以让我们避免一些方法显示抛出的异常,并不是要处理所有的错误。因而,我们必须呀承认一个事实:无论是否有受检异常,均不能解决所有的异常,两者的差别是编译阶段处理异常的数量。而这两者的边界是一种权衡的结果使用受检异常,我们真的处理异常了吗?
CLU 的设计师们认为:我们觉得强迫程序员在不知道该采取什么措施的时候提供处理程序,是不现实的,只有在你知道如何处理的情况下才捕获异常。
Stroustrup 这样认为:这样一来几乎所有的函数都得提供异常说明了,也就都得重新编译,而且还会妨碍它同其他语言的交互。这样会迫使程序员违反异常处理机制的约束,他们会写欺骗程序来掩盖异常。这将给没有注意到这些异常的人造成一种虚假的安全感
我们会发现现在的一种情况是:开发者捕获异常之后,并没有做具体的处理,而是对异常进行打印,或者干脆不做任何处理,这是不符合异常的规则的。只有知道如何处理异常的情况下才去捕获异常。如果捕获异常之后却不做任何处理,那么实际上是把异常“吃了”。因为如果异常发生了,我们以为我们处理了,但事实上我们并没有做任何处理,那么这个错误就会一直存在,反而更不利于我们对代码健壮性的维护。有读者可能会觉得,我们对于不知如何处理的异常可以往上抛,这会导致另一个问题:顶层接口的调用者会拿到非常多的异常类型而不知如何处理,最终只能向上抛给控制台,从而使得整套异常机制完全失效。
我们知道异常机制的目的之一就是为了减少代码,不用每次调用一个方法都去进行条件判断,而通过使用try-catch就可以把错误进行集中处理,把正常代码的逻辑和处理异常的逻辑分离开来,但是,受检异常的存在,当我们即使调用一个Thread.sleep()方法都必须进行try-catch,又如IO流的开启与关闭也都必须进行try-catch,,这样的代码量其实又回到原地了,并没有达到最初的目标:减少代码量。
《Java编程思想》一书认为:不在于编译器是否会强制程序员去处理错误,而是要有一致的、使用异常来报告错误的模型;不在于什么时候进行检查,而是一定要有类型检查。也就是说,必须强制程序使用正确的类型,至于这种强制施加于编译时还是运行时,那倒没关系。
即异常机制的重点在于一致的使用报告错误的模式和强制程序使用正确的类型。所以异常的存在,并不是要我们在编译时把所有异常都处理了,而是我们可以统一处理异常且让程序使用正确的类型。
我的观点
上面讲到我第一次接触到kotlin的时候非常反感他把checkedException去掉了,这让我非常没有安全感,我的原因是:调用别人的接口的时候,这时候没有编译器的提醒我会遗漏关键的异常而导致代码的健壮性下降。但,正如上述,如果不知道如何处理异常而去捕获异常,然后不做任何处理,这是在欺骗自己,获得一种虚假的安全感。我们并没有解决异常,而是把异常“吃了”,看起来程序并没有崩溃,但事实上我们只是把异常藏起来了,没有做任何处理。这种情况不如把异常往上抛到最顶层交给控制台处理,我们知道程序崩溃了,那么我们就知道得去解决这种异常,这样才能真正促进代码的健壮性。
而向上抛会会导致另一个问题:顶层接口的调用者会拿到非常多的异常而不知道如何处理,再次向上抛,最终导致整个异常机制失效。而如果每一层都只处理自己感兴趣的异常,剩下的异常就会自动抛到最顶层,代码会更加简洁,我们也可以更加专注我们要处理的异常。
这两个是我觉得“受检异常”的硬伤,。而其他,如频繁try-catch导致代码量增多,两种类型的代码导致逻辑不连贯等,相对来说这属于软伤,在面对“可以在编译阶段解决更多的异常并没有更强的说服力。
去掉受检异常对于java工程师来说可能会很没有安全感,因为:会存在一些异常没有处理,他们就像一些炸弹,但不知道他们什么时候爆炸。这确实是个问题,没有受检异常会有更多的异常留到运行时。但这个问题影响的效果可能并没有我们想象中的那么大。我们真正处理受检异常机制提醒的异常其实并不多,正如我上面所讲,如果我们捕获异常之后只是“吃掉它”,那么还不如抛给控制台。
那么“受检异常”是不是就一无是处?我觉得不是的。受检异常的目的是在编译阶段处理异常,去掉确实可以避免上述的两个问题,且让代码更加的简洁。但是换之的是需要给测试人员更多的压力和开发者自身的对代码健壮性的处理,需要有更多的精力去解决没有“受检异常”带来的问题。不是有“全局捕获异常吗”?频繁的崩溃和维护,并不是一个好的解决方案,如果能在编码阶段就把隐藏的问题解决是最好不过的。
从我自己的经历来说,我使用java和kotlin语言均开发过不同的项目,而没有受检异常并没有给我带来多大的障碍,反而不用使用try-catch让我在编写代码的时候更加舒适。但因我项目较小,所以可能并不能作为一个示例来证明,仅作为一个参考。而kotlin在这方面有一个我认为不好的特点:没有异常声明。即如果我想要看我调用的方法究竟会抛出什么异常时,需要一行一行代码去查看,而无法通过异常声明来得知。
所以,需要怎么做?我认为这两种解决方案各有千秋,看开发者如何取舍。而我个人觉得这是这种异常机制的限制,我们需要的是对异常机制的改进,如Java有了进行注解对异常进行忽略,这是一种进步。对这个问题的讨论正如对洋葱好不好吃的讨论是差不多的,不同的团队不同的需求取舍,会有不同的结果。讨论这个问题更重要的是了解不同的解决方案有不同的优缺点,我们在开发的时候,可以发挥不同处理方案的优势,注意不要踩坑。
没有最好的语言,只有更好的开发者。如果我们能够在每层都解决自己应该负责解决的异常,那么顶层接口就不会有高达八十多种异常的出现。如果对每种异常捕获之后都能够做好对应的处理,而不是把异常“吃掉”来获得虚假的安全感,那么受检异常也没有那么多的问题。如果我们可以更加规范地开发,那么受检异常,事实上问题也不会那么多。简单粗暴去除掉受检异常,可以通过迎合大部分开发者的惰性和不规范来解决受检异常存在的问题,但是这并不是最好的解决方法。所以我们能做的,不是去捍卫不同的立场,而是不断地提升自己的代码规范,通过代码规范来增强代码的健壮性。
以上就是Java受检异常的一些思考的详细内容,更多关于Java受检异常的资料请关注我们其它相关文章!