当前位置:  开发笔记 > 编程语言 > 正文

针对已检查异常的案例

如何解决《针对已检查异常的案例》经验,为你挑选了18个好方法。

多年来,我一直无法得到以下问题的正确答案:为什么一些开发人员如此反对已检查的异常?我有很多对话,在博客上阅读,阅读Bruce Eckel所说的内容(我看到的第一个人反对他们).

我目前正在编写一些新代码,并非常注意我如何处理异常.我试图看到"我们不喜欢被检查的例外"人群的观点,我仍然看不到它.

每次谈话结束时,同样的问题都没有得到答复......让我把它设置起来:

一般来说(从Java的设计方式来看),

对于永远不应该捕获的东西是错误的(VM有花生过敏而且有人丢了一罐花生)

RuntimeException用于程序员做错的事情(程序员走出数组的末尾)

异常(RuntimeException除外)适用于程序员无法控制的内容(在写入文件系统时磁盘已填满,已达到进程的文件句柄限制,您无法再打开任何文件)

Throwable只是所有异常类型的父级.

我听到的一个常见论点是,如果发生异常,那么开发人员将要做的就是退出该程序.

我听到的另一个常见论点是,经过检查的异常会使重构代码变得更加困难.

对于"我将要做的就是退出"这个论点,我说即使你要退出,你也需要显示一个合理的错误信息.如果您只是在处理错误,那么当程序退出而没有明确说明原因时,您的用户将不会过于高兴.

对于"它很难重构"的人群,这表明没有选择适当的抽象级别.而不是声明方法抛出IOException,IOException应该转换为更适合正在发生的事件的异常.

我没有使用catch(Exception)包装Main的问题(或者在某些情况下catch(Throwable)以确保程序可以正常退出 - 但我总是捕获我需要的特定异常.这样做允许我,至少,显示适当的错误消息.

人们从不回复的问题是:

如果你抛出RuntimeException子类而不是Exception子类,那么你怎么知道你应该捕获什么?

如果答案是捕获异常,那么您也会以与系统异常相同的方式处理程序员错误.这对我来说似乎不对.

如果您捕获Throwable,那么您将以相同的方式处理系统异常和VM错误(等).这对我来说似乎不对.

如果答案是你只捕获你知道的异常,那么你怎么知道抛出的是什么?当程序员X抛出一个新的异常并忘记捕获它时会发生什么?这对我来说似乎非常危险.

我会说显示堆栈跟踪的程序是错误的.那些不喜欢检查异常的人是不是觉得那样?

那么,如果您不喜欢已检查的异常,您可以解释为什么不能并且回答那些无法解答的问题吗?

编辑:我不是在寻找何时使用任何一个模型的建议,我正在寻找的是为什么人们从RuntimeException扩展,因为他们不喜欢从Exception扩展和/或为什么他们捕获异常然后重新抛出RuntimeException而不是将抛出添加到他们的方法中.我想了解不喜欢检查异常的动机.



1> Rhubarb..:

我想我读过你所做的同样的Bruce Eckel采访 - 这总是让我感到烦恼.实际上,这个论点是由受访者提出的(如果这确实是你所说的帖子)Anders Hejlsberg,他是.NET和C#背后的MS天才.

http://www.artima.com/intv/handcuffs.html

虽然我是Hejlsberg和他的作品,但这个论点总是让我感到虚伪.它基本归结为:

"检查异常是不好的,因为程序员只是通过捕获它们并解雇它们来滥用它们,这会导致隐藏和忽略的问题,否则将被呈现给用户".

通过"以其他方式呈现给用户"我的意思是如果你使用运行时异常,懒惰的程序员将忽略它(而不是用空的catch块捕获它),用户将看到它.

该论点摘要的摘要是"程序员不会正确使用它们而不正确使用它们比没有它们更糟糕".

这个论点有一些道理,事实上,我怀疑Goslings没有在Java中使用运算符覆盖的动机来自类似的论点 - 它们使程序员感到困惑,因为它们经常被滥用.

但最后,我发现这是Hejlsberg的一个虚假论点,可能是为了解释缺乏而不是经过深思熟虑的决定而创建的事后论证.

我认为虽然过度使用已检查的异常是一件坏事,并且往往会导致用户的草率处理,但正确使用它们可以让API程序员为API客户端程序员带来很大的好处.

现在,API程序员必须小心不要在整个地方抛出已检查的异常,否则它们只会让客户端程序员烦恼.非常懒惰的客户程序员会(Exception) {}在Hejlsberg警告的情况下采取捕获措施,所有利益都将丢失,地狱将随之而来.但在某些情况下,只有一个好的检查异常无法替代.

对我来说,经典的例子是文件打开API.语言历史中的每种编程语言(至少在文件系统上)都有一个允许您打开文件的API.每个使用此API的客户端程序员都知道他们必须处理他们尝试打开的文件不存在的情况.让我重新说一下:每个使用此API的客户端程序员都应该知道他们必须处理这种情况.还有一个问题:API程序员可以帮助他们知道他们应该通过单独评论来处理它,还是他们确实坚持让客户处理它.

在C中,成语就像是

  if (f = fopen("goodluckfindingthisfile")) { ... } 
  else { // file not found ...

where fopen表示失败,返回0和C(愚蠢地)让你将0视为布尔值...基本上,你学习这个成语,你没事.但是如果你是一个菜鸟并且你没有学习成语那会怎么样呢.然后,当然,你开始

   f = fopen("goodluckfindingthisfile");
   f.read(); // BANG! 

并努力学习.

请注意,我们在这里只讨论强类型语言:对于强类型语言中的API有一个明确的概念:它是一个功能(方法)的大杂烩,供您使用每个明确定义的协议.

明确定义的协议通常由方法签名定义.这里fopen要求你传递一个字符串(或者在C的情况下为char*).如果你给它别的东西,你会得到一个编译时错误.您没有遵循协议 - 您没有正确使用API​​.

在一些(模糊的)语言中,返回类型也是协议的一部分.如果您尝试fopen()在某些语言中调用等效语言而不将其分配给变量,则还会出现编译时错误(您只能使用void函数执行此操作).

我想说的是:在一个静态类型语言中,API程序员鼓励客户端正确使用API​​,如果它出现任何明显的错误,可以防止客户端代码编译.

(在一个动态类型的语言中,比如Ruby,你可以传递任何东西,比如说一个浮点数作为文件名 - 它会编译.如果你甚至不打算控制方法参数,为什么要用带有检查异常的用户.此处所做的参数仅适用于静态类型语言.)

那么,检查异常呢?

那么这里是您可以用来打开文件的Java API之一.

try {
  f = new FileInputStream("goodluckfindingthisfile");
}
catch (FileNotFoundException e) {
  // deal with it. No really, deal with it!
  ... // this is me dealing with it
}

看到那个捕获?这是该API方法的签名:

public FileInputStream(String name)
                throws FileNotFoundException

请注意,这FileNotFoundException是一个已检查的异常

API程序员对你这样说:"你可以使用这个构造函数来创建一个新的FileInputStream

a)必须将文件名作为字符串传递
b)必须接受在运行时可能找不到该文件的可能性"

就我而言,这就是重点.

问题基本上就是"不受程序员控制的事情".我的第一个想法是他/她意味着API程序员控制的东西.但事实上,正确使用时检查的异常应该适用于客户端程序员和API程序员控制之外的事情.我认为这是不滥用已检查异常的关键.

