检查异常的优缺点是什么?

cra*_*her 11 c# java exception

您是否更喜欢在Java中检查异常处理或在C#中未经检查的异常处理以及为什么?

Ran*_*pho 16

咩.

如果使用得当,检查异常是一件好事,但通常会导致以下内容:

doSomething();
try
{
  somethingThrowsCheckedException();
}
catch(ThatCheckedException)
{ }
doSomethingElse();    
Run Code Online (Sandbox Code Playgroud)

而且,坦率地说,那是错的.你应该让你不处理的异常冒泡.

正确使用的检查异常可能很好.但是很常见,正确执行检查异常的结果是这样的方法签名:

public void itMightThrow() throws Exception1, Exception2, Exception3, Exception4, // ...
Exception12, Exception13, /* ... */ Exception4499379874
{
  // body
}
Run Code Online (Sandbox Code Playgroud)

我夸大了吗?只是轻微.

编辑:

也就是说,在异常处理方面,我更喜欢C#而不是Java,这与检查异常无关(如果我使用Spec#,我可以得到它).不,我喜欢的是C#中的堆栈跟踪在您抛出异常时填充,而不是在实例化 Java 中的堆栈跟踪时填充.

编辑2:这是评论者@Yishai,@ Eddie,@ Bill K:

首先,您应该查看此线程,以获取有关如何在不实例化异常的情况下获取堆栈跟踪的信息.请记住,走堆栈是一个繁重的过程,不应该定期进行.

其次,我喜欢在throwal而不是在实例化时填充C#的异常堆栈跟踪的原因是你可以这样做:

private MyException NewException(string message)
{
   MyException e = new MyException(message);
   Logger.LogException(message, e);
   return e;
}

// and elsewhere...
if(mustThrow)
{
   throw NewException("WHOOOOPSIEE!");
}
Run Code Online (Sandbox Code Playgroud)

如果没有NewException堆栈跟踪中包含的方法,这是Java无法做到的技巧.

  • 您可以用任何语言编写错误的代码.至少在那些情况下你可以立即看到程序员是个白痴. (5认同)

Rem*_*anu 15

我认为已检查的例外是一个失败的实验.异常的最初目标是消除验证每个函数调用返回的需要,这导致程序程序难以阅读,并且可能效率低下,从而阻止程序员发信号和处理异常.

虽然在纸面上很棒,但在实践中,已检查的异常重新引入了同样的问题异常应该首先消除.它们在应用程序层之间添加了紧密耦合.它们使库无法在后续版本中更改其实现.crausher发布链接详细介绍了问题,并且比以往任何时候都更好地解释了这些问题.

  • B可以将C的异常包装在其自身中,从而使其对A透明.Rethrowing介绍了耦合,我同意.公开实现的已检查异常会引入耦合.但经过检查的例外情况并不要求您这样做.没有经过检查的例外情况,那你不是说A必须假设B可以抛出任何东西吗?那么特定的异常层次结构会成为调用者真正忽略的实现细节吗?如果是这样,为什么要允许继承子类化? (3认同)

Yis*_*hai 11

我更喜欢检查异常,以防止您无法提前预测的错误.例如,IOException或SQLException.它告诉程序员他们必须考虑到不可预测的错误,他们无法编写不会抛出异常的健壮代码,无论他们尝试多少.

程序员太多次将已检查的异常视为要处理的语言事物.它不是(或者不会在设计良好的API中) - 它表明操作中存在不可预测的行为,并且您应该依赖于操作的确定性结果,在相同输入的情况下始终工作相同.

话虽如此,在实践中检查的例外有两件事:

  1. 并非所有用Java编写的应用程序都需要这种健壮性.用于关闭已检查异常的编译器级别标志会很好 - 尽管这会导致API在开发人员处理设置为关闭它的标志时滥用已检查的异常.在考虑了更好的压缩之后,我目前的想法是编译器警告是最好的平衡.如果检查的异常是编译器警告,包括编译器警告,如果忽略了几个层(因此忽略一个将被编译到类中的事实),这样调用者至少知道捕获异常,即使他不能知道哪一个,然后那些不关心的人会忽略编译器警告,而那些做不了的人,没有任何人被迫编写错误处理代码,他们不关心让他们的代码进行编译.
  2. 异常链接花了太长时间(版本1.4)来介绍.缺乏异常链导致了很多早期开发的坏习惯,而不是每个人都在做:

    抛出新的RuntimeException(e);

