针对已检查异常的案例

时间:2009-03-05 08:18:55

标签: java exception checked-exceptions

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

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

我所结束的每一次谈话都以同样的问题结束......我让我把它设置好:

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

  • 错误是指永远不会被捕获的东西(VM有花生过敏,有人丢了一罐花生)
  • RuntimeException用于程序员做错的事情(程序员走出数组末尾)
  • 异常(RuntimeException除外)适用于程序员无法控制的内容(写入文件系统时磁盘已填满,已达到进程的文件句柄限制,无法再打开文件)
  • Throwable只是所有异常类型的父级。

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

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

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

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

我没有使用catch(Exception)包装Main的问题(或者在某些情况下catch(Throwable)以确保程序可以正常退出 - 但我总是捕获我需要的特定异常。我至少会显示一条相应的错误信息。

人们从不回复的问题是:

  

如果抛出RuntimeException   子类而不是Exception   那么子类你怎么知道   你应该赶上吗?

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

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

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

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

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

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

32 个答案:

答案 0 :(得分:258)

答案 1 :(得分:175)

关于已检查异常的事情是,通过对该概念的通常理解,它们并非真正例外。相反,它们是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的级别也不能这样做。这些方法不需要知道异常处理;必须在签名上添加例外:

  1. 不必要地增加耦合;
  2. 使界面签名变得非常脆弱;
  3. 使代码的可读性降低;
  4. 是如此烦人,以至于常见的程序员反应是通过做一些可怕的事情来打败系统,例如'抛出异常','捕获(异常e){}',或者将所有内容包装在RuntimeException中(这会使调试变得更难)。
  5. 然后有很多荒谬的库异常,如:

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

    当你不得不像这样粗暴地对你的代码进行混乱时,难怪检查异常会产生一些仇恨,即使这只是简单的不良API设计。

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

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

答案 2 :(得分:73)

我不会仅仅针对已检查的异常重复所有(很多)原因,而只选择一个。我已经忘记了编写这段代码的次数:

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

99%的时间我对此无能为力。最后,块会进行任何必要的清理(或者至少应该这样做)。

我也错过了我见过的次数:

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

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

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

好的,我猜这有三个原因。

答案 3 :(得分:44)

嗯,这不是关于显示堆栈跟踪还是静默崩溃。这是关于能够在层之间传达错误。

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

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

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

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

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

  1. 捕获并处理特定的异常。例如,这是为了实现重试逻辑。
  2. 抓住并重新抛出其他例外情况。这里发生的一切通常都是日志记录,它通常是一个陈腐的消息,如“无法打开$ filename”。这些是你无能为力的错误;只有更高的级别知道足以处理它。
  3. 抓住所有内容并显示错误消息。这通常是调度程序的根本,它确实可以通过非异常机制(弹出对话框,编组RPC错误对象等)将错误传递给调用者。

答案 4 :(得分:43)

我知道这是一个古老的问题,但我花了一段时间与已检查的异常进行摔跤,我还要添加一些东西。请原谅我的长度!

我检查异常的主要原因是它们破坏了多态性。使多态接口很好地发挥它们是不可能的。

带上好的' Java List接口。我们有常见的内存实现,如ArrayListLinkedList。我们还有骨架类AbstractList,这使得设计新类型的列表变得容易。对于只读列表,我们只需要实现两种方法:size()get(int index)

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

class WidgetList extends AbstractList<Widget> {
    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界面公开窗口小部件,您可以检索项目(list.get(123))或迭代列表(for (Widget w : list) ...),而无需了解WidgetList本身。可以将此列表传递给使用通用列表的任何标准方法,或将其包装在Collections.synchronizedList中。使用它的代码既不需要知道也不关心&#34; Widgets&#34;在现场组成,来自阵列,或从文件,数据库,或从网络,或从未来的子空间中继读取。它仍然可以正常工作,因为List接口已正确实现。

除非它不是。上面的类没有编译,因为文件访问方法可能会抛出IOException,这是一个经过检查的异常,你必须抓住或指定&#34;。您 无法将其指定为抛出 - 编译器不会让您失望,因为这会违反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,或通过COM端口连接到量子熵源的java.util.Random的实现。一旦您尝试使用多态接口的实现做任何新颖的事情,检查异常的概念就会失败。但是检查过的异常是如此阴险,以至于它们仍然不会让你安静下来,因为你仍然必须从低层方法中捕获并重新抛出任何东西,使代码混乱并使堆栈轨迹混乱。

我发现无处不在的Runnable接口经常被支持到这个角落,如果它调用抛出已检查异常的东西。它不能按原样抛出异常,所以它只能通过捕获和重新抛出RuntimeException来使代码混乱。

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

class Util {
    /**
     * Throws any {@link Throwable} without needing to declare it in the
     * method's {@code throws} clause.
     * 
     * <p>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.<RuntimeException>sneakyThrow1(t);
    }

    @SuppressWarnings("unchecked")
    private static <T extends Throwable> RuntimeException sneakyThrow1(
            Throwable t) throws T {
        throw (T)t;
    }
}

乌拉!使用这个我们可以在堆栈的任何深度处抛出一个已检查的异常而不声明它,而不将它包装在RuntimeException中,并且不会使堆栈跟踪混乱!使用&#34; WidgetList&#34;再举一个例子:

@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;
    }
}

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

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


当检查异常遇到多态时,相反的情况也是一个问题:当一个方法被指定为可能抛出一个已检查的异常时,但被覆盖的实现并不是。例如,抽象类OutputStreamwrite方法都指定throws IOExceptionByteArrayOutputStream是一个子类,它写入内存数组而不是真正的I / O源。其重写的write方法不能导致IOException,因此它们没有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);
}

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

但是等等,检查过的异常实际上是所以令人烦恼,他们甚至不会让你反过来!想象一下你现在抓住任何{{1} IOException调用write抛出的OutputStream,但是你想将变量的声明类型更改为ByteArrayOutputStream,编译器会指责你尝试捕获已检查的异常它说无法抛出。

这条规则会导致一些荒谬的问题。例如,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();一样懒惰,并且仍然在不确定的状态下使用代码。另外,特定日志系统或其他处理程序的选择是特定于应用程序的,因此这会损害代码重用。

但是等等!有一种简单而通用的方法可以找到特定于应用程序的处理程序。它位于调用堆栈的较高位置(或者设置为线程uncaught exception handler)。所以在大多数地方, 你需要做的只是将异常提升到堆栈 。例如,throw e;。检查异常只是妨碍了。

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

答案 5 :(得分:23)

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#。

我在博客上发表了这篇文章previously

答案 6 :(得分:22)

Artima published an interview与.NET的一位架构师Anders Hejlsberg一起,它强烈地涵盖了针对已检查异常的论据。一个简短的品尝者:

  

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

答案 7 :(得分:20)

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

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

原因是,如果它这样做,我必须解决它,我必须立即修复它。我想立刻知道有问题。

您实际处理异常的次数是多少?我不是在谈论捕捉异常 - 我在谈论处理它们?写下面的内容太容易了:

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

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

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

答案 8 :(得分:20)

文章Effective Java Exceptions很好地解释了何时使用未选中以及何时使用已检查的例外。以下是该文章的一些引用,以突出主要观点:

  

<强>应急:   预期的条件要求可以用方法的预期目的表达的方法的替代响应。该方法的调用者期望这些条件,并有一个应对它们的策略。

     

<强>故障:   一种意外情况,阻止方法实现其预期目的,如果不参考方法的内部实现,则无法描述。

(SO不允许使用表格,因此您可能需要阅读original page中的以下内容...)

  

<强>应急

     
      
  • 被认为是:设计的一部分
  •   
  • 预计会发生:定期但很少
  •   
  • 谁在乎呢:调用方法的上游代码
  •   
  • 示例:替代退货模式
  •   
  • 最佳映射:已检查的例外
  •   
     

<强>故障

     
      
  • 被认为是:令人讨厌的惊喜
  •   
  • 预计会发生:从不
  •   
  • 谁在乎呢:需要解决问题的人
  •   
  • 示例:编程错误,硬件故障,配置错误,   丢失文件,不可用的服务器
  •   
  • 最佳映射:未经检查的异常
  •   

答案 9 :(得分:19)

简而言之:

例外是一个API设计问题。 - 不多也不少。

已检查例外的参数:

要理解为什么检查异常可能不是好事,让我们转过来问一下这个问题:什么时候检查异常是有吸引力的,也就是为什么你希望编译器强制执行异常声明?

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

因此, 检查异常的参数是编译器强制程序员声明抛出哪些异常,而希望程序员也会记录特定的异常类和导致它们的错误。

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

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

为什么要这么麻烦?

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

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

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

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

值得注意的是,Java VM not 已检查异常 - 只有Java编译器检查它们,并且具有更改的异常声明的类文件在运行时是兼容的。已检查的异常(仅编码样式)不会改进Java VM安全性。

答案 10 :(得分:14)

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

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

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

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

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

答案 11 :(得分:13)

例外类别

在谈论例外时,我总是会回到Eric Lippert's Vexing exceptions博客文章。他将例外列入这些类别:

  • 致命 - 这些例外情况不是您的错误:您无法阻止,并且您无法明智地处理它们。例如,OutOfMemoryErrorThreadAbortException
  • 斩首 - 这些例外是你的错误:你本应该阻止它们,它们代表你代码中的错误。例如,ArrayIndexOutOfBoundsExceptionNullPointerException或任何IllegalArgumentException
  • 烦恼 - 这些例外情况并非例外,不是您的错,您无法阻止它们,但您必须处理它们。它们通常是不幸的设计决策的结果,例如从NumberFormatException抛出Integer.parseInt而不是提供在解析失败时返回布尔值false的Integer.tryParseInt方法。
  • 外生 - 这些例外通常是例外,不是您的错,您不能(合理地)阻止它们,但您必须处理它们。例如,FileNotFoundException

API用户:

  • 不得处理致命骨干例外。
  • 应该处理烦恼的异常,但它们不应出现在理想的API中。
  • 必须处理外源例外。

检查异常

API用户必须处理特定异常的事实是调用者和被调用者之间方法合同的一部分。除其他事项外,契约还指定了:被调用者期望的参数的数量和类型,调用者可以期望的返回值的类型,以及调用者应该处理的异常

由于API中不存在 vexing 异常,因此只有外生异常必须已检查异常才能成为方法合同的一部分。 外生的例外相对较少,因此任何API都应该有相对较少的检查异常。

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

API问题

但是,任何已经检查 vexing 致命异常(例如JCL)的API都会给API用户带来不必要的压力。这样的异常要处理,但是异常是如此常见以至于它本来不应该是异常,或者在处理它时什么都不能做。 this 会导致Java开发人员讨厌检查过的异常。

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

结论

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

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

答案 12 :(得分:6)

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

Java中的检查异常不能解决后一个问题; C#和VB.NET用洗澡水把婴儿扔掉了。

this OOPSLA 2005 paper(或related technical report中描述了采取中间道路的好方法。)

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

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

答案 13 :(得分:6)

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

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

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

假设在API层底部的某处抛出异常并且冒泡,因为没有人知道甚至可能发生此错误,即使这是一种非常合理的错误。调用代码抛出它(例如FileNotFoundException与VogonsTrashingEarthExcept相反......在这种情况下,如果我们处理它并不重要,因为没有任何东西可以处理它)。

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

最后,我看到的大多数论点是“我不想处理异常,很多人不想处理异常。检查异常迫使我处理它们因此我讨厌检查异常”要消除这种情况完全机制,并将其降低到goto hell的鸿沟只是愚蠢而且缺乏凝聚力和视野。

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

答案 14 :(得分:5)

Anders谈到了检查异常的缺陷以及为什么他在软件工程无线电的episode 97中将它们从C#中删除了。

答案 15 :(得分:5)

问题

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

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

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

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

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

解决方案

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

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

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

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

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

最后一件事

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

答案 16 :(得分:5)

尝试解决未回答的问题:

  

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

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

例如:

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

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

答案 17 :(得分:4)

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

事实是,如果你强制执行检查过的异常,你就不会强迫每个程序员“做正确的事”而现在其他所有人都是因为你制定的规则而讨厌你的附带损害

修复坏程序!不要试图修改语言不允许它们!对于大多数人来说,“做一些关于异常的事情”实际上只是告诉用户它。我也可以告诉用户一个未经检查的异常,因此请将检查过的异常类保留在我的API之外。

答案 18 :(得分:4)

这不是反对已检查异常的纯概念的论据,但Java用于它们的类层次结构是一个怪异的表现。我们总是简单地称呼这些事情&#34;例外&#34; - 这是正确的,因为语言规范calls them that too - 但是如何在类型系统中命名和表示异常?

通过班级Exception想象?好吧,因为Exception是例外,同样例外是Exception s,除了那些 Exception的例外情况s,因为其他异常实际上是Error s,这是另一种异常,是一种超常例外的异常,除非它发生,否则永远不会发生,除非有时你必须抓住它。除非并非全部,因为您还可以定义既不是Exception也不是Error但只有Throwable例外的其他例外。

其中哪些是&#34;已检查&#34;异常? Throwable是已检查的例外,除非他们还是Error,这是未经检查的例外情况,然后是Exception s,它们也是{{ 1}} s并且是检查异常的主要类型,除了它也有一个例外,即如果它们也是RuntimeException s,因为它是另一种未经检查的异常。

Throwable的内容是什么?就像名字所暗示的那样,它们像所有RuntimeException一样是异常,并且它们在运行时发生,就像所有异常一样,除了Exception与其他运行相比是例外的-time RuntimeException s因为它们不应该发生,除非你犯了一些愚蠢的错误,虽然Exception s永远不会RuntimeException s,所以它们不适用于事物这是非常错误的,但实际上并不是Error。除了RuntimeErrorExceptionError的{​​{1}}实际上是RuntimeException。但是,所有异常都不应该代表错误的情况吗?是的,所有这些。除了ThreadDeath之外,这是一个非常例外的例外情况,因为文档解释说它是一个正常的事件&#34;这就是为什么他们把它变成Error的类型。

无论如何,因为我们将所有异常从中间划分为Error s(用于异常执行异常,因此未经检查)和Error s(用于不太常见的执行错误) ,所以检查除非他们不是),我们现在需要两种不同的例外。我们需要IllegalAccessErrorIllegalAccessExceptionInstantiationErrorInstantiationExceptionNoSuchFieldErrorNoSuchFieldExceptionNoSuchMethodError和{{ 3}},NoSuchMethodExceptionZipError

除非检查异常,否则总是(相当容易)欺骗编译器并在不进行检查的情况下抛出它。如果你这样做,你可能会获得ZipException,除了在其他情况下,它可以作为UndeclaredThrowableExceptionUnexpectedException(与UnknownException无关) ,仅适用于&#34;严重例外&#34;),或UnknownErrorExecutionExceptionInvocationTargetException

哦,我们不能忘记Java 8的时髦新ExceptionInInitializerError,这是一个Exception异常,旨在让你通过包装将异常检查概念抛出窗外已检查由{I / O错误导致的UncheckedIOException异常(不会导致IOException异常,但也存在异常),这些异常难以处理,因此您无需检查它们。< / p>

感谢Java!

答案 19 :(得分:4)

我在c2.com上的写作与原始形式相比基本没有变化:CheckedExceptionsAreIncompatibleWithVisitorPattern

总结:

访问者模式及其亲属是一类接口,其中间接调用者和接口实现都知道异常但接口和直接调用者形成了一个无法知道的库。

CheckedExceptions的基本假设是所有声明的异常都可以从调用具有该声明的方法的任何点抛出。 VisitorPattern揭示了这个假设是错误的。

在这些情况下检查异常的最终结果是许多其他无用的代码,它们基本上在运行时删除了编译器的已检查异常约束。

至于潜在的问题:

我的一般想法是顶级处理程序需要解释异常并显示相应的错误消息。我几乎总是看到IO异常,通信异常(由于某种原因API区分),或任务致命错误(程序错误或备份服务器上的严重问题),所以如果我们允许严重的堆栈跟踪这不应该太难服务器问题。

答案 20 :(得分:4)

原始形式的已检查异常是处理意外事件而非故障的尝试。值得称赞的目标是突出特定的可预测点(无法连接,文件未找到等)&amp;确保开发人员处理这些问题。

原始概念中从未包含的内容是强制广泛的系统性和广泛性。不可恢复的失败声明。这些失败永远不会被声明为已检查的例外。

通常可以在代码中使用失败,并且EJB,web&amp; Swing / AWT容器已经通过提供最外层的“失败请求”异常处理程序来满足此要求。最基本的正确策略是回滚交易&amp;返回错误。

一个关键点是运行时&amp;已检查的异常在功能上是等效的。检查异常无法处理或恢复,运行时异常不能。

反对“检查”例外的最大争议是大多数例外无法修复。简单的事实是,我们没有破坏的代码/子系统。我们无法看到实施,我们对此不负责任,也无法解决。

如果我们的应用程序不是数据库..我们不应该尝试修复数据库。这会违反封装原则

特别成问题的是JDBC(SQLException)和EJB(RemoteException)的RMI领域。这些强制普遍的系统可靠性问题,而不是实际上可以解决的问题,而不是根据最初的“检查异常”概念来识别可修复的意外事件,而是被广泛宣布。

Java设计中的另一个严重缺陷是,异常处理应该正确地放置在尽可能高的“业务”或“请求”级别。这里的原则是“早退,赶晚”。检查的例外做的很少,但会妨碍这一点。

我们在Java中有一个明显的问题,即需要数千个无用的try-catch块,其中很大一部分(40%以上)被错误编码。这些中几乎没有一个实现任何真正的处理或可靠性,但会产生重大的编码开销。

最后,“检查异常”与FP函数编程非常不兼容。

他们坚持“立即处理”与“赶上后期”异常处理最佳实践以及抽象循环/控制流的任何FP结构不一致。

许多人谈论“处理”已检查的异常,但正在通过他们的帽子进行讨论。在假装成功的无效,不完整或不正确的数据失败后继续处理任何事情。它是最低形式的工程/可靠性弊端。

干净利落,是处理异常的最基本的正确策略。回滚事务,记录错误&amp;报告对用户的“失败”响应是合理的做法 - 最重要的是,防止错误的业务数据被提交到数据库。

异常处理的其他策略是在业务,子系统或请求级别“重试”,“重新连接”或“跳过”。所有这些都是一般可靠性策略,并且在运行时异常情况下运行良好/更好。

最后,失败比使用不正确的数据运行更可取。继续将导致次要错误,远离原始原因&amp;更难调试;或最终会导致错误的数据被提交。人们被解雇了。

请参阅:
- http://literatejava.com/exceptions/checked-exceptions-javas-biggest-mistake/

答案 21 :(得分:4)

This article是我读过的Java中异常处理的最佳文本。

它倾向于对未经检查的异常进行未经检查,但是这个选择是通过强有力的论证进行解释的。

我不想在这里引用太多的文章内容(最好将其作为一个整体来阅读),但它涵盖了来自此主题的未经检查的异常提倡者的大多数论据。特别是这个论点(似乎很受欢迎)包括在内:

  

假设在API层底部的某处抛出异常并且冒泡,因为没有人知道甚至可能发生此错误,即使这是一种非常合理的错误。调用代码抛出它(例如FileNotFoundException与VogonsTrashingEarthExcept相反......在这种情况下,如果我们处理它并不重要,因为没有任何东西可以处理它)。

作者“回应”:

  

假设所有运行时异常是完全错误的   不应被抓住并允许传播到非常“顶部”   应用。 (...)对于所需的每种特殊情况   由系统/业务要求明确处理 -   程序员必须决定在哪里捕获它以及一旦做什么就要做   条件被抓住了。这必须严格按照   应用程序的实际需求,而不是基于编译器警报。所有   必须允许其他错误自由传播到最顶层   处理程序,他们将被记录和优雅(也许,   终止)将采取行动。

主要思想或文章是:

  

当谈到软件中的错误处理时,唯一可能做出的安全和正确的假设是,绝对每个子例程或模块都可能出现故障!

因此,如果“没有人知道甚至可能发生此错误”,那么该项目就会出现问题。至少应该由作者建议的最通用的异常处理程序(例如,处理所有异常未由更具体的处理程序处理的异常处理程序)处理此类异常。

很伤心没有多少人似乎发现了这篇伟大的文章:-(。我全心全意地推荐所有犹豫不决的人,哪种方法最好花一些时间阅读。

答案 22 :(得分:3)

检查异常的一个问题是,如果该接口的一个实现使用它,则异常通常附加到接口的方法。

检查异常的另一个问题是它们往往被滥用。这方面的完美示例是java.sql.Connectionclose()方法。它可以抛出SQLException,即使您已经明确声明您已完成连接。什么信息可以关闭()可能传达你关心的?

通常,当我关闭()连接*时,它看起来像这样:

try {
    conn.close();
} catch (SQLException ex) {
    // Do nothing
}

另外,不要让我开始使用各种解析方法和NumberFormatException ... .NET的TryParse,它不会抛出异常,使用它更加容易,不得不回到Java(我们使用我工作的Java和C#。)

*作为附加注释,PooledConnection的Connection.close()甚至不会关闭连接,但由于它是一个已检查的异常,您仍然必须捕获SQLException

答案 23 :(得分:3)

程序员需要知道方法可能抛出的所有异常,才能正确使用它。因此,仅仅通过一些例外情况来击败他并不一定能帮助粗心的程序员避免错误。

繁重的成本(特别是在较大的,不太灵活的代码库中,不断修改界面签名是不切实际的)超过了微薄的好处。

静态分析可能很好,但真正可靠的静态分析通常需要程序员严格的工作。有一个成本收益计算,并且需要将该栏设置为高,以便进行导致编译时错误的检查。如果IDE承担了传达方法可能抛出哪些异常(包括哪些是不可避免的)的作用,那将会更有帮助。虽然如果没有强制异常声明可能不会那么可靠,但大多数异常仍然会在文档中声明,并且IDE警告的可靠性并不是那么重要。

答案 24 :(得分:3)

Here针对已检查异常的一个参数(来自joelonsoftware.com):

  

理由是我认为例外并不比   “goto's”,自20世纪60年代以来被认为是有害的,因为他们创造了一个   突然从一个代码点跳转到另一个代码点。事实上他们是   比goto更差:

     
      
  • 它们在源代码中不可见。看一段代码,   包括可能会或可能不会抛出异常的函数,没有   查看可能抛出哪些异常以及从何处抛出异常的方法。这意味着   甚至仔细的代码检查也没有发现潜在的错误。
  •   
  • 他们为函数创建了太多可能的退出点。写得正确   代码,你真的必须考虑每个可能的代码路径   你的功能。每次调用一个可以提升的函数   例外,不要当场抓住它,为你创造机会   由突然终止的函数引起的惊喜错误,离开   处于不一致状态的数据或您没有的其他代码路径   想一想。
  •   

答案 25 :(得分:2)

没有人提到的一件重要事情是它如何干扰接口和lambda表达式。

假设您定义了MyAppException extends Exception。它是应用程序抛出的所有异常继承的顶级异常。每个方法都声明throws MyAppException这是一种新生,但可管理。异常处理程序记录异常并以某种方式通知用户。

所有看起来都没问题,直到你想要实现一些不属于你的界面。显然它没有声明抛出MyApException的意图,所以编译器不允许你从那里抛出异常。

但是,如果您的异常扩展RuntimeException,那么接口就没有问题。如果您愿意,可以自愿在JavaDoc中提及异常。但除此之外,它只是默默地通过任何东西,在你的异常处理层中被捕获。

答案 26 :(得分:2)

好消息证明不需要Checked Exception:

  1. 许多为Java工作的框架。就像Spring一样,将JDBC异常包装到未经检查的异常,将消息抛给日志
  2. 很多语言都是在java之后出现的,甚至在java平台上也是如此 - 他们不使用它们
  3. 检查异常,它是关于客户端如何使用抛出异常的代码的类型预测。但是编写此代码的开发人员永远不会知道代码客户端正在使用的系统和业务。作为示例强制抛出已检查异常的Interfcace方法。在系统上有100个实现,50个甚至90个实现不会抛出此异常,但是如果用户引用该接口,则客户端仍然必须捕获此异常。那些50或90实现倾向于处理自己内部的异常,将异常置于日志中(这对他们来说是好的行为)。我们该怎么做?我最好有一些后台逻辑可以完成所有工作 - 向日志发送消息。如果我作为代码的客户端,会觉得我需要处理异常 - 我会这样做。我可能会忘记它,但是如果我使用TDD,我的所有步骤都会被覆盖,我知道我想要什么。
  4. 另一个例子,当我在java中使用I / O时,它强制我检查所有异常,如果文件不存在?我应该怎么做?如果它不存在,系统将不会进入下一步。这个方法的客户端不会从该文件获得预期的内容 - 他可以处理Runtime Exception,否则我应该首先检查Checked Exception,将消息发送到日志,然后从方法中抛出异常。不......不 - 我最好自动使用RuntimeEception,它会自动执行。手动处理没有任何意义 - 我很高兴我在日志中看到了一条错误消息(AOP可以帮助解决这个问题......修复java的问题)。最后,如果我认为系统应该向最终用户显示弹出消息 - 我会显示它,而不是问题。
  5. 我很高兴java在使用核心库(如I / O)时会为我提供一个选择使用什么。 Like提供了两个相同类的副本 - 一个用RuntimeEception包装。 然后我们可以比较人们会使用什么。但是现在,很多人最好选择java或不同语言的框架。像Scala,JRuby无论如何。许多人只相信SUN是对的。

答案 27 :(得分:2)

我认为这是一个很好的问题而且根本没有争议。我认为第三方库应该(通常)抛出未经检查的异常。这意味着您可以隔离对库的依赖关系(即,您不必重新抛出异常或抛出Exception - 通常是不好的做法)。 Spring的DAO layer就是一个很好的例子。

另一方面,核心Java API的异常通常应该检查是否可以永远处理。取FileNotFoundException或(我最喜欢的)InterruptedException。这些条件几乎总是专门处理(即您对InterruptedException的反应与您对IllegalArgumentException的反应不同。检查异常的事实迫使开发人员考虑条件是否可处理。 (也就是说,我很少看到InterruptedException处理得当!)

还有一件事 - RuntimeException并不总是“开发人员出错的地方”。当您尝试使用enum创建valueOf并且该名称没有enum时,会引发非法参数异常。这不一定是开发人员的错误!

答案 28 :(得分:1)

我们已经看到了一些对C#首席架构师的引用。

这是Java人员关于何时使用已检查异常的备用观点。他承认其他人提到的许多负面消息: Effective Exceptions

答案 29 :(得分:0)

尽管已经阅读了整个页面,但我仍然找不到针对已检查异常的单一合理参数。大多数人都在谈论糟糕的API设计,无论是在某些Java类还是在他们自己的类中。

此功能可能令人烦恼的唯一情况是原型。这可以通过向语言添加一些机制来解决(例如,一些@supresscheckedexceptions注释)。但对于常规编程,我认为检查异常是一件好事。

答案 30 :(得分:0)

我已经阅读了很多关于异常处理的内容,即使(大多数情况下)我也不能真正地说我对已检查异常的存在感到高兴或悲伤这是我的看法:在低级代码中检查异常( IO,网络,操作系统等)以及高级API /应用程序级别中未经检查的异常。

即使没有那么容易在它们之间画一条线,我发现在同一屋檐下整合几个API /库真的很烦人/很难,而不是一直包装大量的检查异常但另一方面,有时候强制捕获一些异常并提供一个在当前环境中更有意义的另一个异常是有用/更好的。

我正在开发的项目需要大量的库并将它们集成在相同的API下,API完全基于未经检查的异常。这个框架提供了一个高级API,它在开始时充满了检查异常并具有只有几个未经检查的异常(Initialization Exception,ConfigurationException等),我必须说不是非常友好。大多数时候你不得不抓住或重新抛出你不知道如何处理的异常,或者你甚至不关心(不要与你混淆应该忽略异常),尤其是在单个客户端点击可能会抛出10个可能的(已检查)异常。

当前版本(第3版)仅使用未经检查的异常,并且它有一个全局异常处理程序,负责处理任何未捕获的内容。 API提供了一种注册异常处理程序的方法,它将决定异常是否被视为错误(大多数时候是这种情况),这意味着log&amp;通知某人,或者它可能意味着其他东西 - 比如这个异常,AbortException,这意味着打破当前的执行线程并且不记录任何错误,因为它是不希望的。当然,为了解决所有自定义线程必须使用try {...} catch(all)来处理run()方法。

public void run(){

try {
     ... do something ...
} catch (Throwable throwable) {
     ApplicationContext.getExceptionService().handleException("Handle this exception", throwable);
}

}

如果您使用WorkerService来安排为您处理所有事情的作业(Runnable,Callable,Worker),则不需要这样做。

当然这只是我的意见,它可能不是正确的,但它看起来对我来说是一个好方法。如果我觉得这对我有好处,我会发布项目,对其他人也有好处......:)

答案 31 :(得分:-2)

在我看来,经过检查的例外是一个非常好的概念。不幸的是,大多数合作过的程序员我都有另一种意见,所以项目有很多错误的使用异常处理。我已经看到大多数程序员创建一个(只有一个)异常类,一个RuntimeException的子类。它包含一条消息,有时是一个多语言密钥。我没有机会反驳这一点。当我向他们解释反模式是什么,方法的合同是什么时,我的印象是我在墙上说话。我有点失望。

但是今天显而易见的是,对所有内容都有一般运行时异常的概念是反模式。他们用它来检查用户输入。抛出异常,以便用户对话可以从中发出错误消息。但并非每个方法的调用者都是对话!通过抛出运行时异常,方法的契约已更改但未声明,因为它不是已检查的异常。

希望他们今天学到了一些东西,并且会在另一个地方进行检查(有用且必要)。仅使用已检查的异常无法解决问题,但已检查的异常将向程序员发出信号,表明他实现了错误。

相关问题