我认为文件打开很好地说明了这一点.API程序员知道你可能会给他们一个文件名,这个文件名在API被调用时证明是不存在的,并且他们将无法返回你想要的东西,但是必须抛出异常.他们也知道这会经常发生,并且客户端程序员可能希望文件名在编写调用时是正确的,但是在运行时可能由于他们无法控制的原因而出错.

因此,API明确指出:有些情况下,当您打电话给我时,此文件不存在,并且您最好处理它.

反案例会更清楚.想象一下,我正在编写一个表API.我在某个地方使用包含此方法的API的表模型:

public RowData getRowData(int row) 

现在,作为一名API程序员,我知道会有一些客户端传递一个负值表示行或表外的行值.所以我可能会想要抛出一个已检查的异常并强制客户端处理它:

public RowData getRowData(int row) throws CheckedInvalidRowNumberException

(当然,我不会把它称为"已检查".)

这是对已检查异常的错误使用.客户端代码将充满对获取行数据的调用,其中每一个都必须使用try/catch,为什么?他们是否会向用户报告是否寻找了错误的行?可能不是 - 因为无论我的表视图周围的UI是什么,它都不应该让用户进入请求非法行的状态.所以这是客户端程序员的一个错误.

API程序员仍然可以预测客户端会编写这样的错误,并应该像运行时一样处理它IllegalArgumentException.

有了一个经过检查的例外情况getRowData,这显然是一个导致Hejlsberg的懒惰程序员只是添加空捕获的情况.当发生这种情况时,即使对于测试人员或客户端开发人员进行调试,非法行值也不会显而易见,相反,他们会导致难以确定其来源的连锁错误.Arianne火箭将在发射后爆炸.

好的,这就是问题所在:我说的是,检查异常FileNotFoundException不仅仅是一件好事,而且是API程序员工具箱中的一个必不可少的工具,用于以最有用的方式为客户端程序员定义API.但这CheckedInvalidRowNumberException是一个很大的不便,导致编程错误,应该避免.但是如何分辨出来呢.

我想这不是一门精确的科学,我想这可能是Hejlsberg在某种程度上的论证.但是我很高兴把婴儿扔出这里的洗澡水,所以请允许我在这里提取一些规则来区分好的检查异常和坏的:

    客户无法控制或已关闭与开放:

    只有在错误情况不受API 客户端程序员控制的情况下,才应使用已检查的异常.这与系统的打开关闭程度有关.在客户端程序员可以控制的约束 UI中,例如,在表视图(封闭系统)中添加和删除行的所有按钮,键盘命令等,如果它尝试从中获取数据,则是客户端编程错误一个不存在的行.在基于文件的操作系统中,任意数量的用户/应用程序可以添加和删除文件(开放系统),可以想象客户端请求的文件在他们不知情的情况下被删除,因此应该期望他们处理它.

    无处不在:

    不应在客户端经常进行的API调用上使用已检查的异常.通常我的意思是来自客户端代码中的很多地方 - 不经常及时.因此,客户端代码不会尝试大量打开同一个文件,但我的表视图RowData从不同的方法到处都是.特别是,我将编写很多代码

    if (model.getRowData().getCell(0).isEmpty())
    

    每次必须包装try/catch都会很痛苦.

    通知用户:

    如果您可以想象向最终用户呈现有用的错误消息,则应使用已检查的异常.这就是"当它发生时你会做什么?" 我上面提出的问题.它还与第1项有关.由于您可以预测客户端API系统之外的某些内容可能导致文件不存在,您可以合理地告诉用户:

    "Error: could not find the file 'goodluckfindingthisfile'"
    

    由于您的非法行号是由内部错误引起的,并且没有用户的错误,因此您无法提供有用的信息.如果您的应用程序不允许运行时异常落入控制台,它可能最终会给它们一些丑陋的消息,例如:

    "Internal error occured: IllegalArgumentException in ...."
    

    简而言之,如果您认为您的客户端程序员不能以帮助用户的方式解释您的异常,那么您可能不应该使用已检查的异常.

所以那些是我的规则.有点做作,无疑会有例外(如果你愿意,请帮我改进它们).但我的主要观点是,在某些情况FileNotFoundException下,检查的异常与参数类型的API合同的一部分一样重要且有用.所以我们不应该因为它被滥用而免除它.

对不起,并不是故意让这么长时间和华丽.让我谈谈两个建议:

答:API程序员:谨慎使用已检查的异常以保持其有用性.如有疑问,请使用未经检查的例外.

B:客户端程序员:养成在开发早期创建包装异常(google it)的习惯.JDK 1.4及更高版本RuntimeException为此提供了构造函数,但您也可以轻松创建自己的构造函数.这是构造函数:

public RuntimeException(Throwable cause)

然后习惯于每当你必须处理一个检查过的异常并且你感觉很懒(或者你认为API程序员在使用检查异常时过于热心),不要只是吞下异常,包装它并重新抛出它.

try {
  overzealousAPI(thisArgumentWontWork);
}
catch (OverzealousCheckedException exception) {
  throw new RuntimeException(exception);  
}

把它放在IDE的一个小代码模板中,当你感到懒惰时使用它.这样,如果你真的需要处理已检查的异常,那么在运行时看到问题后,你将被迫返回并处理它.因为,相信我(和Anders Hejlsberg),你永远不会回到你的TODO

catch (Exception e) { /* TODO deal with this at some point (yeah right) */}


