代码之家  ›  专栏  ›  技术社区  ›  Matt Sheppard

何时选择选中和未选中的异常

  •  264
  • Matt Sheppard  · 技术社区  · 17 年前

    在Java(或任何其他有检查异常的语言)中,在创建自己的异常类时,如何决定是检查还是取消检查?

    我的直觉是,在调用者可能能够以某种富有成效的方式恢复的情况下,会调用检查异常,而对于不可恢复的情况,不检查异常更适用,但我会对其他人的想法感兴趣。

    18 回复  |  直到 14 年前
        1
  •  187
  •   Gili    7 年前

    只要你知道什么时候应该使用它们,检查异常就很好。Java核心API未能遵循SQLException(有时甚至IOException)的这些规则,这就是它们如此糟糕的原因。

    检查违例 应用于 可预测的 但是 不可预防的 错误是 合理的恢复 .

    不检查违例 应该用于其他所有事情。

    我会为你详细解释一下,因为大多数人误解了这意味着什么。

    1. 可预测但不可预防 :调用者尽其所能验证输入参数,但他们无法控制的某些情况导致操作失败。例如,您尝试读取一个文件,但在您检查它是否存在和读取操作开始之间,有人删除了它。通过声明一个已检查的异常,您可以告诉调用者预期此失败。
    2. 合理的恢复 :告诉调用者预测他们无法恢复的异常是没有意义的。如果用户试图从不存在的文件中读取,调用者可以提示他们输入新的文件名。另一方面,如果方法因编程错误(无效的方法参数或有缺陷的方法实现)而失败,应用程序在执行过程中无法解决问题。它能做的最好的事情就是记录问题,并等待开发人员稍后修复它。

    除非你抛出的异常满足 所有 在上述条件中,它应该使用Unchecked Exception。

    在各个层面重新评估 :有时捕获已检查异常的方法不是处理错误的正确位置。在这种情况下,考虑一下对你自己的来电者来说什么是合理的。如果异常是可预测的、不可预防的,并且他们可以从中恢复,那么你应该自己抛出一个已检查的异常。如果没有,则应将异常包装为未选中的异常。如果你遵循这条规则,你会发现自己将选中的异常转换为未选中的异常,反之亦然,具体取决于你所在的层。

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

        2
  •  55
  •   Espo    17 年前

    来自 A Java Learner :

    当发生异常时,您必须 要么捕获并处理异常, 或者告诉编译器您无法处理 通过声明你的方法 抛出该异常,然后代码 使用你的方法的人必须 处理该异常(即使它也 可以选择声明它抛出 如果无法处理,则表示异常)。

    编译器将检查我们是否已完成 两件事之一(抓,或 声明)。所以这些被称为检查 例外情况。但是错误和运行时 不检查例外情况 编译器(即使你可以选择 捕捉或声明它不是 需要)。所以,这两个被称为 未经检查的例外情况。

    错误用于表示这些 发生在外部的条件 应用程序,例如崩溃 系统。运行时异常包括 通常由故障引起 应用程序逻辑。你做不到 在这种情况下的任何事情。什么时候 运行时发生异常,您必须 重写你的程序代码。那么,这些 编译器不会检查。这些 运行时异常将在 开发和测试阶段。然后 我们必须重构代码以删除 这些错误。

        3
  •  45
  •   Konrad Rudolph    17 年前

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

    情况正好相反: 从不 使用已检查的例外情况。我不愿意在辩论中偏袒任何一方(双方都有很好的论点!),但相当多的专家认为,事后看来,检查例外是一个错误的决定。

    有关讨论,请查看WikiWikiWebs “Checked exceptions are of dubious value” 另一个早期广泛争论的例子是 Rod Waldhoff’s blog post .

        4
  •  38
  •   Stephane    16 年前

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

    除了已检查的例外情况,您的错误处理策略是微观管理的,在任何大型系统上都是无法忍受的。

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

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

    在这种情况下,API的调用程序不想捕获异常。他只想让例外“浮出水面”。如果我选择了一个已检查的异常,则此调用者将有大量无用的catch块,只能人为地重新抛出异常。

    什么是可恢复的大部分时间取决于API的调用方,而不是API的编写方。API不应使用已检查的异常,因为只有未检查的异常才允许选择捕获或忽略异常。

        5
  •  28
  •   OscarRyz    16 年前

    你说得对。

    未检查的例外情况 用于让系统 fail fast 这是一件好事。你应该清楚地说明你的方法需要什么才能正常工作。这样,您只能验证输入一次。

    例如:

    /**
     * @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使用“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)将会出现。

        6
  •  18
  •   Community CDub    8 年前

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

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


    对于这两个异常,我将为我的应用程序创建自己的未经检查和已检查的异常(这是一个很好的做法, as mentionned here ),除了非常常见的未经检查的异常(如NullPointerException)

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

    • 创建/获取对象的容器必须存在(由调用者负责
      =>未检查的异常,并清除此被调用函数的javadoc注释)
    • 其他参数不能为空
      (选择将其放在CALLER上的编码器:编码器不会检查空参数,但会记录它)
    • 结果不能为空
      (被叫方代码的责任和选择,主叫方非常感兴趣的选择
      =>选中异常,因为如果无法创建/找到对象,每个调用者都必须做出决定,并且必须在编译时强制执行该决定:如果不处理这种可能性,他们就不能使用此函数,这意味着 有方格图案的 例外)。

    例子:


    /**
     * 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;
    }
    
        7
  •  17
  •   Yoni    16 年前

    这不仅仅是从异常中恢复的能力问题。在我看来,最重要的是呼叫者是否有兴趣捕捉异常。

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

    这是许多框架使用的哲学。特别是Spring和hibernate,它们将已知的已检查异常转换为未检查异常,正是因为Java中过度使用了已检查异常。我能想到的一个例子是json.org的JSONException,这是一个已检查的异常,而且大多很烦人——它应该是未检查的,但开发人员根本没有想过。

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

        8
  •  11
  •   user3598189    11 年前

    这里有一个非常简单的解决方案,可以解决您的选中/未选中困境。

    规则1:将未检查异常视为代码执行前的可测试条件。 例如

    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:将Checked Exception视为代码执行时可能发生的不可测试的情况。

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

    在上面的示例中,由于DNS服务器关闭,URL(google.com)可能不可用。即使在DNS服务器工作并将google.com名称解析为IP地址的那一刻,如果连接到google.com,在任何时候,网络都可能崩溃。在读写流之前,你根本无法一直测试网络。

    有时,在我们知道是否存在问题之前,代码必须执行。通过强迫开发人员以这种方式编写代码,迫使他们通过Checked Exception处理这些情况,我必须向发明这个概念的Java的创建者致敬。

    一般来说,Java中的几乎所有API都遵循上述2条规则。如果您尝试写入文件,磁盘可能会在完成写入之前填满。可能是其他进程导致磁盘已满。根本没有办法测试这种情况。对于那些在任何时候使用硬件都可能失败的人来说,Checked Exceptions似乎是解决这个问题的一个优雅的解决方案。

    这有一个灰色地带。如果需要进行许多测试(如果语句中有很多&amp;and ||,这会让人大吃一惊),抛出的异常将是CheckedException,因为很难正确处理,你根本不能说这个问题是编程错误。如果测试数量远少于10个(例如If(x==null)),则程序员错误应为UncheckedException。

    与语言口译员打交道时,事情会变得有趣。根据上述规则,语法错误应被视为选中异常还是未选中异常?我认为,如果语言的语法可以在执行之前进行测试,那么它应该是一个UncheckedException。如果语言不能像汇编代码在个人计算机上运行那样进行测试,那么语法错误应该是一个检查异常。

    上述两条规则可能会消除你90%的选择顾虑。要总结规则,请遵循以下模式 1) 如果要执行的代码可以在执行之前进行测试,以确保其正确运行,并且如果发生异常,即程序员错误,则该异常应为UncheckedException(RuntimeException的子类)。 2) 如果要执行的代码在执行之前无法进行测试以使其正确运行,则异常应为已检查异常(异常的子类)。

        9
  •  8
  •   Keith Pinson sumit vedi    12 年前

    您可以将其称为已检查或未检查的异常;然而, 两者 程序员可以捕获异常类型,因此最好的答案是:写 所有 您的例外情况如下 不受限制的 并记录下来。这样,使用API的开发人员就可以选择是否要捕获该异常并执行某些操作。检查异常完全浪费了每个人的时间,这让你的代码看起来像是一场令人震惊的噩梦。适当的单元测试会发现你可能必须捕捉并处理的任何异常。

        10
  •  7
  •   Reachgoals    12 年前

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

    未检查异常: 如果客户端在异常发生后无法执行任何操作,则引发未经检查的异常。

    示例:如果你希望在方法a()中进行算术运算,并且基于a()的输出,那么你必须进行另一个操作。如果方法A()的输出为null,而这是您在运行时不期望的,那么您应该抛出null指针异常,这是运行时异常。

    参考 here

        11
  •  2
  •   Scott Kurz    12 年前

    以下是我想分享我多年开发经验后的看法:

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

    2. 未检查的异常。这是编程异常的一部分,是软件代码编程中的一些错误(bug、缺陷),反映了程序员必须按照文档使用API的方式。如果一个外部lib/framework文档说它希望获得某个范围内的非null数据,因为会抛出NPE或IllegalArgumentException,程序员应该预料到这一点,并根据文档正确使用API。否则将抛出异常。 我通常称之为预处理异常或“验证”异常。

    根据目标受众。现在让我们来谈谈目标受众或人群——例外情况已经设计好了(根据我的意见):

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

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

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

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

        12
  •  2
  •   Denys    8 年前

    我们必须根据是否是程序员错误来区分这两种类型的异常。

    • 如果错误是程序员错误,则必须是未检查异常 . 例如: SQLException/IOException/NullPointerException。这些例外情况是 编程错误。它们应该由程序员处理。当在 JDBC API,SQLException is Checked异常,In Spring JDBCTemplate 这是一个未经检查的例外。程序员不担心 使用Spring时发生SqlException。
    • 如果错误不是程序员错误,并且原因来自外部,则必须是检查异常。 例如:如果 文件被删除或文件权限被其他人更改,它 应该恢复。

    FileNotFoundException是理解细微差异的一个很好的例子。如果找不到文件,则抛出FileNotFoundException。这种例外有两个原因。如果文件路径由开发人员定义或通过GUI从最终用户处获取,则应为未选中异常。如果文件被其他人删除,则应为“已检查异常”。

    检查异常可以通过两种方式处理。这些正在使用try-catch或传播异常。在异常传播的情况下,调用堆栈中的所有方法都将 紧密耦合 由于异常处理。因此,我们必须谨慎使用Checked Exception。

    如果你开发了一个分层的企业系统,你必须选择大部分未检查的异常来抛出,但不要忘记在你什么都做不了的情况下使用检查的异常。

        13
  •  1
  •   David Crow    17 年前

    我同意将未检查的异常作为一种规则,尤其是在设计API时。调用者始终可以选择捕获记录在案的、未经检查的异常。你只是没有不必要地强迫来电者。

    我发现检查异常在较低级别很有用,作为实现细节。这似乎是一种比必须管理指定错误“返回代码”更好的控制机制。它有时也有助于看到一个想法对低级代码更改的影响。..在下游声明一个已检查的异常,看看谁需要调整。如果有很多通用的情况,最后一点不适用: catch(例外情况e) 抛出异常 无论如何,这通常都不是经过深思熟虑的。

        14
  •  1
  •   ramu p    11 年前

    选中的异常对于您想向调用者提供信息的可恢复情况非常有用(即权限不足、找不到文件等)。

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

        15
  •  1
  •   community wiki 2 revs Jacky    9 年前

    如果异常不太可能发生,即使在捕获到异常后我们也可以继续,并且我们无法避免该异常,那么我们可以使用检查异常。

    每当我们想在发生特定异常时做一些有意义的事情,并且当该异常是预期的但不确定时,我们可以使用checked异常。

    每当在不同层中导航异常时,我们不需要在每一层都捕获它,在这种情况下,我们可以使用运行时异常或将异常包装为未检查的异常。

    运行时异常用于最有可能发生异常、无法进一步处理且无法恢复的情况。因此,在这种情况下,我们可以对这种例外情况采取预防措施。例如:NUllPointerException,ArrayOutofBoundsException。这些更有可能发生。在这种情况下,我们可以在编码时采取预防措施来避免此类异常。否则,我们将不得不在每个地方编写try-catch块。

    更一般的例外可以设置为“未选中”,不太一般的例外则被选中。

        16
  •  1
  •   ailhanli    7 年前

    我认为我们可以从几个问题中考虑解释:

    为什么会发生性行为?当它发生时,我们能做什么

    一个错误,一个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中发生了什么样的异常吗? 这取决于:

    1. 如果你能处理某些类型的异常,你需要捕获它们并进行处理。对其他人来说,把它们泡起来。

    2. 如果你需要记录或响应用户的特定执行,你可以捕获它们。对其他人来说,把它们泡起来。

        17
  •  0
  •   Akash Aggarwal    8 年前

    我认为在声明Application Exception时,它应该是Unchecked Exception,即RuntimeException的子类。 原因是它不会在方法上使用try-catch和throws声明来混淆应用程序代码。如果你的应用程序使用的是Java Api,它抛出了无论如何都需要处理的已检查异常。对于其他情况,应用程序可以抛出未经检查的异常。如果应用程序调用者仍然需要处理未经检查的异常,则可以完成。

        18
  •  -12
  •   jochen.derwae    17 年前

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

    从使用库的开发人员或使用库/应用程序的最终用户的角度来看,遇到因不可数异常而崩溃的应用程序真的很糟糕。指望包罗万象也不好。

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