在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)将出现。

其他回答

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

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

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

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

在任何一个足够大的系统上,有很多层,检查异常是无用的,因为无论如何,您需要一个架构级策略来处理异常将如何处理(使用故障屏障)。

使用受控异常,您的错误处理策略是微管理的,在任何大型系统上都无法承受。

大多数情况下,您不知道错误是否“可恢复”,因为您不知道API的调用者位于哪一层。

假设我创建了一个StringToInt API,用于将整数的字符串表示形式转换为Int。如果API是用“foo”字符串调用的,我必须抛出检查异常吗?它可以恢复吗?我不知道,因为在他的层中,我的StringToInt API的调用者可能已经验证了输入,如果抛出这个异常,它要么是一个错误,要么是一个数据损坏,它是不可恢复的这一层。

在这种情况下,API的调用者不想捕获异常。他只想让异常“冒出来”。如果我选择了一个受控异常,这个调用者将有大量无用的catch块,只能人为地重新抛出异常。

大多数时候,什么是可恢复的取决于API的调用者,而不是API的编写者。API不应该使用受控异常,因为只有未受控异常才允许选择捕获或忽略异常。

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

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)的原因是框架不能确定应用程序的业务逻辑,这取决于开发人员捕捉并设计自己的逻辑。

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.

当你想要向调用者提供信息时,受控异常对于可恢复的情况很有用(例如,权限不足,文件未找到等)。

未检查异常很少用于在运行时通知用户或程序员严重错误或意外情况。如果你编写的代码或库将被其他人使用,不要抛出这些异常,因为他们可能不希望你的软件抛出未经检查的异常,因为编译器不会强制捕获或声明这些异常。