@Michael:同意你通常可以处理几个级别更高级别的IO异常 - 但是我没有听到你否认作为API客户端你要预测这些.出于这个原因,我认为检查的例外是合适的.是的,你将不得不在每个方法调用堆栈上声明"抛出",但我不同意这是混乱的.它是方法签名中有用的声明性信息.你说的是:我的低级方法可能会遇到丢失的文件,但他们会把它处理给你.我没有看到任何损失,只有好的,干净的代码才能获得
打开文件实际上是完全适得其反的检查异常的一个很好的例子.因为大多数情况下打开文件的代码不能对异常做任何有用的事情 - 最好在调用堆栈的几个层面上完成.检查的异常会强制您混淆方法签名,这样您就可以完成您原本应该做的事情 - 在最有意义的地方处理异常.
@Rhubarb:+1,非常有趣的答案,显示双方的争论.大.关于你的上一条评论:请注意,在调用堆栈上向每个方法声明抛出可能并不总是可行,特别是如果您正在实施一个接口.
这是一个非常强大的论点(我一般都赞同它),但有一种情况它不起作用:泛型回调.如果我有一些库调用一些用户定义的代码,那么该库没有办法(我知道,在java中)将已检查的异常从它调用的用户代码传播到调用它的用户代码.此问题还扩展到许多静态类型语言的类型系统的其他部分,这些部分不支持正确的泛型类型.通过提及(也许是响应)可以改善这个答案.
错了@Rhubarb.已检查的例外情况是针对可能和应该处理的"意外情况",但始终与"提前投,迟到"的最佳做法不相容.打开文件是一个挑选的例子,可以处理__.大多数IOException(例如,写一个字节),SQLException,RemoteException都无法以有意义的方式处理.失败或重试应该处于"业务"或"请求"级别,并且检查的异常(如Java库中所使用的)是一个错误,使得这很困难.http://literatejava.com/exceptions/checked-exceptions-javas-biggest-mistake/
@jpmc26你没有被强制污染SQLException抛出任何东西,这样做意味着你没有正确处理已检查的异常.抛出子句是界面的一部分,并且在其中放置实现细节是不好的设计.无论什么代码获得SQLExceptions都应该,如果它无法有效地处理它们,请捕获它们并抛出一些其他异常,以传达该错误对该函数的域的意义(可能在记录此异常的详细信息以进行调试之后),或者如果更合适,则抛出未经检查的异常.
我认为这个答案从根本上忽略了抛出异常的地方很少靠近你想要处理它的地方.因此,如果您的层次结构中有深度数据库调用抛出`SQLException`,那么您将被迫使用`throws SQLException'污染Web应用程序中的近***函数,这样您的全局处理程序就可以捕获它并返回500这种无意识的重复提供了*没有任何好处.`throws`声明变成无意义的噪音,你必须训练自己忽略,如果你这样做,你就没有得到所谓的好处.
那不是我的意思.检查异常很有用,它们迫使程序员决定如何使用抛出它们的函数在代码中处理它们(但这与我们一直争论的不同).确定代码无法有效地处理它们并将它们"转换"为未经检查的异常是可供选择的几种可能性之一.
@JamesWald:我认为关于一个方法永远不应该抛出的已检查异常的正确行为是将它们包装在某种运行时错误中; 实际上,我会进一步认为99%的关于检查异常的控制源于缺乏一种简洁的方式来表达声明性而非强制性.
@ jpmc26保持接口和实现之间的分离更为重要.如果有任何混乱,它应该包含在不泄漏到接口的实现代码中(即抛出).您的Web框架应该能够运行时异常,如果它不适合在您的代码中处理,您可能希望将SQLException包装起来.
@ jpmc26你似乎错过了未经检查的异常点.你的函数已经可能抛出一个未经检查的异常,所以是的,在某种程度上应该处理它,并且应该已经处理它,所以包装一个SQLException,你的函数在RuntimeException中无法做任何有用的事情,不会增加额外的开销.但是让你的函数抛出SQLException会产生很大的影响并污染接口.您也可能无法控制正在实现的接口并使其抛出SQLException甚至可能不是一个选项.

2> bobince..:

关于已检查异常的事情是,通过对该概念的通常理解,它们并不是真正的例外.相反,它们是API替代返回值.

异常的整个想法是,在调用链的某个地方抛出的错误可能会冒出来并由代码进一步处理,而中间代码不必担心它.另一方面,检查异常需要thrower和catcher之间的每个级别的代码声明他们知道可以通过它们的所有形式的异常.实际上,如果检查的异常只是调用者必须检查的特殊返回值,那么这在实践中几乎没有什么不同.例如[伪]:

public [int or IOException] writeToStream(OutputStream stream) {
    [void or IOException] a= stream.write(mybytes);
    if (a instanceof IOException)
        return a;
    return mybytes.length;
}

由于Java不能执行备用返回值,或者简单的内联元组作为返回值,因此检查的异常是合理的响应.

问题在于,许多代码(包括标准库的大量代码)都会滥用已检查的异常,以确定您可能非常希望捕获几个级别的真实异常情况.为什么IOException不是RuntimeException?在其他所有语言中,我都可以发生IO异常,如果我不做任何处理,我的应用程序将停止,我将获得一个方便的堆栈跟踪来查看.这是可能发生的最好的事情.

从您希望从整个写入到流的过程中捕获所有IOExceptions的示例中提取两个方法,中止该过程并跳转到错误报告代码; 在Java中,如果不在每个调用级别添加'throws IOException',即使自己没有IO的级别也不能这样做.这些方法不需要知道异常处理; 必须为其签名添加例外:

    不必要地增加耦合;

    使界面签名变得非常脆弱;

    使代码可读性降低;

    很烦人,以至于常见的程序员反应就是通过做一些可怕的事情来打败系统,例如'抛出异常','捕获(异常e){}',或者将所有内容包装在RuntimeException中(这会使调试变得更难).

然后有很多荒谬的库异常,例如:

try {
    httpconn.setRequestMethod("POST");
}?catch (ProtocolException e) {
    throw new CanNeverHappenException("oh dear!");
}

当你不得不像这样粗暴地对你的代码混乱时,难怪检查异常会收到一堆仇恨,即使这真的只是简单的不良API设计.

另一个特别糟糕的影响是控制反转,其中组件A向通用组件B提供回调.组件A希望能够让异常从其回调中返回到它调用组件B的地方,但它不能因为这会改变由B修复的回调接口.A只能通过将实际异常包装在RuntimeException中来实现,RuntimeException是更多要处理的异常处理样板.

在Java及其标准库中实现的已检查异常意味着样板,样板,样板.在一种已经冗长的语言中,这不是一场胜利.


@Mister:我所说的是,在Java中实现的检查异常在实践中更像是在C中的返回值,而不是我们可能从C++和其他Java语言中识别的传统"异常".而IMO确实会导致混乱和糟糕的设计.
+1"API替代回报值".查看已检查异常的有趣方式.
在您的代码示例中,最好将异常链接起来,以便在读取日志时找到原始原因:throw CanNeverHappenException(e);
同意标准库滥用已检查的异常肯定会增加混乱和不良捕获行为.并且,通常只是来自糟糕的文档,例如像"disconnect()"这样的拆卸方法,当"发生其他一些I/O错误"时会抛出IOException.好吧,我正在断开连接!我是在泄漏手柄还是其他资源?我需要重试吗?在不知道_why_它发生的情况下,我无法得出我应该采取的行动,所以我不得不猜测我是应该吞下它,重试还是保释.
我不同意.例外,无论是否检查,都是例外情况.示例:通过HTTP检索对象的方法.返回值是对象或者没有,所有可能变坏的东西都是例外.将它们视为在C中完成的返回值只会导致混乱和糟糕的设计.
我认为概念上将异常作为替代回报值的想法是有道理的,但我会更进一步.这是另一种**回归机制**.异常可以通过函数调用堆栈**中的多个条目传递相同的值,静默地绕过进程中的代码行.这不是正常的`return`机制可以做的事情,而是异常允许我们实现解耦的原因.最重要的是,例外是*流量控制*,与陈词滥调相反.它们更有限,更易于管理(因为对国家的更大保障)GOTO.
我喜欢"替代回报价值"的概念.除了直接调用者之外,某些异常类型不太可能对任何人都有用,即使调用者无法处理异常,调用者也应该以这样的方式将其包装起来,使调用者知道它是从嵌套中抛出的常规).我认为检查异常的一个大问题通常与更大的异常问题联系在一起:太多不同的东西都被包含在异常类型中.Java基于异常类型区分"已检查与未检查"异常这一事实会使事情变得更糟,但......

3> cletus..:

而不是重新检查所有(许多)原​​因对付已检查的异常,我只选择一个.我已经忘记了编写这段代码的次数:

try {
  // do stuff
} catch (AnnoyingcheckedException e) {
  throw new RuntimeException(e);
}

99%的时间我无能为力.最后,块会进行任何必要的清理(或至少它们应该).

我也失去了我见过的次数:

try {
  // do stuff
} catch (AnnoyingCheckedException e) {
  // do nothing
}

为什么?因为有人必须处理它并且很懒惰.这不对吗?当然.会发生吗?绝对.如果这是一个未经检查的例外而该怎么办?该应用程序将刚刚死亡(这比吞咽异常更可取).

然后我们有令人激动的代码,它使用异常作为流控制的一种形式,就像java.text.Format那样.Bzzzt.错误.将"abc"放入表单上的数字字段的用户也不例外.

好吧,我想这有三个原因.


我认为像Eclipse这样的IDE有很多可归咎于你看到空catch块的次数.真的,他们应该默认重新抛出.
"99%的时间我对此无能为力" - 错误,您可以向用户显示"无法连接到服务器"或"IO设备失败"的消息,而不仅仅是让应用程序崩溃由于网络打嗝.你的两个例子都是来自糟糕程序员的工作艺术.你应该攻击坏程序员而不是自己检查异常.当我用它作为沙拉酱时,就像是在攻击胰岛素而不帮助我的糖尿病.
但是如果正确捕获了异常,您可以通知用户,执行其他任务(记录?)并以受控方式退出应用程序.我同意某些API部件可以更好地设计.对于懒惰的程序员而言,我认为作为程序员,您对代码负有全部责任.
请注意try-catch-rethrow允许您指定消息 - 我通常使用它来添加有关状态变量内容的信息.一个常见的例子是IOExceptions添加有问题文件的absolutePathName().

4> Boann..:

我知道这是一个古老的问题,但我花了一些时间与已检查的异常进行摔跤,我还有一些要补充的内容.请原谅我的长度!

检查异常的主要原因是它们破坏了多态性.不可能让它们与多态接口很好地配合.

采取良好的'Java List接口.我们有像ArrayList和的常见内存实现LinkedList.我们还有骨架类AbstractList,可以很容易地设计新类型的列表.对于只读列表,我们只需要实现两种方法:size()get(int index).

此示例WidgetList类从文件中读取一些类型固定的对象Widget(未显示):

class WidgetList extends AbstractList {
    private static final int SIZE_OF_WIDGET = 100;
    private final RandomAccessFile file;

    public WidgetList(RandomAccessFile file) {
        this.file = file;
    }

    @Override
    public int size() {
        return (int)(file.length() / SIZE_OF_WIDGET);
    }

    @Override
    public Widget get(int index) {
        file.seek((long)index * SIZE_OF_WIDGET);
        byte[] data = new byte[SIZE_OF_WIDGET];
        file.read(data);
        return new Widget(data);
    }
}

通过使用熟悉的List界面公开窗口小部件,您可以检索items(list.get(123))或迭代列表(for (Widget w : list) ...)而无需了解WidgetList自身.可以将此列表传递给使用通用列表的任何标准方法,或将其包装在Collections.synchronizedList.使用它的代码既不知道也不关心"窗口小部件"是在现场组成,来自数组,还是从文件,数据库,或从网络中读取,或从未来的子空间中继读取.它仍然可以正常工作,因为List接口已正确实现.

除非它不是.上面的类没有编译,因为文件访问方法可能会抛出一个IOException必须"捕获或指定"的已检查异常.你不能将它指定为抛出 - 编译器不会让你,因为这会违反List接口的合同.并且没有任何有用的方法可以WidgetList自己处理异常(我稍后会详细说明).

显然,唯一要做的就是捕获并重新抛出已检查的异常作为一些未经检查的异常:

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw new WidgetListException(e);
    }
}

public static class WidgetListException extends RuntimeException {
    public WidgetListException(Throwable cause) {
        super(cause);
    }
}

((编辑:Java 8 UncheckedIOException为这种情况添加了一个类:用于IOException跨多态方法边界捕获和重新抛出.有点证明我的观点!))

所以检查过的例外根本不适用于这种情况.你不能抛弃它们.同样是Map由数据库支持的聪明人,或java.util.Random通过COM端口连接到量子熵源的实现.一旦您尝试使用多态接口的实现做任何新颖的事情,检查异常的概念就会失败.但是检查过的异常是如此阴险,以至于他们仍然不会让你安静下来,因为你仍然必须从低级方法中捕获并重新抛出任何东西,使代码混乱并使堆栈跟踪混乱.

我发现,无处不在的Runnable界面经常被支持到这个角落,如果它调用抛出已检查异常的东西.它不能按原样抛出异常,所以它可以做的就是通过捕获和重新抛出代码来混淆代码RuntimeException.

实际上,如果你诉诸黑客,你可以抛出未声明的已检查异常.JVM在运行时不关心已检查的异常规则,因此我们只需要欺骗编译器.最简单的方法是滥用泛型.这是我的方法(显示的类名因为(在Java 8之前),它在泛型方法的调用语法中是必需的):

class Util {
    /**
     * Throws any {@link Throwable} without needing to declare it in the
     * method's {@code throws} clause.
     * 
     * 

When calling, it is suggested to prepend this method by the * {@code throw} keyword. This tells the compiler about the control flow, * about reachable and unreachable code. (For example, you don't need to * specify a method return value when throwing an exception.) To support * this, this method has a return type of {@link RuntimeException}, * although it never returns anything. * * @param t the {@code Throwable} to throw * @return nothing; this method never returns normally * @throws Throwable that was provided to the method * @throws NullPointerException if {@code t} is {@code null} */ public static RuntimeException sneakyThrow(Throwable t) { return Util.sneakyThrow1(t); } @SuppressWarnings("unchecked") private static RuntimeException sneakyThrow1( Throwable t) throws T { throw (T)t; } }

欢呼!使用这个,我们可以在堆栈的任何深度处抛出一个已检查的异常而不会声明它,而不会将其包装在一起RuntimeException,并且不会使堆栈跟踪混乱!再次使用"WidgetList"示例:

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw sneakyThrow(e);
    }
}

遗憾的是,检查异常的最终侮辱是编译器拒绝允许您捕获已检查的异常,如果在其有缺陷的意见中,它不会被抛出.(未经检查的异常没有此规则.)要捕获偷偷摸摸的抛出异常,我们必须这样做:

try {
    ...
} catch (Throwable t) { // catch everything
    if (t instanceof IOException) {
        // handle it
        ...
    } else {
        // didn't want to catch this one; let it go
        throw t;
    }
}

这有点尴尬,但从好的方面来说,它仍然比用于提取包含在a中的已检查异常的代码稍微简单一些RuntimeException.

令人高兴的是,这个throw t;声明在这里是合法的,即使t检查了类型,这要归功于Java 7中添加的关于重新抛出捕获的异常的规则.


当检查异常遇到多态时,相反的情况也是一个问题:当一个方法被指定为可能抛出一个已检查的异常时,但被覆盖的实现却没有.例如,抽象类OutputStreamwrite方法都指定throws IOException.ByteArrayOutputStream是一个子类,它写入内存数组而不是真正的I/O源.它被重写的write方法不能导致IOExceptions,因此它们没有throws子句,你可以调用它们而不必担心捕获或指定的要求.

除了并非总是如此.假设Widget有一种方法可以将其保存到流中:

public void writeTo(OutputStream out) throws IOException;

声明这种方法接受普通OutputStream是正确的事情,因此它可以多态地用于各种输出:文件,数据库,网络等.和内存中的数组.但是,对于内存数组,处理实际上不会发生的异常存在虚假要求:

ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
    someWidget.writeTo(out);
} catch (IOException e) {
    // can't happen (although we shouldn't ignore it if it does)
    throw new RuntimeException(e);
}

像往常一样,检查的异常会妨碍您.如果您的变量被声明为具有更多开放式异常要求的基类型,则必须为这些异常添加处理程序,即使您知道它们不会在您的应用程序中出现.

但等等,检查异常实际上是如此烦人,他们甚至不会让你反过来!想象一下,你当前捕获的任何一个调用IOException抛出,但你想将变量的声明类型更改为a ,编译器会责备你试图捕获它说不能抛出的已检查异常.writeOutputStreamByteArrayOutputStream

这条规则会导致一些荒谬的问题.例如,三个write方法中的OutputStream一个被覆盖ByteArrayOutputStream.具体来说,write(byte[] data)是一种方便的方法,通过调用write(byte[] data, int offset, int length)0的偏移量和数组的长度来写入完整的数组.ByteArrayOutputStream覆盖三参数方法,但按原样继承单参数便捷方法.继承的方法完全正确,但它包含一个不需要的throws子句.这可能是设计中的疏忽ByteArrayOutputStream,但它们永远无法解决它,因为它会破坏与任何捕获异常的代码的源兼容性 - 从来没有,永远不会抛出的异常!

在编辑和调试期间,该规则也很烦人.例如,有时我会临时注释掉一个方法调用,如果它可能抛出一个已检查的异常,编译器现在会抱怨本地trycatch块的存在.所以我也要对它们进行评论,现在在编辑代码时,IDE将缩进到错误的级别,因为{并且}被注释掉了.尔加!这是一个小小的抱怨,但似乎唯一检查异常的事情是造成麻烦.


我差不多完成了.对于检查异常,我最后的挫败感是,在大多数呼叫站点,你可以用它们做任何有用的事情.理想情况下,当出现问题时,我们会有一个称职的特定于应用程序的处理程序,它可以告知用户该问题和/或结束或重试该操作.只有处理器高位的处理程序才能执行此操作,因为它是唯一知道总体目标的处理程序.

相反,我们得到以下习语,这是一种猖獗的方式来关闭编译器:

try {
    ...
} catch (SomeStupidExceptionOmgWhoCares e) {
    e.printStackTrace();
}

在GUI或自动程序中,将看不到打印的消息.更糟糕的是,它在异常之后继续使用其余的代码.异常实际上不是错误吗?然后不要打印它.否则,其他东西会在一瞬间爆炸,到那时原始异常对象将消失.这个成语并不比BASIC On Error Resume Next或PHP 更好error_reporting(0);.

调用某种记录器类并不是更好:

try {
    ...
} catch (SomethingWeird e) {
    logger.log(e);
}

e.printStackTrace();与在不确定状态下的代码一样懒惰并且仍在继续.此外,特定日志记录系统或其他处理程序的选择是特定于应用程序的,因此这会损害代码重用.

可是等等!有一种简单而通用的方法可以找到特定于应用程序的处理程序.它位于调用堆栈的较高位置(或者设置为Thread的未捕获异常处理程序).因此,在大多数地方,您需要做的就是将异常抛到堆栈的上方.例如,throw e;.检查异常只是妨碍了.

我确信在设计语言时检查异常听起来是个好主意,但在实践中我发现它们都很麻烦而且没有任何好处.


我是一名专业的C#开发人员,他有一些Java经验,偶然发现了这篇文章.我很困惑为什么有人会支持这种奇怪的行为.在.NET中,如果我想捕获特定类型的异常,我可以捕获它.如果我想让它被抛到堆叠上,那就无所事事了.我希望Java不那么古怪.:)
SomeStupidExceptionOmgWhoCare很好有人关心它扔掉它.所以要么它永远不应该抛出(糟糕的设计),要么你应该真正处理它.对于1.0之前的类(字节数组输出流)的不良实现也是如此,不幸的是设计很糟糕.
正确的习惯用法是一个指令,它将捕获嵌套子例程调用抛出的任何指定异常,并重新抛出它们包含在`RuntimeException`中.请注意,例程可以同时声明为`throws IOException`,并且还指定从嵌套调用中抛出的任何`IOException`应该被视为意外和包装.
很好的例子。伟大的阅读。完全同意。

5> Richard Leva..:

好吧,这不是关于显示堆栈跟踪还是静默崩溃.它是关于能够在层之间传递错误.

检查异常的问题是它们鼓励人们吞下重要的细节(即异常类).如果您选择不吞下该细节,则必须在整个应用程序中不断添加抛出声明.这意味着1)新的异常类型将影响许多函数签名,以及2)您可能会错过您实际需要捕获的异常的特定实例(例如,您打开一个辅助文件,用于将数据写入a的函数)辅助文件是可选的,因此您可以忽略其错误,但由于签名throws IOException,很容易忽略这一点).

我现在正在应用程序中处理这种情况.我们将几乎异常重新打包为AppSpecificException.这使签名真的很干净,我们不必担心throws签名爆炸.

当然,现在我们需要专门处理更高级别的错误处理,实现重试逻辑等.但是,一切都是AppSpecificException,所以我们不能说"如果抛出IOException,重试"或"如果抛出ClassNotFound,则完全中止".我们没有可靠的方法来获得真正的异常,因为当它们在我们的代码和第三方代码之间传递时,事情会一次又一次地重新打包.

这就是为什么我是python异常处理的忠实粉丝.您只能捕获您想要和/或可以处理的事物.其他一切都在冒泡,好像你自己重新开始一样(你已经做过了).

我一次又一次地发现,在我提到的整个项目中,异常处理分为3类:

    捕获并处理特定异常.例如,这是为了实现重试逻辑.

    抓住并重新抛出其他例外情况.这里发生的一切通常都是日志记录,它通常是一个陈腐的消息,如"无法打开$ filename".这些是你无能为力的错误; 只有更高的级别知道足以处理它.

    抓住所有内容并显示错误消息.这通常是调度程序的根本,它所做的一切都确保它可以通过非异常机制(弹出对话框,编组RPC错误对象等)将错误传递给调用者.


@Newtopian - 例外情况很大程度上只能在"业务"或"请求"级别处理.在大粒度下失败或重试是有意义的,而不是每个微小的潜在失败.因此,异常处理最佳实践概括为"早退,赶迟".检查异常使*更难以在正确的级别管理可靠性,并鼓励大量错误编码的catch块.http://literatejava.com/exceptions/checked-exceptions-javas-biggest-mistake/
您可以创建AppSpecificException的特定子类以允许在保持普通方法签名的同时进行分离.
那不是我说的话.你的最后一句话实际上与我同意.如果所有内容都包含在AppSpecificException中,那么它不会冒泡(并且意味着/上下文丢失),并且是的,API客户端没有得到通知 - 这正是检查异常所发生的情况(因为它们在java中)因为人们不想处理具有大量`throws`声明的函数.

6> Luke Quinane..:

SNR

首先,检查异常会降​​低代码的"信噪比".Anders Hejlsberg还谈到了命令式与声明式编程,这是一个类似的概念.无论如何,请考虑以下代码片段:

从Java中的非UI线程更新UI:

try {  
    // Run the update code on the Swing thread  
    SwingUtilities.invokeAndWait(() -> {  
        try {
            // Update UI value from the file system data  
            FileUtility f = new FileUtility();  
            uiComponent.setValue(f.readSomething());
        } catch (IOException e) {  
            throw new UncheckedIOException(e);
        }
    });
} catch (InterruptedException ex) {  
    throw new IllegalStateException("Interrupted updating UI", ex);  
} catch (InvocationTargetException ex) {
    throw new IllegalStateException("Invocation target exception updating UI", ex);
}

从C#中的非UI线程更新UI:

private void UpdateValue()  
{  
   // Ensure the update happens on the UI thread  
   if (InvokeRequired)  
   {  
       Invoke(new MethodInvoker(UpdateValue));  
   }  
   else  
   {  
       // Update UI value from the file system data  
       FileUtility f = new FileUtility();  
       uiComponent.Value = f.ReadSomething();  
   }  
}  

这似乎对我来说更加清晰.当你开始在Swing检查异常中开始做越来越多的UI工作时,开始变得非常烦人和无用.

越狱

为了实现最基本的实现,例如Java的List接口,已检查的异常作为合同设计的工具就会失败.考虑由数据库或文件系统或任何其他抛出已检查异常的实现支持的列表.唯一可能的实现是捕获已检查的异常并将其重新抛出为未经检查的异常:

@Override
public void clear()  
{  
   try  
   {  
       backingImplementation.clear();  
   }  
   catch (CheckedBackingImplException ex)  
   {  
       throw new IllegalStateException("Error clearing underlying list.", ex);  
   }  
}  

现在你必须问一下所有代码的重点是什么?已检查的异常只会添加噪声,异常已被捕获但未处理,并且按合同设计(在已检查的异常方面)已经破坏.

结论

捕获异常与处理异常不同.

已检查的异常会为代码添加噪音.

没有它们,异常处理在C#中运行良好.

我的博客上讲述这个之前.


我们到达那里:-)因此,对于API的每个新版本,您必须梳理所有调用并查找可能发生的任何新异常?公司API内部很容易发生,因为他们不必担心向后兼容性.
在该示例中,Java和C#都只是让异常传播而不处理它们(Java通过IllegalStateException).不同之处在于您可能希望处理FileNotFoundException,但处理InvocationTargetException或InterruptedException的可能性很小.
在C#方式中,我怎么知道可能发生I/O异常?此外,我永远不会从运行中抛出异常...我认为滥用异常处理.对不起,但是对于那部分代码,我只能看到你的一面.
你是说*降低*信噪比?
@TofuBeer在底层API的界面改变好事之后,是不是被迫更新你的代码?如果你在那里只有未经检查的例外,你最终会得到一个破碎/不完整的程序而不知道它.
@FrancoisBourgeois,我认为那是TofuBeer的观点.

7> Le Dude..:

Artima 发表了对.NET的一位建筑师Anders Hejlsberg 的采访,该文章深入讨论了针对已检查异常的论点.品酒师:

throws子句,至少它在Java中的实现方式,并不一定会强制您处理异常,但如果您不处理它们,它会强制您确切地确认哪些异常可能会通过.它要求您捕获声明的异常或将它们放在您自己的throws子句中.为了解决这个问题,人们做了荒谬的事情.例如,他们用"抛出异常"来装饰每个方法.这完全打败了这个功能,你只是让程序员写了更多的gobbledy gunk.这对任何人都没有帮助.


我已经读过,对我而言,他的论点归结为"那里有糟糕的程序员".
豆腐贝尔,完全没有.重点是很多时候你不知道如何处理被调用方法抛出的异常,甚至没有提到你真正感兴趣的情况.你打开一个文件,你得到一个IO异常,例如......这不是我的问题,所以我把它扔了.但顶级调用方法只是想停止处理并通知用户存在未知问题.检查过的Exception根本没有帮助.这是可能发生的一百万件奇怪的事情之一.
@yar,如果你不喜欢被检查的异常,那就做一个"抛出新的RuntimeException("我们在做Foo.bar()时没想到这个,e)"并完成它.
@ThorbjørnRavnAndersen:Java中一个基本的设计弱点,.不幸的是,它复制了它,它使用异常的类型作为决定是否应该采取行动的主要手段,以及指示事物的一般类型的主要手段那是错的,事实上这两个问题基本上是正交的.重要的不是出了什么问题,而是出现了什么状态对象.此外,默认情况下,.net和Java都假定对异常进行操作和解决异常通常是相同的,实际上它们通常是不同的.
事实上,首席架构师.
TofuBeer,我认为他真正的论点是那里有人类.而且总的来说,并不是说使用经过检查的例外所产生的痛苦小于没有它们所引起的痛苦.

8> tsimon..:

我最初同意你的意见,因为我一直赞成检查异常,并开始考虑为什么我不喜欢在.Net中检查异常.但后来我意识到我并没有像检查异常一样.

为了回答你的问题,是的,我喜欢我的程序来显示堆栈跟踪,最好是非常难看的.我希望应用程序爆炸成一堆你可能想看到的最丑陋的错误消息.

原因是,如果它这样做,我必须解决它,我必须马上解决它.我想马上知道有问题.

你实际处理异常的次数是多少?我不是在谈论捕捉异常 - 我在谈论处理它们?编写以下代码太容易了:

try {
  thirdPartyMethod();
} catch(TPException e) {
  // this should never happen
}

而且我知道你可以说这是不好的做法,那个'答案'是做异常的事情(让我猜,记录下来吗?),但在真实世界(tm)中,大多数程序员都不做它.

所以,是的,如果我不必这样做,我不想捕捉异常,而且当我搞砸时,我希望我的程序能够爆炸.无声的失败是最糟糕的结果.


有趣..自从我正确地接受了检查异常并正确使用它们后,我的程序停止在你面对顾客不满的巨大蒸汽堆中炸毁.如果在开发过程中你有一个很难看的坏堆栈跟踪,那么客户也必然会得到它们.当他看到ArrayIndexOutOfBoundsException并且在他崩溃的系统上有一英里高的堆栈跟踪而不是一个小托盘通知说他无法解析按钮XYZ的颜色配置时,D'喜欢看到他的脸,因此默认使用了软件而不是软件哼唱沿
@Newtopian - 我写了服务器和高可靠性软件,并且已经这样做了25年.我的程序已经烧毁,我使用高可用性,重试和重新连接,基于集成的金融和军事系统.我有一个绝对的客观基础来选择运行时异常.检查异常使得更难以遵循正确的"早退,赶晚"的最佳实践.正确的可靠性和错误处理处于"业务","连接"或"请求"级别.(或者偶尔在解析数据时).检查的异常妨碍了正确行事.

9> Esko Luontol..:

文章有效的Java异常解释很好时使用未经检查以及何时使用检查的异常.以下是该文章的一些引用,以突出主要观点:

意外事件: 一种预期的条件,要求采用可以用方法预期目的表达的方法进行替代响应.该方法的调用者期望这些条件并且具有应对它们的策略.

错误: 一种意外情况,它阻止方法实现其无法在不参考方法内部实现的情况下描述的预期目的.

(因此不允许使用表格,因此您可能希望从原始页面中阅读以下内容...)

偶然性

被认为是:设计的一部分

预计会发生:定期但很少

谁在乎它:调用该方法的上游代码

示例:替代返回模式

最佳映射:已检查的异常

故障

被认为是:令人讨厌的惊喜

预计会发生:永远不会

谁在乎呢:需要解决问题的人

示例:编程错误,硬件故障,配置错误,丢失文件,不可用的服务器

最佳映射:未经检查的异常


@TofuBeer - 因为Java库设计者选择将各种不可恢复的低级别故障作为_checked exceptions_,当它们显然应该_unchecked_时.例如,FileNotFound是唯一应该检查的IOException.关于JDBC - 只连接到数据库,可以合理地被认为是_contingency_.所有其他SQLExceptions应该是_failures_并且未选中.错误处理应该正确处于"业务"或"请求"级别 - 请参阅"提前抛出,赶上最晚"的最佳实践.检查异常是一个障碍.

10> David Lichte..:

简而言之:

例外是API设计问题. - 不多也不少.

检查异常的参数:

要理解为什么检查异常可能不是好事,让我们转过来问一下问题:何时或为什么检查异常具有吸引力,即为什么要让编译器强制执行异常声明?

答案显而易见:有时您需要捕获异常,这只有在被调用的代码为您感兴趣的错误提供特定异常类时才有可能.

因此,争论 checked异常是编译器迫使程序员声明其抛出异常,并希望程序员会随后也记录特定的异常类,并导致他们的错误.

但实际上,一个包com.acme只会抛出一个AcmeException而不是特定的子类.然后呼叫者需要处理,声明或重新发出信号AcmeExceptions,但仍无法确定是AcmeFileNotFoundError发生了还是发生了AcmePermissionDeniedError.

因此,如果您只对a感兴趣AcmeFileNotFoundError,解决方案是向ACME程序员提交功能请求,并告诉他们实现,声明和记录该子类AcmeException.

那为什么要这么麻烦?

因此,即使使用已检查的异常,编译器也无法强制程序员抛出有用的异常.它仍然只是API质量的问题.

因此,没有经过检查的例外的语言通常不会更糟糕.程序员可能会倾向于抛出一般Error类的非特定实例,而不是一类AcmeException,但如果他们完全关心他们的API质量,他们会学会介绍一个AcmeFileNotFoundError.

总的来说,例外的规范和文档与普通方法的规范和文档没有太大的不同.这些也是一个API设计问题,如果程序员忘记实现或导出有用的功能,则需要对API进行改进,以便您可以有效地使用它.

如果你遵循这种推理方式,很明显,声称,捕获和重新抛出在Java等语言中如此常见的异常的"麻烦"往往没什么价值.

还值得一提的是,Java虚拟机就不能有检查的异常-只有Java编译器会检查他们,并与改变的异常声明的类文件在运行时兼容.已检查的异常(仅编码样式)不会改进Java VM安全性.


你的论点反对自己.如果"有时你需要捕获异常"并且API质量通常很差,没有经过检查的异常,你就不会知道设计者是否忽略了某个方法是否会抛出需要捕获的异常.结合投掷'AcmeException`而不是'AcmeFileNotFoundError`并祝你好运找出你做错了什么以及你需要抓住它.经过检查的异常为程序员提供了一些防止错误API设计的保护.

