在Java(或任何其他带有受控异常的语言)中,当创建您自己的异常类时,您如何决定它应该被检查还是未检查?

我的直觉是,在调用者可能能够以某种有效的方式恢复的情况下,将调用checked异常,而作为未检查的异常则更多地用于不可恢复的情况,但我对其他人的想法感兴趣。


当前回答

这不仅仅是从异常中恢复的能力问题。在我看来,最重要的是调用者是否对捕获异常感兴趣。

如果您编写的库用于其他地方,或应用程序中的较低级别层,请询问自己调用者是否有兴趣捕获(了解)您的异常。如果他不是,那么使用一个未检查的异常,这样就不会给他带来不必要的负担。

这是许多框架使用的哲学。尤其是Spring和hibernate——它们精确地将已知的受控异常转换为未检查异常,因为Java中过度使用了受控异常。我能想到的一个例子是来自json.org的JSONException,这是一个受检查的异常,最烦人的是——它应该是不受检查的,但开发人员根本没有考虑到这一点。

顺便说一下,大多数情况下,调用者对异常的兴趣与从异常中恢复的能力直接相关,但情况并非总是如此。

其他回答

受控异常非常好,只要你知道什么时候应该使用它们。对于SQLException(有时对于IOException), Java核心API无法遵循这些规则,这就是它们如此糟糕的原因。

受控异常应该用于可预测的、但无法预防的、可以合理恢复的错误。

未检查异常应该用于其他所有事情。

我来解释一下,因为大多数人都误解了这句话的意思。

Predictable but unpreventable: The caller did everything within their power to validate the input parameters, but some condition outside their control has caused the operation to fail. For example, you try reading a file but someone deletes it between the time you check if it exists and the time the read operation begins. By declaring a checked exception, you are telling the caller to anticipate this failure. Reasonable to recover from: There is no point telling callers to anticipate exceptions that they cannot recover from. If a user attempts to read from an non-existing file, the caller can prompt them for a new filename. On the other hand, if the method fails due to a programming bug (invalid method arguments or buggy method implementation) there is nothing the application can do to fix the problem in mid-execution. The best it can do is log the problem and wait for the developer to fix it at a later time.

除非您抛出的异常满足上述所有条件,否则它应该使用未检查异常。

Reevaluate at every level: Sometimes the method catching the checked exception isn't the right place to handle the error. In that case, consider what is reasonable for your own callers. If the exception is predictable, unpreventable and reasonable for them to recover from then you should throw a checked exception yourself. If not, you should wrap the exception in an unchecked exception. If you follow this rule you will find yourself converting checked exceptions to unchecked exceptions and vice versa depending on what layer you are in.

对于已检查和未检查的异常,使用正确的抽象级别。例如,具有两种不同实现(数据库和文件系统)的代码存储库应该通过抛出SQLException或IOException来避免暴露特定于实现的细节。相反,它应该将异常包装在一个跨越所有实现的抽象中(例如RepositoryException)。

以下是我的“最终经验法则”。 我使用:

方法代码中由于调用者导致的失败而出现的未检查的异常(这涉及一个显式和完整的文档) 检查异常失败由于被调用,我需要明确的任何人想要使用我的代码

与前面的答案相比,这是使用一种或另一种(或两种)例外的明确理由(人们可以同意或不同意)。


对于这两个异常,我将为我的应用程序创建自己的未检查和已检查的异常(这里提到过,这是一个很好的实践),除了非常常见的未检查异常(如NullPointerException)

例如,下面这个特定函数的目标是创建(如果已经存在,则获取)一个对象, 意义:

the container of the object to make/get MUST exist (responsibility of the CALLER => unchecked exception, AND clear javadoc comment for this called function) the other parameters can not be null (choice of the coder to put that on the CALLER: the coder will not check for null parameter but the coder DOES DOCUMENT IT) the result CAN NOT BE NULL (responsibility and choice of the code of the callee, choice which will be of great interest for the caller => checked exception because every callers MUST take a decision if the object can not be created/found, and that decision must be enforced at the compilation time: they can not use this function without having to deal with this possibility, meaning with this checked exception).

