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

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


当前回答

我使用的规则是:永远不要使用未经检查的异常!(或者当你看不到任何方法的时候)

从使用您的库的开发人员或使用您的库/应用程序的最终用户的角度来看,遇到由于不应出现的异常而导致应用程序崩溃的情况真的很糟糕。指望包罗万象也不行。

通过这种方式,最终用户仍然可以看到错误消息,而不是应用程序完全消失。

其他回答

你是正确的。

未经检查的异常用于让系统快速失效,这是一件好事。为了正常工作,你应该清楚地说明你的方法期望什么。通过这种方式,您可以只验证输入一次。

例如:

/**
 * @params operation - The operation to execute.
 * @throws IllegalArgumentException if the operation is "exit"
 */
 public final void execute( String operation ) {
     if( "exit".equals(operation)){
          throw new IllegalArgumentException("I told you not to...");
     }
     this.operation = operation; 
     .....  
 }
 private void secretCode(){
      // we perform the operation.
      // at this point the opreation was validated already.
      // so we don't worry that operation is "exit"
      .....  
 }

举个例子。关键是,如果系统快速失效,那么你就会知道它在哪里以及为什么会失效。你会得到这样的堆栈跟踪:

 IllegalArgumentException: I told you not to use "exit" 
 at some.package.AClass.execute(Aclass.java:5)
 at otherPackage.Otherlass.delegateTheWork(OtherClass.java:4569)
 ar ......

你会知道发生了什么。“delegateTheWork”方法中的OtherClass(在第4569行)使用“exit”值调用你的类,即使它不应该这样做等等。

否则你将不得不在你的代码中散布验证,这很容易出错。另外,有时很难跟踪哪里出了问题,可能会有几个小时令人沮丧的调试

同样的事情也发生在nullpointerexception中。如果你有一个700行的类,有15个方法,使用30个属性,它们都不能为空,而不是在每个方法中验证可为空性,你可以使所有这些属性都是只读的,并在构造函数或工厂方法中验证它们。

 public static MyClass createInstane( Object data1, Object data2 /* etc */ ){ 
      if( data1 == null ){ throw NullPointerException( "data1 cannot be null"); }

  }


  // the rest of the methods don't validate data1 anymore.
  public void method1(){ // don't worry, nothing is null 
      ....
  }
  public void method2(){ // don't worry, nothing is null 
      ....
  }
  public void method3(){ // don't worry, nothing is null 
      ....
  }

当程序员(你或你的同事)做的每件事都是正确的,验证输入,运行测试,所有的代码都是完美的,但代码连接到第三方web服务可能会关闭(或你正在使用的文件被另一个外部进程删除等)时,检查异常是有用的。web服务甚至可以在连接尝试之前进行验证,但是在数据传输过程中出错了。

在这种情况下,你或你的同事都无能为力。但你还是得做点什么,不能让应用程序在用户眼中消失。你使用一个检查异常来处理异常,当发生这种情况时你能做什么?,大多数时候,只是尝试记录错误,可能会保存你的工作(应用程序工作),并向用户显示消息。(网站blabla宕机,请稍后重试)

如果检查过的异常被过度使用(通过在所有方法签名中添加“throw exception”),那么您的代码将变得非常脆弱,因为每个人都会忽略这个异常(因为太普遍),代码质量将严重受损。

如果过度使用未检查异常,也会发生类似的情况。这段代码的用户不知道是否会出现错误,因此进行了大量的尝试{…}catch(Throwable t)将出现。

我同意将未检查异常作为规则的偏好,特别是在设计API时。调用方总是可以选择捕获记录在案的、未检查的异常。你只是没有必要强迫打电话的人。

I find checked exceptions useful at the lower-level, as implementation detail. It often seems like a better flow of control mechanism than having to manage a specified error "return code". It can sometimes help see the impact of an idea for a low level code change too... declare a checked exception downstream and see who would need to adjust. This last point doesn't apply if there are a lot of generic: catch(Exception e) or throws Exception which is usually not too well-thought out anyway.

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

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

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


对于这两个异常,我将为我的应用程序创建自己的未检查和已检查的异常(这里提到过,这是一个很好的实践),除了非常常见的未检查异常(如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;
}

我认为我们可以从以下几个问题来考虑例外:

为什么会发生异常?当它发生时我们能做什么

一个错误,一个bug。如方法调用空对象。

String name = null;
... // some logics
System.out.print(name.length()); // name is still null here

这种异常应该在测试期间修复。否则,它会破坏生产,你会得到一个非常严重的bug,需要立即修复。这种异常不需要检查。

通过来自外部的输入,您不能控制或信任外部服务的输出。

String name = ExternalService.getName(); // return null
System.out.print(name.length());    // name is null here

在这里,如果您希望在名称为空时继续,则可能需要检查名称是否为空,否则,您可以让它单独运行,它将在这里停止并向调用者提供运行时异常。 这种异常不需要检查。

通过来自外部的运行时异常,您不能控制或信任外部服务。

在这里,如果您希望在发生异常时继续执行,则可能需要从ExternalService捕获所有异常,否则,您可以不去管它,它将在这里停止并向调用者提供运行时异常。

通过来自外部的受控异常,您不能控制或信任外部服务。

在这里,如果您希望在发生异常时继续执行,则可能需要从ExternalService捕获所有异常,否则,您可以不去管它,它将在这里停止并向调用者提供运行时异常。

在这种情况下,我们需要知道ExternalService中发生了什么样的异常吗?这取决于:

如果您可以处理某些类型的异常,则需要捕获它们并进行处理。对其他人来说,泡一下。 如果您需要记录或响应用户特定的异常,您可以捕获它们。对其他人来说,泡一下。

我使用的规则是:永远不要使用未经检查的异常!(或者当你看不到任何方法的时候)

有一种情况正好相反:永远不要使用受控异常。我不愿意在辩论中偏袒任何一方(双方都有很好的论据!),但相当多的专家认为,事后看来,受控例外是一个错误的决定。

对于一些讨论,请查看维基百科网站的“已检查异常的价值可疑”。另一个早期广泛争论的例子是Rod Waldhoff的博客文章。