11> Mario Ortegó..:

在过去三年中,我一直在与几个开发人员合作,处理相对复杂的应用程序.我们有一个代码库,它经常使用Checked Exceptions进行适当的错误处理,而另一些则没有.

到目前为止,我发现使用Checked Exceptions更容易使用代码库.当我使用别人的API时,我可以确切地看到当我调用代码并正确处理它们时,我可以预期到什么样的错误条件,无论是通过记录,显示还是忽略(是的,有一些有效的忽略的情况)例外,例如ClassLoader实现).这给我写的代码提供了恢复的机会.我传播的所有运行时异常,直到它们被缓存并使用一些通用错误处理代码处理.当我发现一个我不想在特定级别处理的已检查异常,或者我认为编程逻辑错误时,我将其包装到RuntimeException中并让它冒泡.永远不要没有充分的理由吞下一个例外(并且这样做的充分理由相当稀少)

当我使用没有检查异常的代码库时,它让我更难以了解在调用函数时我可以期待什么,这可能会破坏一些非常糟糕的东西.

这当然是一个偏好和开发人员技能的问题.编程和错误处理的两种方式都可以同样有效(或无效),所以我不会说有单向方式.

总而言之,我发现使用Checked Exceptions更容易,特别是在拥有大量开发人员的大型项目中.