例子:


/**
 * Build a folder. <br />
 * Folder located under a Parent Folder (either RootFolder or an existing Folder)
 * @param aFolderName name of folder
 * @param aPVob project vob containing folder (MUST NOT BE NULL)
 * @param aParent parent folder containing folder 
 *        (MUST NOT BE NULL, MUST BE IN THE SAME PVOB than aPvob)
 * @param aComment comment for folder (MUST NOT BE NULL)
 * @return a new folder or an existing one
 * @throws CCException if any problems occurs during folder creation
 * @throws AssertionFailedException if aParent is not in the same PVob
 * @throws NullPointerException if aPVob or aParent or aComment is null
 */
static public Folder makeOrGetFolder(final String aFoldername, final Folder aParent,
    final IPVob aPVob, final Comment aComment) throws CCException {
    Folder aFolderRes = null;
    if (aPVob.equals(aParent.getPVob() == false) { 
       // UNCHECKED EXCEPTION because the caller failed to live up
       // to the documented entry criteria for this function
       Assert.isLegal(false, "parent Folder must be in the same PVob than " + aPVob); }

    final String ctcmd = "mkfolder " + aComment.getCommentOption() + 
        " -in " + getPNameFromRepoObject(aParent) + " " + aPVob.getFullName(aFolderName);

    final Status st = getCleartool().executeCmd(ctcmd);

    if (st.status || StringUtils.strictContains(st.message,"already exists.")) {
        aFolderRes = Folder.getFolder(aFolderName, aPVob);
    }
    else {
        // CHECKED EXCEPTION because the callee failed to respect his contract
        throw new CCException.Error("Unable to make/get folder '" + aFolderName + "'");
    }
    return aFolderRes;
}

以下是我在多年开发经验后的一些看法:

Checked exception. This is a part of business use case or call flow, this is a part of application logic we expect or not expect. For example connection rejected, condition is not satisfied etc. We need to handle it and show corresponding message to user with instructions what happened and what to do next (try again later etc). I usually call it post-processing exception or "user" exception. Unchecked exception. This is a part of programming exception, some mistake in software code programming (bug, defect) and reflects a way how programmers must use API as per documentation. If an external lib/framework doc says it expects to get data in some range and non null, because NPE or IllegalArgumentException will be thrown, programmer should expect it and use API correctly as per documentation. Otherwise the exception will be thrown. I usually call it pre-processing exception or "validation" exception.

目标受众。现在让我们来谈谈目标受众或设计例外的人群(根据我的观点):

检查异常。目标受众是用户/客户。 未经检查的异常。目标受众是开发人员。换句话说,未检查异常仅是为开发人员设计的。

通过应用程序开发生命周期阶段。

受控异常被设计为在整个生产生命周期中存在,作为应用程序处理异常情况的正常和预期机制。 未检查异常被设计为只存在于应用程序开发/测试生命周期期间,所有这些异常都应该在这段时间内得到修复,并且当应用程序已经在生产环境中运行时不应该抛出。

框架通常使用未检查异常(例如Spring)的原因是框架不能确定应用程序的业务逻辑,这取决于开发人员捕捉并设计自己的逻辑。

检查例外: 如果客户端可以从异常中恢复,并希望继续,请使用受控异常。

未经检查的异常: 如果客户端在异常发生后不能做任何事情,则引发未检查的异常。

示例:如果您希望在方法a()中执行算术操作,并且基于a()的输出,则必须执行另一个操作。如果方法A()的输出为空,而您在运行时并不期望它,那么您将抛出空指针异常,即运行时异常。

请参考这里

You can call it a checked or unchecked exception; however, both types of exception can be caught by the programmer, so the best answer is: write all of your exceptions as unchecked and document them. That way the developer who uses your API can choose whether he or she wants to catch that exception and do something. Checked exceptions are a complete waste of everyone's time and it makes your code a shocking nightmare to look at. Proper unit testing will then bring up any exceptions that you may have to catch and do something with.