Java真的需要Checked Exception?

Java真的需要Checked Exception

?

尽管C++首次引入异常的规范,但是Java是强制实施"检查的异常"Checked Exception)规范的唯一的主流语言。 在这个讨论中,我将考察这个实验品的动机和结果,并且提出另一种 (并且也许更有用)管理异常的方法。 这个讨论的目标是探索这个想法 -- 尤其,你使用异常的经验是什么?使用非检查的异常Unchecked Exception)将对你更有利吗?

Note你在这里能找到一篇相关散文。

当异常被引入C++委员会时,我开始了解它们,并且这是一条长的学习曲线。 引入异常的首要理由之一是,他们将使得程序员写较少的错误检查代码,因为这些代码可以被放在后面的一个更合适的地方, 而不是必须放在每个函数调用点处 尽管很少有人会照做。 实际上, 我认为:C++里的异常处理的主要动机是修正C所带来的差劲(poor)的异常处理模型, 因为我们真正需要的是一种统一和一致的报告错误的模型 (不幸的是,因为C++ 向后与C相容,在C++里的异常处理仅仅是一个附加的错误处理模型)

初看起来,“检查的异常”好象真的是一种好想法。 这一切基于我们未经检验的假设--静态的类型检查能探测出你的问题并且总是最好的。 Java是第一个 (我所知道的)采用检查的异常的语言,因此就变成了第一个实验品。 不过,你必须在原有的代码外围再写异常处理代码,正是这些我们常见的swallowed异常(泽者注:其实就是我们常说的异常匹配)的现象使得我们开始认为这其中有问题。 Python里,异常是未经检查的, 并且如果你想要,你能抓住他们并且做某些事情,但是你不是被迫的。 并且好象工作的很好。

我认为,从根本上讲,这是“编译时检查”与“运行时检查”的较量。 我们习惯于认为“编译时检查”是唯一正确、安全和可靠的做法,我们自然会把任何依赖“运行时检查”的做法认为是不可靠。 这种思想来自C++ 但我需指出:Java实际上在运行期做了相当多的事情,我们仍然接受它是因为它们不能在编译期做! 尽管如此,我们仍然坚持:如果可能在编译期做的, 那么这就是唯一的适当的时间 (可参考Python中的弱类型检查) 我知道这好象是一个不怎么准确和可证明的思维方式;但当你有了反驳常规思维方式的经验后,你就可以质疑它了(不懂?me 2)。

我从去年夏天开始这个讨论,并且我已经开始收到其他人的来信,关于 --”检查的异常是一个错误。 PythonC C++中都没有。 实际上, 据说所知只有在Java中存在, 并且我将打赌这是因为人们在C++里看到了未经检查的异常规范并且认定那是一个错误(我知道我也是, 很长时间以来) 基于这一点, 我感到好象检查的异常(1)还没有试验过, 就把它们放进Java的实验品(除非你知道另一些语言中实现了... Ada,或许?) (2)一个失败,因为那么多人end up swallowing那些异常。

Python/C#中, 异常被抛出, 如果你愿意你可以写代码去捕捉之; 当然,如果你不愿意,你不会被迫写一段额外的代码去swallow该异常。

我目前正计划重写异常这一章(及相关的其它章节),在其中会一改以前处理异常的方式,并把它加入第3 版中(译者注:当然是大名鼎鼎的Java编程思想》)。

(现在)认为异常是这样的:

1)异常的巨大的价值在于它使用统一的方式报告错误: 使用标准机制以报告哪个错误, 而不是我们在C中使用的可忽略的方法的混合 (因此,C++,这只给这个混合体添加了异常,但并没有使它成为专有的方法) Java相对于C++的一个很大的优势也在于其异常是报告错误的唯一的方法。

2)在前面的段落中提及的"可忽略"是另一个问题。 其理论是:如果编译器强迫程序员或者处理异常,或者把它传递下去, 那么程序员的注意力就会放在可能的错误上,这样他们将能正确地处理它们。 我认为问题在于:这只是语言的设计者站在心理学的角度上做出的一个未经验证的假定而已。 我的理论是:当某人正试图做事情时,你经常用烦恼事去惹他们, 那么他们将用可能的最便捷的方法去搞定它,以把烦恼事赶走;当他们完成了这件事,或许他们将返回并且把这种方法去掉,带着傲慢的神情。 我发现我曾在Java编程思想》的第一版里这样写过:

...
} catch (SomeKindOfException e) {}

在后面的章节里或多或少忘记了(译者注:即没有遵从此规范),直到书的再版。 但又有多少人认为这是一个好的范例例而跟随它? 我开始阅读与此相关的代码,并且意识到人们正掐灭(stubbing)异常,异常正在消失。 高高在上的检查的异常正起着与它预期相反的效果, 甚至可能发生在你的经历中(并且我现在相信检查的异常是一个基于某人认为是一种好想法的实验,直到最近我还一直认为这是一个好的想法)

我开始使用Python的时候,全部异常都出现了,没有一个会偶然"消失" 如果你想要抓住一个异常,你能, 但是你没被迫因仅仅为了传递异常而写大量代码。 他们一直被传递到你想要抓住他们的地方, 如果你忘记在某个地方捕捉,他们则会一直走出去 (因此他们提醒你),但是他们不消失,这是在所有可能的情况当中最坏的。 我现在相信检查的异常鼓励人们让异常消失, 而且这使得代码不可读。