我愿意.对我来说,他们是合同的重要组成部分.无需在API文档中详细介绍,我可以快速了解最有可能出现的错误情况.

12> Daniel A.A. ..:

例外类别

在谈论异常时,我总是回顾Eric Lippert的Vexing例外博客文章.他将例外列入以下类别:

致命 - 这些例外不是你的错:你不能阻止它,你不能明智地处理它们.例如,OutOfMemoryErrorThreadAbortException.

Boneheaded - 这些例外是你的错:你应该阻止它们,它们代表你代码中的错误.例如ArrayIndexOutOfBoundsException,NullPointerException或任何IllegalArgumentException.

烦恼 - 这些例外不是例外,不是你的错,你不能阻止它们,但你必须处理它们.他们往往是一个不幸的设计决策的结果,如投掷NumberFormatException距离Integer.parseInt,而不是提供一个Integer.tryParseInt返回上解析失败一个布尔值false方法.

外生 - 这些异常通常是例外,不是你的错,你不能(合理地)阻止它们,但你必须处理它们.例如,FileNotFoundException.

API用户:

不得处理致命愚蠢的例外情况.

应该处理烦恼的异常,但它们不应该出现在理想的API中.

必须处理外部异常.

检查异常

API用户必须处理特定异常的事实是调用方和被调用方之间方法的合同的一部分.该协议指定了以下内容:被调用者期望的参数的数量和类型,调用者可以期望的返回值的类型,以及调用者期望处理的异常.

