何时select已选中和未选中的例外

在Java(或任何其他具有检查exception的语言)中,当创build自己的exception类时,如何决定是否应该检查或不检查?

我的直觉就是说,如果调用者能够以某种有效的方式恢复,那么就会要求检查一个exception,对于不可恢复的情况来说,一个未经检查的exception更多,但是我会对其他人的想法感兴趣。

检查exception是好的,只要你明白什么时候应该使用。 Java核心API无法遵循SQLException的这些规则(有时甚至是IOException),这就是为什么它们如此糟糕。

检查exception应该用于预期的 ,但是可以从中恢复的不可预知的错误。

未检查的exception应该用于其他一切。

我会为你解决这个问题,因为大多数人误解了这个意思。

  1. 预期但不可预知 :调用者尽其所能地validationinput参数,但是一些不受控制的情况导致操作失败。 例如,您尝试读取文件,但有人在检查文件是否存在与读取操作开始之间删除文件。 通过声明一个检查的exception,你告诉调用者预测这个失败。
  2. 合理的恢复 :告诉调用者没有必要预测他们无法从中恢复的exception。 如果用户试图从不存在的文件读取,则调用者可以提示他们input新的文件名。 另一方面,如果由于编程错误(无效的方法参数或错误的方法实现)而导致该方法失败,那么应用程序就无法在执行过程中修复该问题。 最好的办法是logging下问题,等待开发者稍后再修复。

除非你抛出的exception符合上述所有条件,否则应该使用未经检查的exception。

在每个级别重新评估 :有时捕获检查exception的方法不是处理错误的正确位置。 在这种情况下,请考虑您自己的来电者的合理性。 如果预料到这个例外,那么他们从中恢复是不可能的,合理的,你应该自己去做一个检查的例外。 如果没有,你应该把这个exception包装成一个未经检查的exception。 如果你遵循这个规则,你会发现自己将检查的exception转换为未经检查的exception,反之亦然,这取决于你所处的层次。

对于checked和uncheckedexception, 使用正确的抽象级别 。 例如,具有两个不同实现(数据库和文件系统)的代码库应避免通过抛出SQLExceptionIOException暴露实现特定的细节。 相反,它应该将这个exception包装在一个跨所有实现的抽象中(例如RepositoryException )。

来自Java学习者 :

当发生exception时,你必须捕捉和处理exception,或者告诉编译器你不能通过声明你的方法抛出exception来处理它,那么使用你的方法的代码将不得不处理这个exception(甚至是也可以select声明它抛出exception,如果它不能处理它)。

编译器会检查我们做了两件事情之一(catch或者declare)。 所以这些被称为检查exception。 但错误和运行时exception不会被编译器检查(即使您可以select捕获或声明,它不是必需的)。 所以,这两个被称为未经检查的例外。

错误用于表示在应用程序之外发生的情况,例如系统崩溃。 运行时exception通常是由应用程序逻辑中的错误发生的。 在这些情况下你什么都做不了。 发生运行时exception时,必须重新编写程序代码。 所以,这些不是由编译器检查。 这些运行时exception将在开发和testing阶段发现。 然后,我们必须重构我们的代码,以消除这些错误。

我使用的规则是:永远不要使用未经检查的exception! (或者当你没有看到任何方式)

相反,有一个非常强大的例子:从不使用检查的exception。 我不愿意在辩论中支持,但似乎有一个广泛的共识,认为引入检查的例外是事后的错误决定。 请不要拍摄使者,并参考这些 论点 。

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

对于已检查的exception,您的error handling状态是微pipe理的,在任何大型系统上都是无法忍受的。

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

假设我创build了一个将整数的string表示forms转换为Int的StringToInt API。 如果使用“foo”string调用API,我必须抛出一个检查exception吗? 它可以恢复吗? 我不知道,因为在他的图层中,我的StringToInt API的调用者可能已经validation了input,并且如果抛出这个exception,它可能是一个错误或数据损坏,并且对于这个层是不可恢复的。