最后, 我认为在“以为”关于Java的异常的一切是好的之前,我们必须意识到检查的异常的做为实验品的本性并且仔细检查他们。 我相信采用单一的机制处理异常是极好的, 并且我相信使用一条单独的通道使得异常在其中移动(异常处理机制)也是好的。 但是我确实记得在C++里的异常处理的早的论点之一是, 它将允许程序员把工作代码部分与处理差错代码部分分开,但据我看来,“检查的异常”没有做到这一点; 相反,它们(译者注:指异常处理代码)倾向于闯入(很多)进你的"正常的工作代码",因此这是一个倒退。 我使用Python异常的经验支持我的这一观点,and unless I get turned around on this issue I intend to put a lot more RuntimeExceptions into my Java code.

一件事情已经变得对我非常清楚,特别因为Python 你越是向程序员随便的堆砌规章,特别是一些无助于解决现有问题的规章,程序员的生产效率就越低。并且这个因素的影响似乎不是线性的,而是指数级的。

我有一二份报告, 指出:在工业级的代码中,检查的异常是如此大的问题(getting swallowed),以致于他们想改变这种情形。在你看来,有这种想法的多数程序员可能只能被归类为初学者。 但我已经在研究班(seminars)再三看见这种情形 -- 有多年的编程经验的人甚至不理解一些最基本的事情。

或许这只是时间问题。 当异常被介绍给C++委员会时,我开始努力地理解异常的想法(idea), 并且过了很久我终于有所领悟。(and after this much time it suddenly hit me. 而且我觉察到:是使用有异常的一种语言所致,但不是“检查的异常”。 我认为它是两全其美 -- 如果我想要抓住异常,我能,但是我没被强迫仅仅为了避免写大量代码而swallow它。 如果我不想,我则忽略它们,如果真的出现了异常,则它会在调试期间向我报告,然后我将决定怎样办理它。 我仍然能够处理异常,但是我没有被迫为了异常写一串代码。

这是我在Heinz Kabutz的帮助下开发的一个工具。 它把所有的“检查的异常”转换成一个RuntimeException,同时还保留了“检查的异常”的所有信息。

import java.io.*;
class ExceptionAdapter extends RuntimeException {
? private final String stackTrace;
? public Exception originalException;
? public ExceptionAdapter(Exception e) {
??? super(e.toString());
??? originalException = e;
??? StringWriter sw = new StringWriter();
??? e.printStackTrace(new PrintWriter(sw));
??? stackTrace = sw.toString();
? }
? public void printStackTrace() { 
????printStackTrace(System.err);
? }
? public void printStackTrace(java.io.PrintStream s) { 
????synchronized(s) {
????? s.print(getClass().getName() + ": ");
????? s.print(stackTrace);
??? }
? }
? public void printStackTrace(java.io.PrintWriter s) { 
????synchronized(s) {
????? s.print(getClass().getName() + ": ");
????? s.print(stackTrace);
??? }
? }
? public void rethrow() { throw originalException; }
} 

?

原先的异常被储存在originalException里,因此你总是能恢复它。 另外,stack trace信息被抽取出来,放到字符串变量stackTrace中,于是它将被使用printStackTrace()打印出来, 如果异常一直没有捕捉而到了控制台。 但是, 你同样可以在你的程序的较高的层次上放置一个catch语句来捕捉ExceptionAdapter异常,然后再寻找某个具体的异常,如下所示:

catch(ExceptionAdapter ea) {
? try {
??? ea.rethrow();
? } catch(IllegalArgumentException e) {
??? // ...
? } catch(FileNotFoundException e) {
??? // ...
? }
? // etc.
} 

这里, 你仍能抓住某种具体类型的异常,但是你没有被迫在异常源和被捕捉的任何地方之间写上全部异常说明和try-catch语句。更重要的,没有人被引诱(temptedswallow此异常,然后再除去它。 如果你忘记抓住一些异常,它将一直向上传递。 如果你想要传递的某个中途抓住异常,你同样可以。

或者,因为originalExceptionpublic的,你也能借助RTTI来寻找某种类型的异常。

这是一些测试代码,只是保证上面的示例能够工作(但,这并不是我建议的使用方式)

public class ExceptionAdapterTest {
? public static void main(String[] args) {
??? try {
????? try {
??????? throw new java.io.FileNotFoundException("Bla");
????? } catch(Exception ex) {
??????? ex.printStackTrace();
??????? throw new ExceptionAdapter(ex);
????? }?? 
????} catch(RuntimeException e) {
????? e.printStackTrace();
??? }
??? System.out.println("That's all!");
? }
}

借助于这个工具,你能享受到未检查的异常的好处(更少、更清晰的代码),同时我们也不会丢失关于此异常的核心信息。

如果你想在代码里抛出某种类型的“检查的异常”,你可以像下面这样使用ExceptionAdapter(如果不太适合,则可以适当修改之):

?if(futzedUp)
??? throw new ExceptionAdapter(new CloneNotSupportedException());

?

这表明:你可以使用“未检查的异常”的编程风格,容易地以它们的原先的角色使用全部异常。

?

译者注:

1、?????????? 此文章提出:当你准备抛出一个异常时,你可以尝试把它封装在ExceptionAdapter对象中,并抛出此对象。这样一来,虽然某个方法抛出了异常,但在调用它的方法中并不必须捕捉之,因为ExceptionAdapter对象的父类是RuntimeException,而RuntimeException是非检查的;

2、?????????? 这篇文章是探讨性、前沿性的。因为近段时间我在看C#,搞不懂为什么在C#中抛弃了Java中我认为绝好的异常处理方法,于是我便找到了这篇文章;

3、?????????? 我译出这篇文章,并不代表我支持它的观点。以此共勉!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值