由于API中不应存在令人烦恼的异常,因此只有这些外生异常必须经过检查才能成为方法合同的一部分.相对较少的异常是外生的,因此任何API都应该具有相对较少的检查异常.

已检查的异常是必须处理的异常.处理异常可以像吞下异常一样简单.那里!处理异常.期.如果开发人员希望以这种方式处理它,那很好.但是他不能忽视这个例外,并且已经被警告过了.

API问题

但任何检查过令人烦恼致命异常的API (例如JCL)都会给API用户带来不必要的压力.这些例外必须要处理,但任何的异常是很常见的,它不应该已经摆在首位的异常,或没有任何东西可以在搬运时完成.而使得Java开发者憎恨检查的异常.

此外,许多API没有适当的异常类层次结构,导致所有类型的非外部异常原因由单个已检查的异常类表示(例如IOException).这也导致Java开发人员讨厌检查异常.

结论

外来的例外是那些不是你的错,不能被阻止,应该被处理的例外.这些形成了可以抛出的所有异常的一小部分.API应该只检查外部异常,并且不检查所有其他异常.这将产生更好的API,减少对API用户的压力,从而减少捕获所有,吞下或重新抛出未经检查的异常的需要.

所以不要讨厌Java及其检查的异常.相反,讨厌过度使用已检查异常的API.