在这种情况下,API的调用者不想捕获exception。 他只想让这个例外“泡”起来。 如果我select了一个被检查的exception,这个调用者将会有大量无用的catch块来人为地重新抛出exception。

什么是可恢复的取决于API的调用者的大部分时间,而不是API的编写者。 API不应该使用已检查的exception,因为只有未经检查的exception允许select捕获或忽略exception。

你是对的

未经检查的exception用于使系统快速失效 ,这是一件好事。 你应该清楚说明你的方法期望什么才能正常工作。 这样你可以只validation一次input。

例如:

 /** * @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”方法(在4569行)的OtherClass调用你的类与“退出”的价值,即使它不应该等。

否则,你将不得不在你的代码中散布validation,这很容易出错。 此外,有时很难跟踪出了什么问题,你可能会期望几个小时的debugging令人沮丧

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

  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 .... } 

检查exception当编程人员(您或您的同事)做了正确的事情,validation了input,运行testing,并且所有的代码都是完美的,但代码连接到可能closures的第三方Web服务(或文件你正在使用被其他外部进程删除等)。 在尝试连接之前,web服务甚至可能会被validation,但在数据传输过程中出现了问题。

在这种情况下,你或你的同事可以做什么来帮助它。 但是,你仍然需要做一些事情,而不是让应用程序在用户眼中死去并消失。 你使用一个检查过的exception来处理这个exception,当这种情况发生时你可以做什么?大多数情况下,只是为了尝试logging错误,可能会保存你的工作(应用程序工作)并向用户显示一条消息。 (该网站blabla下来,请稍后重试等)

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

如果过度使用未经检查的exception,会发生类似的情况。 该代码的用户不知道是否有可能出现错误,很多try {…} catch(Throwable t)会出现。

这是我的“最后的经验法则”。
我用:

  • 我的方法的代码中的未经检查的exception 由于调用者 (包括明确和完整的文档
  • 检查 由于被调用者故障 ,我需要明确给任何人想要使用我的代码的exception

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


对于这两个例外,我将为我的应用程序创build自己的未经检查和检查的exception(这是一个很好的习惯, 如此处所述 ),除非常见的未经检查的exception(如NullPointerException)

例如,下面这个特定函数的目标是使(或者如果已经存在)一个对象,
含义:

  • 对象的容器必须存在(CALLER的责任
    =>未经检查的exception,并清除这个被称为函数的javadoc注释)
  • 其他参数不能为空
    (编码器的select将其放在CALLER上:编码器不会检查空参数,但编码器会logging它)
  • 结果不能为NULL
    (被叫方的代码的责任和select,对于主叫方来说是非常感兴趣的select
    =>检查exception,因为如果对象不能被创build/find,每个调用者必须做出决定,并且该决定必须在编译时被强制执行:他们不能使用这个function,而不必处理这种可能性,例外)。

例:


 /** * 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; } 

这不仅是从exception中恢复的能力问题。 在我看来,最重要的是来电者是否有兴趣去捕捉例外。

如果您在其他地方编写库或在应用程序中使用较低级别的图层,请问问自己,调用者是否有兴趣捕获(了解)您的exception。 如果他不是,那么使用一个不受检查的例外,所以你不会不必要地加重他的负担。

这是许多框架使用的哲学。 特别是,Spring和hibernate想到了 – 它们将已知的已检查exception转换为未经检查的exception,这是因为已检查的exception在Java中被过度使用。 我能想到的一个例子是来自json.org的JSONException,它是一个检查的exception,大部分是烦人的 – 它应该是没有检查的,但开发者根本没有想到它通过。

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

您可以将其称为已选中或未选中的例外; 然而, 这两种types的exception都可以被程序员捕获,所以最好的答案是:将所有的exception写入未经检查并logging下来。 这样,使用API​​的开发人员可以select是否要捕获该exception并执行某些操作。 检查exception是对每个人时间的完全浪费,它使你的代码成为一个令人震惊的恶梦。 然后,适当的unit testing将带来任何你可能需要捕捉和处理的exception。

这是一个非常简单的解决scheme,您的Checked / Unchecked困境。

规则1:在执行代码之前将未检查的exception看作可testing的条件。 例如…

 x.doSomething(); // the code throws a NullPointerException 

其中x是空的… …代码应该可能有以下…

 if (x==null) { //do something below to make sure when x.doSomething() is executed, it won't throw a NullPointerException. x = new X(); } x.doSomething(); 

规则2:将检查exception看作代码执行时可能发生的不可testing条件。

 Socket s = new Socket(“google.com”, 80); InputStream in = s.getInputStream(); OutputStream out = s.getOutputStream(); 

…在上面的例子中,由于DNS服务器closures,URL(google.com)可能不可用。 即使在DNS服务器正在工作并将“google.com”名称parsing为IP地址的情况下,如果连接到google.com,在任何时候,networking都可能停止。 在读取和写入stream之前,您无法一直testingnetworking。

在我们能够知道是否存在问题之前,有时代码必须执行。 通过强迫开发人员以强制他们通过Checked Exception来处理这些情况的方式编写他们的代码,我必须向发明这个概念的Java创build者倾诉自己的一切。

一般来说,Java中几乎所有的API都遵循上面的两条规则。 如果您尝试写入文件,则在完成写入之前磁盘可能已满。 其他进程可能导致磁盘变满。 没有办法testing这种情况。 对于那些随时随地使用硬件的人来说,使用硬件可能会失败,Checked Exceptions似乎是解决这个问题的一个很好的方法。

这是一个灰色的地方。 如果需要进行很多testing(如果声明中有很多&&和||),抛出的exception将是一个CheckedException,仅仅是因为这样做太痛苦了 – 你根本就不能说这个问题是一个编程错误。 如果有less于10个testing(例如'if(x == null)'),那么程序员错误应该是UncheckedException。

处理语言翻译时,事情会变得有趣。 根据上面的规则,应该将一个语法错误视为一个Checked或Unchecked Exception? 我认为,如果语言的语法在被执行之前可以被testing,它应该是一个UncheckedException。 如果语言不能被testing – 与在个人计算机上运行汇编代码类似,那么语法错误应该是一个Checked Exception。

上面的两个规则可能会删除90%的关心你的select。 要总结规则,请遵循以下模式:1)如果要执行的代码在执行前可以进行testing,以便其正确运行,并且如果发生exception(又名程序员错误),则Exception应该是UncheckedException(RuntimeException的子类)。 2)如果要执行的代码在被执行之前不能被testing以使其正确运行,则该exception应该是被检查的exception(exception的子类)。

检查exception:如果客户端可以从exception中恢复并希望继续,请使用检查的exception。

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

例如:如果您希望在方法A()中进行算术运算并根据A()的输出进行操作,则需要执行另一个操作。 如果方法A()中的输出为空,那么在运行期间您并不期待该方法,那么您应该抛出Null指针exception,这是运行时exception。

请参阅这里

我同意一般情况下对非检查exception的偏好,特别是在deviseAPI时。 调用者总是可以select捕获logging的,未经检查的exception。 你只是不必要的强迫来电。

作为实现细节,我发现在较低级别有用的检查exception。 它通常看起来像是一个更好的控制机制stream程,而不必pipe理指定的错误“返回代码”。 它有时可以帮助我们看到一个想法对于低级别代码变更的影响…在下游声明一个检查的exception,看看谁需要调整。 如果存在大量generics: catch(Exception e)或者抛出exception (Exception),那么这个最后一点不适用。

检查exception对于要向调用者提供信息的可恢复情况有用(即权限不足,文件未find等)。

未经检查的exception很less用于在运行期间通知用户或程序员严重错误或意外情况。 如果你编写的代码或库会被别人使用,那么不要抛出它们,因为编译器不会强迫它们被捕获或声明,因为它们可能不会期望你的软件抛出未经检查的exception。

每当发生exception的可能性较小时,即使在捕获exception之后,我们也可以继续进行,并且我们无法做任何事情来避免该exception,那么我们可以使用checkedexception。

每当我们想要做一些有意义的事情,当一个特定的exception发生,当这个exception是预期的,但不是确定的,那么我们可以使用检查exception。

每当exception导航到不同的层次时,我们都不需要在每一层都捕捉它,在这种情况下,我们可以使用运行时exception或包装exception作为非检查exception。

运行时exception用于最有可能发生的exception,没有办法走得更远,什么都不能恢复。 所以在这种情况下,我们可以对这个例外采取预防措施。 EX:NUllPointerException,ArrayOutofBoundsException。 这些更可能发生。 在这种情况下,我们可以在编码时采取预防措施以避免这种exception。 否则,我们将不得不在每个地方写try catch块。

更一般的例外可以取消选中,不太普通的选项被检查。

我想我们可以考虑几个问题的解释:

为什么豁免发生? 发生的时候我们该怎么办?

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

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

这种exception应该在testing过程中加以修正。 否则,它会打破生产,并且你有一个很高的bug,需要立即修复。 这种例外情况不需要检查。

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

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

在这里,你可能需要检查这个名字是否为null,如果你想继续的时候是null,否则,你可以让它自己停下来,给这个调用者一个运行时exception。 这种例外情况不需要检查。

通过来自外部的运行时exception,您无法控制或信任外部服务。

在这里,你可能需要从ExternalService中捕获所有的exception,如果你想在这个时候继续,否则,你可以让它独自停下来,给调用者一个运行时exception。

通过外部检查exception,您不能控制或信任外部服务。

在这里,你可能需要从ExternalService中捕获所有的exception,如果你想在这个时候继续,否则,你可以让它独自停下来,给调用者一个运行时exception。

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

  1. 如果你能处理某些exception,你需要抓住它们并处理。 对于其他人,泡他们。

  2. 如果你需要login或响应用户的具体执行,你可以抓住他们。 对于其他人,泡他们。

以下是我想分享我的观点,我经过多年的发展经验:

  1. 检查exception。 这是业务用例或调用stream程的一部分,这是我们期望或不期望的应用程序逻辑的一部分。 例如连接被拒绝,条件不满足等等。我们需要处理它,并向用户显示相应的消息,指示发生了什么以及接下来要做什么(稍后再试一次等​​)。 我通常把它称为后处理exception或“用户”exception。

  2. 未经检查的例外。 这是编程exception的一部分,软件代码编程中的一些错误(错误,缺陷),反映了程序员如何按文档使用API​​。 如果一个外部的lib / framework文件声明它希望得到一些范围内的数据而不是null,因为NPE或者IllegalArgumentException将会被抛出,程序员应该期待它并且根据文档正确地使用API​​。 否则,将抛出exception。 我通常称之为预处理exception或“validation”exception。

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

  1. 检查exception。 目标受众是用户/客户。
  2. 未经检查的例外。 目标受众是开发者。 换句话说,未经检查的exception仅为开发人员devise。

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

  1. 检查exception被devise为在整个生产生命周期中存在,正如应用程序处理exception情况的正常和期望的机制。
  2. 未经检查的exception被devise为仅在应用程序开发/testing生命周期中存在,所有这些exception都应在此期间修复,并且在应用程序已经在生产环境中运行时不应抛出。

框架通常使用未经检查的exception(例如Spring)的原因是框架无法确定应用程序的业务逻辑,这取决于开发人员接下来要devise自己的逻辑。

我认为当声明应用程序exception时,它应该是未经检查的exception,即RuntimeException的子类。 原因是它不会使应用程序代码与try-catch混淆,并抛出方法上的声明。 如果您的应用程序正在使用Java Api,它会抛出检查exception,无论如何需要处理。 对于其他情况,应用程序可以抛出未经检查的exception。 如果应用程序调用者仍然需要处理未经检查的exception,则可以完成。

我使用的规则是:永远不要使用未经检查的exception! (或者当你没有看到任何方式)

从开发人员的angular度来看,使用你的库或最终用户使用你的库/应用程序,它真的很尴尬,面临一个应用程序,由于意外的exception崩溃。 而且指望一个都是不好的。

这样,最终用户仍然可以看到错误信息,而不是应用程序完全消失。