当他们不知道该怎么做.

此外,检查异常是另一个可能出错的API设计元素,并且API的用户必须承受设计缺陷.

编辑:另一个答案指向两个问题,促使C#设计决定没有检查异常.在我看来,这两个论点都很糟糕,所以我认为它们值得解决/平衡.

  1. 版本.这个论点是,如果您更改了API实现并希望添加其他已检查的异常,那么您将破坏现有的客户端代码.
  2. Scallability.在您知道它之前,您有一个方法可以抛出15个已检查的异常.

我认为这两个版本都受到了无法解决的问题,即当这些评论被提出时,已经接受了处理检查异常的正确方法,该方法可以通过包含适合于API抽象的不同检查异常来实现. .例如,如果您有一个可以处理IOException,SQLException或XML相关异常的存储API,那么正确设计的API会隐藏一般PersistanceException背后的差异或类似的东西.

除了一般的设计指导外,具体而言,论证确实引发了很多关于替代方案的问题:

  1. 版本.因此,开发人员针对您的数据库API开发,认为他们捕获并处理了相关的异常(比如DatabaseException),然后您决定在下一个版本中添加NetworkException以捕获与数据库的网络级通信问题.现在你刚刚打破了现有代码的所有兼容性,编译器甚至都不会抱怨它.如果幸运的话,每个人都可以在回归测试中发现它.
  2. 可扩展性.在C#解决方案中,如果有三个API级别可能会访问volatile资源,那么您完全依赖于API文档,因为编译器不会告诉您.

对于网络应用程序来说,这是一个很棒的设计,在这个应用程序中死亡和向用户显示一个很好的错误500页是关于所有人都在做的事情(因为交易是由容器处理的).但并非所有应用程序都考虑到这些要求.

争论最终沸腾了(无论如何):不要担心异常,任何事情都可能出错,只是建立一个全能.

好.这是已检查和未检查的异常方法之间的核心区别.已检查的异常会向程序员发出无法预测的不可预测的呼叫.未经检查的异常方法只是假设所有错误条件都属于同一个类,它们只是具有不同的名称,并且它们被取消选中,以便没有人可以捕获它们.

现在这些论点在CLR层面确实有价值.我同意所有已检查的异常应该在编译器级别,而不是运行时级别.

  • 究竟.检查的异常都是关于API的,就像接口一样.接口也有版本问题,但它们仍然是不可或缺的 (2认同)

Dan*_*ner 7

我从未使用过Java,但是自从我读过

我很确定我不喜欢已检查的异常(在当前的实现中).

提到的两个要点如下.

Versionability

Anders Hejlsberg:让我们从版本开始,因为问题很容易在那里看到.假设我创建了一个声明它抛出异常A,B和C的方法foo.在foo的第二个版本中,我想添加一些功能,现在foo可能会抛出异常D.这对我来说是一个彻底的改变.将D添加到该方法的throws子句中,因为该方法的现有调用者几乎肯定不会处理该异常.

在新版本中向throws子句添加新异常会破坏客户端代码.这就像在界面中添加方法一样.在发布接口之后,它实际上是不可变的,因为它的任何实现都可能具有您要在下一个版本中添加的方法.所以你必须创建一个新的界面.与异常类似,您可能必须创建一个名为foo2的全新方法,该方法会抛出更多异常,或者您必须在新foo中捕获异常D,并将D转换为A,B或C.

可扩展性

Anders Hejlsberg:可扩展性问题与可版本性问题有些相关.在小的,经过检查的例外中非常诱人.举个小例子,你可以证明你实际上已经检查过你发现了FileNotFoundException,并不是很好吗?好吧,当你只是调用一个API时,这很好.当您开始构建与四个或五个不同子系统通信的大型系统时,麻烦就开始了.每个子系统抛出四到十个异常.现在,每当你走上聚合的阶梯时,你就有了这个指数层次结构,在你必须处理的异常之下.您最终必须声明可能抛出的40个异常.一旦你将它与另一个子系统聚合,你的throws子句中就有80个例外.它只是气球失控.