13> Kurt Schelft..:

实际上,检查异常一方面可以提高程序的健壮性和正确性(您不得不对接口进行正确的声明 - 方法抛出的异常基本上是一种特殊的返回类型).另一方面,你面临的问题是,由于异常"冒泡",你经常需要更改一大堆方法(所有调用者和调用者的调用者,等等),当你更改异常时方法抛出.

Java中的已检查异常不能解决后一个问题; C#和VB.NET用洗澡水甩掉了宝宝.

这篇OOPSLA 2005论文(或相关技术报告)中描述了采取中间道路的好方法.

简而言之,它允许你说:method g(x) throws like f(x),这意味着g会抛出抛出的所有异常.Voila,在没有级联变更问题的情况下检查异常.

虽然这是一篇学术论文,但我鼓励你阅读(部分)它,因为它很好地解释了检查异常的好处和缺点.



14> Newtopian..:

好的...已检查的例外情况并不理想,并且有一些警告但它们确实有用.在创建API时,存在特定的失败案例,这些失败是此API的合同.当处于强静态类型语言(如Java)的上下文中时,如果不使用已检查的异常,则必须依赖临时文档和约定来传达错误的可能性.这样做会消除编译器在处理错误时可能带来的所有好处,并且完全是为了程序员的良好愿望.

因此,一个删除Checked异常,例如在C#中完成,那么如何以编程方式和结构方式传达错误的可能性?如何通知客户端代码可能发生此类错误并且必须处理?

在处理已检查的异常时,我听到各种各样的恐怖,它们被滥用这是肯定的,但未经检查的异常也是如此.我说等待几年,当API堆叠很多层时,你会乞求返回某种结构化的意思来传达失败.

