关于c#:检查异常的优缺点是什么?

What are the pros and cons of checked exception?

您喜欢像Java那样检查异常处理还是像C语言那样检查异常,为什么?


无聊的。

检查异常在正确使用时是一件很好的事情,但通常会导致以下情况:

1
2
3
4
5
6
7
8
doSomething();
try
{
  somethingThrowsCheckedException();
}
catch(ThatCheckedException)
{ }
doSomethingElse();

坦率地说,那是错误的。你应该让你无法处理的例外情况浮出水面。

正确使用检查过的异常可以是好的。但通常情况下,正确执行检查异常的结果是如下方法签名:

1
2
3
4
5
public void itMightThrow() throws Exception1, Exception2, Exception3, Exception4, // ...
Exception12, Exception13, /* ... */ Exception4499379874
{
  // body
}

我夸张了吗?只有轻微。

编辑:

也就是说,在处理异常时,我更喜欢在Java上用C语言来处理与检查异常无关的事情(不管怎样,我可以得到它。不,我喜欢的是,当抛出异常时,C中的堆栈跟踪被填充,而不是在Java中实例化一个异常。

编辑2:这是给评论@yishai,@eddie,@bill k:

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

其次,我喜欢C的异常堆栈跟踪在throwal而不是在instantiation处填充,原因是您可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
private MyException NewException(string message)
{
   MyException e = new MyException(message);
   Logger.LogException(message, e);
   return e;
}

// and elsewhere...
if(mustThrow)
{
   throw NewException("WHOOOOPSIEE!");
}

这是Java中不能做的一个技巧,而不需要在堆栈跟踪中包含EDCOX1 0Ω方法。


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

虽然在理论上很好,但在实践中,检查的异常重新引入了同样的问题,异常应该首先消除。它们在应用程序层之间添加了紧密耦合。它们使库无法在后续版本中更改其实现。克拉瑟贴出的链接详细地解释了问题,比我以前解释得更好。


对于那些你不能提前预测的可能出错的事情,我更喜欢检查异常。例如,IOException或SQLException。它告诉程序员,他们必须考虑到一些不可预知的错误,他们不能编写不会抛出异常的健壮代码,不管他们尝试了多少。好的。

很多时候,程序员将检查过的异常视为要处理的语言。它不是(或者不会出现在一个设计良好的API中),它表明操作中固有着不可预知的行为,并且您应该依赖于操作的确定结果,即在相同的输入下,始终以相同的方式工作。好的。

这就是说,在实践中,检查异常有两个原因:好的。

  • 并非所有用Java编写的应用程序都需要健壮性。关闭选中异常的编译器级别标志是不错的-尽管这可能会导致API在开发人员使用设置为关闭它们的标志时滥用选中异常。在考虑到更好的一致性后,我当前的想法是编译器警告是这里最好的平衡。如果选中的异常是编译器警告,包括编译器警告如果一个被忽略了几个层(这样一个被忽略的事实将被编译到类中),以便调用方至少知道捕获异常,即使他不知道哪一个,那么那些不在乎的人将忽略编译器警告,而那些如果没有任何人被迫编写错误处理代码,他们不会在意让他们的代码编译。
  • 引入异常链接花费的时间太长(1.4版)。缺乏异常链导致了许多坏习惯的早期发展,而不是每个人都这样做:好的。

    引发新的RuntimeException(e);好的。

  • 当他们不知道该怎么做的时候。好的。

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

    编辑:另一个答案指向两个问题,这两个问题促使C设计决定不检查异常。在我看来,这两个论点都很糟糕,所以我认为它们值得处理/反平衡。好的。

  • 版本控制。参数是,如果您更改了API实现并希望添加额外的检查异常,那么您将破坏现有的客户机代码。
  • 可剥落性在您知道它之前,您有一个方法可以抛出15个已检查的异常。
  • 我认为这两个版本都有一个未解决的问题,即当做出这些评论时,已经接受了处理向上移动到下一个级别的检查异常的正确方法是包装一个适合于您的API抽象的不同的检查异常。例如,如果您有一个可以处理IOException、SQLException或XML相关异常的存储API,经过适当设计的API会将这些差异隐藏在常规PersistenceException或类似的异常后面。好的。

    除了一般的设计指南外,具体来说,这些争论确实导致了许多关于替代方案的问题:好的。

  • 版本控制。因此,开发人员根据您的数据库API开发,认为他们捕获并处理了相关的异常(比如databaseexception),然后在下一个版本中决定添加networkexception以捕获与数据库的网络级通信问题。现在您已经破坏了与现有代码的所有兼容性,编译器甚至不会抱怨它。如果幸运的话,每个人都可以在回归测试中发现它。
  • 可扩展性。在C解决方案中,如果下面三个API级别有可能访问易失性资源,那么您完全依赖于API文档,因为编译器不会告诉您这一点。
  • 这是一个伟大的网页应用程序设计,在那里死亡并向用户显示一个漂亮的错误500页是关于所有人做的麻烦(因为事务是由容器处理的)。但并非所有的应用程序都是基于这样的需求构建的。好的。

    这场争论最终以沸腾而告终(无论如何对我来说):不要担心异常,任何事情都可能出错,只会造成一网打尽。好的。

    好啊。这是选中和未选中异常方法之间的核心区别。选中的异常警告程序员不可预测的调用。未经检查的异常方法只是假设所有错误条件都属于同一类,它们只是具有不同的名称,并且它们被取消检查,这样就不会有人到处捕获它们。好的。

    现在,参数在clr级别确实有优点。我同意所有被检查的异常都应该在编译器级别,而不是运行时级别。好的。好啊。


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

    • 为什么C没有异常规范?
    • Java是否需要检查异常?
    • 检查异常的问题

    我确信我不喜欢检查异常(在当前的实现中)。

    以下是两个要点。

    可版本性

    Anders Hejlsberg: Let's start with versioning, because the issues are pretty easy to see there. Let's say I create a method foo that declares it throws exceptions A, B, and C. In version two of foo, I want to add a bunch of features, and now foo might throw exception D. It is a breaking change for me to add D to the throws clause of that method, because existing caller of that method will almost certainly not handle that exception.

    Adding a new exception to a throws clause in a new version breaks client code. It's like adding a method to an interface. After you publish an interface, it is for all practical purposes immutable, because any implementation of it might have the methods that you want to add in the next version. So you've got to create a new interface instead. Similarly with exceptions, you would either have to create a whole new method called foo2 that throws more exceptions, or you would have to catch exception D in the new foo, and transform the D into an A, B, or C.

    可扩展性

    Anders Hejlsberg: The scalability issue is somewhat related to the versionability issue. In the small, checked exceptions are very enticing. With a little example, you can show that you've actually checked that you caught the FileNotFoundException, and isn't that great? Well, that's fine when you're just calling one API. The trouble begins when you start building big systems where you're talking to four or five different subsystems. Each subsystem throws four to ten exceptions. Now, each time you walk up the ladder of aggregation, you have this exponential hierarchy below you of exceptions you have to deal with. You end up having to declare 40 exceptions that you might throw. And once you aggregate that with another subsystem you've got 80 exceptions in your throws clause. It just balloons out of control.

    In the large, checked exceptions become such an irritation that people completely circumvent the feature. They either say,"throws Exception," everywhere; or—and I can't tell you how many times I've seen this—they say,"try, da da da da da, catch curly curly." They think,"Oh I'll come back and deal with these empty catch clauses later," and then of course they never do. In those situations, checked exceptions have actually degraded the quality of the system in the large.


    好吧,我不想回答,但这太久了,关不上门,在栅栏的一边得到了很多答案,所以我觉得有必要在另一边称重。

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

    如果您想创建5-9的可靠代码,您需要知道从您调用的代码中可以抛出哪些异常,这样您就可以解释哪些异常可以从中恢复,哪些异常必须导致您放弃正在做的工作。如果C,您可以这样做,但它涉及大量的尝试和错误,直到您看到可以抛出的所有可能的异常为止。或者你只是抓住例外,尽你所能。

    这两种方法都有优点和缺点,即Java和C语言。合理的论点既有利于两者,也有利于两者。同样,在Net上,我更喜欢Java所选择的方法,但是如果我今天要重写Java,我会将API更改为将一些已检查的异常更改为运行时异常。Java API在使用检查异常时不一致。正如其他人所说,异常链接作为标准API特性和JVM的一部分出现花费了太长时间。

    然而,在检查异常的基础上产生的费用往往属于"懒惰的程序员滥用这种语言特性"的范畴,这是真的。但许多语言和它们的特性都是如此。"懒惰的程序员"的论点是一个薄弱的论点。

    让我们来解决不属于"懒惰的程序员"的主要抱怨:

  • 版本控制能力——是的,在新版本的代码中抛出一个新的异常将破坏对盲目放入新JAR文件的客户机的编译。在我看来,这是一件好事(只要你有充分的理由抛出一个额外的检查异常),因为你的库的客户必须对他们需要对这种行为改变做什么做出解释。如果一切都未选中,那么您的客户就不一定有任何线索(直到发生异常)表明您的行为发生了变化。如果您正在更改代码的行为,那么您的客户就应该知道这一点。您是否曾经更新到第三方库的新版本,结果发现它的行为发生了无形的变化,现在您的程序被破坏了?如果在库中更改了中断行为,则应中断与使用库早期版本的客户端的自动兼容性。

  • 可伸缩性—如果您通过将检查的异常转换为适合您的API层的特定检查(或未检查)异常来正确地处理这些异常,那么这将成为一个没有问题的问题。也就是说,如果编码正确,这个问题就消失了。这样做,您就可以正确地隐藏实现细节,而调用者无论如何都不应该关心这些细节。

  • 太多时候,这只是人们的宗教问题,这就是为什么我会(不必要地,我知道)生气的原因。如果你对被检查的例外有宗教上的厌恶,那没关系。如果您对检查的异常有一个合理的理由,那没关系。我已经看到了合理的论据(我大多不同意,但仍然…)。但大多数情况下,我都会看到反对检查异常的错误论点,包括在讨论Java 1时公平和合理的参数,但是对于Java的现代版本来说,这些参数不再适用。


    在我看来,有些情况下检查异常是适当的。在Java中可能有不同的功能可以更好地支持它们。这并非毫无困难(例如,在某些情况下,您可能需要检查异常,而在其他情况下则不需要)。当然,Java也支持未检查的异常类型。

    通常应记录适合检查的异常类型。最好在代码中记录。民粹主义的方法只是把它弄糟,只考虑好的情况。


    在实践中,最好使用检查异常处理,因为它允许在应用程序凌晨2点开始淹没错误日志时获得更详细的信息,并且您得到一个调用来进行一些调试…


    我唯一想让编译器检查的是函数是否抛出异常。可以抛出哪些特定的异常并不重要。事实上,在我的经验中,有很多函数是不抛出任何东西的,如果在函数规范中记录了这一点,那就太好了。对于这些函数,您不必担心异常处理。


    我认为在大多数情况下,检查异常是浪费时间。它们会陷入诸如randolpho所提到的反模式或大量自定义异常的创建中,从而将您的实现与已使用的库分离开来。摆脱这个"功能"让你专注于你想做的事情。


    只要检查的异常是可恢复的,或者不是由于编程错误(例如对结果集的索引访问无效)而导致的,检查的异常就非常好。否则,它们往往会通过强制编码人员在许多方法签名中声明IOException之类的东西,而对客户机代码没有真正有用的东西,从而污染代码层和API。