在大型的,经检查的异常变得如此恼怒,人们完全绕过这个特征.他们要么说,"抛出异常",到处都是; 或者 - 我不能告诉你我见过多少次 - 他们说,"试着,哒哒哒哒哒,卷曲的卷曲." 他们认为,"我以后会回来处理这些空的捕获条款",然后他们当然不会这样做.在这些情况下,经过检查的异常实际上降低了大型系统的质量.

  • 所以你很确定你不喜欢你从未使用过的东西,看过一个有说服力的人认为它是"坏"的吗?对于上述两点,有非常合理的论据. (3认同)

Edd*_*die 5

好吧,我不打算回复,但这需要太长时间才能关闭并在围栏的一侧得到很多答案,所以我觉得需要在另一边权衡.

我支持检查异常 - 正确使用时 - 并相信它们是一件好事.我上面已经多次听过所有的论点,并且在一些反对检查异常的论据中有一些优点.但在网上,我认为他们是积极的.使用C#和Java进行编程后,我发现C#程序更难以对Exceptions进行稳定.检查异常的好处是保证 JavaDoc 可以告诉您可以从该方法抛出异常.使用C#,您依赖于编码器记住告诉您可以从任何给定方法抛出哪些异常,以及可以从该方法调用的任何方法抛出哪些异常,等等.

如果要创建5-9的可靠代码,您需要知道可以从您调用的代码中抛出哪些异常,这样您就可以推断出可以从中恢复的内容以及必须放弃正在执行的操作的原因.如果是C#,你可以这样做,但它会涉及大量的反复试验,直到你看到所有可能抛出的异常.或者你只是抓住了Exception并尽力而为.

这两种方法都有利有弊,即Java和C#.合理的论据可以有利于两者,也有利于两者.同样,在网上,我更喜欢Java选择的方法,但是我今天要重新编写Java,我会更改API以将一些已检查的异常更改为运行时异常.Java API在使用已检查的异常时不一致.正如其他人所说,Exception链接花了很长时间才能成为标准API功能和JVM的一部分.

然而,经过检查的例外情况下的收费往往属于"懒惰的程序员滥用这种语言特征"的范畴.确实如此.但是许多语言及其功能都是如此."懒惰的程序员"论证是一个弱点.

让我们来解决那些不属于"懒惰程序员"桶的主要问题:

  1. 可版本性 - 是的,在新版本的代码中抛出一个新的Exception会破坏盲目放入新JAR文件的客户端的编译.国际海事组织,这是一个很好的事情(只要你有一个很好的理由抛出一个额外的检查除外),因为你的库的客户都来思考他们需要这种行为的改变做什么.如果一切都未经检查,那么您的客户不一定有任何线索(直到发生异常)您的行为已经改变.如果您要更改代码的行为,那么您的客户必须了解这一点是合理的.你有没有更新到第三方库的新版本只是为了发现它的行为已经无形改变,现在你的程序被破坏了?如果您在库中进行了更改行为更改,则应该使用早期版本的库中断与客户端的自动兼容性.

  2. 可伸缩性 - 如果您通过将检查的异常转换为适合您的API层的特定已检查(或未检查)异常来正确处理它,则这将成为一个非问题.也就是说,如果您正确编码,此问题就会消失.这样做,您可以正确隐藏您的实施细节,无论如何您的呼叫者都不应该关心.

很多时候,这只是一个与人有关的宗教问题,这就是为什么我(不必要地,我知道)烦恼的原因.如果您对检查异常有宗教厌恶,那就没问题.如果你有一个针对已检查异常的理由参数,那就没问题.我已经看到了很好的理由(我大多不同意,但仍然......).但大多数情况下,我看到针对已检查异常的错误论据,包括在讨论Java 1.0时公平合理的参数,但在Java的现代版本中不再适用.