假设异常被抛到API层底部的某个地方并且刚刚冒泡,因为没有人知道甚至可能发生此错误,即使这是一种在调用代码时非常合理的错误抛出它(FileNotFoundException例如与VogonsTrashingEarthExcept相反......在这种情况下,如果我们处理它没关系,因为没有什么可以处理它).

许多人认为,无法加载文件几乎总是这个过程的世界末日,它必定会死于可怕而痛苦的死亡.所以是的..当然......好吧..你为某些东西构建了一个API,它在某些时候加载文件......我作为所述API的用户只能回复......"你到底决定我什么时候到底程序应该崩溃!" 当然,鉴于选择哪些异常被吞并并且没有留下任何痕迹或EletroFlabbingChunkFluxManifoldChuggingException,其堆栈跟踪比Marianna沟槽更深,我会毫不犹豫地采取后者,但这是否意味着它是处理异常的理想方式?我们可以不在中间的某个地方,在每次遍历到一个新的抽象级别时,异常将被重铸和包装,以便它实际上意味着什么吗?

最后,我看到的大多数论点是"我不想处理异常,许多人不想处理异常.检查异常迫使我处理它们因此我讨厌检查异常"完全消除这种机制把它放到goto地狱的深渊中只是愚蠢而缺乏凝聚力和视野.

如果我们消除了检查异常,我们也可以消除函数的返回类型,并且总是返回一个"anytype"变量......这会让生活变得如此简单,现在不是吗?


如果存在声明性的方法,说明块中的方法调用都不会抛出某些(或任何)已检查的异常,则检查的异常将非常有用,并且任何此类异常应自动包装并重新抛出.如果对被声明为抛出已检查异常的方法的调用以异常处理速度的调用速度/返回值进行交换(这样可以处理预期异常几乎与正常程序流一样快),它们可能会更有用.但是,目前这两种情况都不适用.

15> Chuck Conway..:

Anders谈到了检查异常的缺陷,以及为什么他在软件工程广播第97集中将它们从C#中删除.



16> Martin..:

正如人们已经说过的那样,Java字节码中不存在检查异常。它们只是一种编译器机制,与其他语法检查不同。我看到了很多检查异常,就像我看到编译器抱怨一个多余的条件:if(true) { a; } b;。这很有用,但我可能故意这样做,所以让我忽略您的警告。

事实是,如果强制执行检查的异常,那么您将无法强迫每个程序员“做正确的事”,而其他所有人现在都是附带的损害,他们只是讨厌您所制定的规则。

在那里修复错误的程序!不要试图修复不允许的语言!对于大多数人来说,“对异常做一些事情”实际上只是在告诉用户。我也可以向用户说明未检查的异常,因此请不要在我的API中使用已检查的异常类。



17> Piotr Sobczy..:

问题

我在异常处理机制中看到的最糟糕的问题是它引入了大规模的代码重复!说实话:在大多数项目中,95%的开发人员真正需要做的事情就是以某种方式将其传达给用户(在某些情况下,还要与开发团队进行沟通,例如通过发送e邮件与堆栈跟踪).因此,通常在处理异常的每个位置使用相同的代码行/代码块.

假设我们在每个catch块中对某些类型的已检查异常进行简单的日志记录:

try{
   methodDeclaringCheckedException();
}catch(CheckedException e){
   logger.error(e);
}

如果它是一个常见的例外,那么在更大的代码库中甚至可能有数百个这样的try-catch块.现在让我们假设我们需要引入基于弹出对话框的异常处理而不是控制台日志记录,或者开始另外向开发团队发送电子邮件.

等一下......我们真的要编辑代码中的几百个位置吗?!你明白我的观点:-).

解决方案

我们为解决这个问题所做的是引入了异常处理程序的概念(我将进一步称之为EH),以集中处理异常.对于需要处理异常的每个类,我们的依赖注入框架都会注入异常处理程序的实例.因此,典型的异常处理模式现在看起来像这样:

try{
    methodDeclaringCheckedException();
}catch(CheckedException e){
    exceptionHandler.handleError(e);
}

现在要自定义我们的异常处理,我们只需要在一个地方(EH代码)更改代码.

当然,对于更复杂的情况,我们可以实现EH的几个子类,并利用我们的DI框架为我们提供的功能.通过更改我们的DI框架配置,我们可以轻松地全局切换EH实现,或者为具有特殊异常处理需求的类提供特定的EH实现(例如使用Guice @Named注释).

这样我们就可以在开发和发布版本的应用程序中区分异常处理行为(例如,开发 - 记录错误并暂停应用程序,生成错误并记录更多详细信息并让应用程序继续执行).

最后一件事

最后但并非最不重要的是,似乎可以通过将我们的异常"向上"传递到它们到达某个顶级异常处理类来获得相同类型的集中化.但这会导致我们的方法的代码和签名混乱,并引入其他人在此线程中提到的维护问题.


发明例外是为了对它们做一些有用的事情.将它们写入日志文件或渲染漂亮的窗口是没有用的,因为原始问题不会得到解决.做有用的事情需要尝试不同的解决方案策略.示例:如果我无法从服务器AI获取我的数据,请在服务器B上尝试.或者如果算法A产生堆溢出,我会尝试算法B,它会慢很多但可能会成功.
正确的,@ PiotrSobczyk.大多数情况下,响应异常的唯一正确操作是回滚事务并返回错误响应."解决异常"的想法意味着我们没有(也不应该)拥有的知识和权威,并且违反了封装.如果我们的应用程序不是数据库,我们不应该尝试_fix_数据库.干净利落地避免写错误的数据,完全没有用_.
@ceving是的,这在理论上是好的和真实的.但现在让我们回到练习单词.请真实地回答你在真实项目中经常这样做的经历?在这个真实的项目中,`catch`块的哪一部分对exceptins做了一些真正"有用"的东西?10%会很好.产生异常的常见问题就像尝试从不存在的文件中读取配置,OutOfMemoryErrors,NullPointerExceptions,数据库约束完整性错误等等.您是否真的想从所有这些中优雅地恢复?我不相信你:).通常没有办法恢复.
@PiotrSobczyk:如果一个程序因为一个suer请求而采取了一些操作,并且操作以某种方式失败而没有损坏系统状态中的任何东西,通知用户该操作无法完成是一个非常有用的处理情况的方式.C#和.net中最大的异常失败是没有一致的方法来确定系统状态中的任何内容是否可能已经损坏.

18> Dave Elton..:

试图解决未回答的问题:

如果你抛出RuntimeException子类而不是Exception子类,那么你怎么知道你应该捕获什么?

这个问题包含似是而非的推理恕我直言.仅仅因为API告诉你它抛出的内容并不意味着你在所有情况下都以相同的方式处理它.换句话说,您需要捕获的异常取决于您使用抛出异常的组件的上下文.

例如:

如果我正在为数据库编写连接测试程序,或者检查用户输入的XPath的有效性,那么我可能想要捕获并报告操作引发的所有已检查和未检查的异常.

但是,如果我正在编写处理引擎,我可能会以与NPE相同的方式处理XPathException(已检查):我会让它运行到工作线程的顶部,跳过该批次的其余部分,记录问题(或将其发送给支持部门进行诊断)并留下反馈给用户以联系支持部门.


究竟.简单直接,异常处理方式.正如Dave所说,正确的异常处理通常在_high level_完成."早退,赶晚"是原则.检查异常使这很困难.
推荐阅读
Life一切安好
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有