何时捕获异常与何时抛出异常?

时间:2013-09-08 00:02:26

标签: java exception throw

我已经用Java编写了一段时间了。但有时候,我不明白何时应该抛出异常,何时应该捕获异常。我正在开发一个有很多方法的项目。层次结构是这样的 -

Method A will call Method B and Method B will call some Method C and Method C will call Method D and Method E.

所以目前我正在做的是 - 我在所有方法中抛出异常并在方法A中捕获它,然后记录为错误。

但我不确定这是否是正确的方法呢?或者我应该开始捕获所有方法中的异常。所以这就是为什么这种混乱开始于我 - 我应该何时捕获异常与何时应该抛出异常。我知道这是一个愚蠢的问题,但不知怎的,我正在努力理解这个主要概念。

有人能给我一个When to catch the Exception vs When to throw the Exceptions的详细例子,以便我的概念得到澄清吗?在我的情况下,我应该继续抛出异常,然后在主调用方法A中捕获它吗?

8 个答案:

答案 0 :(得分:52)

当您使用知道该做什么的方法时,您应该捕获异常。

例如,忘记它实际上是如何工作的,假设您正在编写一个用于打开和读取文件的库。

所以你有一节课,比如说:

public class FileInputStream extends InputStream {
    public FileInputStream(String filename) { }
}

现在,假设该文件不存在。你该怎么办?如果你正在努力想到答案,那是因为没有一个...... FileInputStream不知道如何解决这个问题。所以它把它扔到链子上,即:

public class FileInputStream extends InputStream {
    public FileInputStream(String filename) throws FileNotFoundException { }
}

现在,让我们说某人正在使用您的图书馆。他们的代码可能如下所示:

public class Main {
    public static void main(String... args) {
        String filename = "foo.txt";
        try {
            FileInputStream fs = new FileInputStream(filename);

            // The rest of the code
        } catch (FileNotFoundException e) {
            System.err.println("Unable to find input file: " + filename);
            System.err.println("Terminating...");
            System.exit(3);
        }
    }
}

这里,程序员知道该怎么做,所以他们捕获异常并处理它。

答案 1 :(得分:7)

当函数遇到故障时,应该抛出异常,即错误。

功能是一个工作单元,故障应视为错误或基于其对功能的影响。在函数 f 中,失败是一个错误,当且仅当它阻止 f 满足其任何被调用者的前提条件,实现任何< strong> f 自己的后置条件,或者重新建立 f f 分担维护责任的不变

有三种不同的错误:

  • 阻止函数满足必须调用的另一个函数的前提条件(例如,参数限制)的条件;
  • 阻止函数建立其自己的后置条件之一的条件(例如,产生有效的返回值是后置条件);和
  • 阻止函数重新建立它负责维护的不变量的条件。这是一种特殊的后置条件,特别适用于成员函数。每个非私有成员函数的基本后置条件是它必须重新建立其类的不变量。

任何其他条件错误,不应报告为错误。

如果某个函数检测到一个错误,它无法处理自身并且无法继续执行任何形式的正常或预期操作,则会报告错误。

处理有足够知识处理错误,转换错误或强制执行错误策略中定义的边界的地方的错误,例如或线程主线。

答案 2 :(得分:5)

我将分享一种模式,将我的培根保存在一两个生产环境中。

<强>动机

我的目标是确保可怜的家伙(也许是我)在午夜试图解决sev1支持票,得到一个很好的“由......引起的”错误的层次结构,完成使用ID等数据,所有这些都不会使代码过于混乱。

方式

为了实现这一点,我捕获所有已检查的异常并将其作为未经检查的异常重新抛出。然后,我在每个架构层的边界处使用全局捕获(通常是抽象或注入,因此它只被写入一次)。在这些点上,我可以向错误堆栈添加额外的上下文,或者决定是否记录和忽略,或者使用变量引发自定义检查的异常来保存任何额外的上下文。另外,我只记录顶层的错误以阻止'双重记录'的发生(例如cron作业,ajax的弹簧控制器)

throw new RuntimeException(checked,"Could not retrieve contact " + id);

通过这种方法,您不必为数据库相关的异常声明'throws',从而使您的GUI或业务层的方法签名不会混乱。

在现实生活中如何运作的示例:

让我们说我的代码的工作是一个自动化的过程来更新许多保险单。该体系结构支持GUI以手动触发一个策略的续订。还可以说,对于其中一个策略,数据库中的评级区域的邮政编码已损坏。

我想要实现的错误日志类型的一个例子是。

  

日志消息:由于错误而将标记策略1234标记为手动干预:

     

来自堆栈跟踪:错误更新策略1234.回滚事务... 此捕获还将涵盖诸如保存错误或生成字母之类的错误。

     

来自堆栈跟踪:引起:错误评级策略1234 ... 此捕获将检索错误,检索许多其他对象,以及算法错误,如NPE等...

     

来自堆栈跟踪:引起:错误检索评级区域73932 ...

     

来自堆栈跟踪:引起:JPA:字段'postcode'中的意外空值

答案 3 :(得分:4)

一般来说,抓住你可以做一些有用的事情。例如,用户正在尝试连接到某个数据库,并且在方法D中失败。

你想怎么处理它?也许是通过建立一个对话框“抱歉,无法连接到SERVER / DB”或其他任何内容。是方法A,B或C创建此SERVER / DB信息(例如,通过读取设置文件或要求用户输入)并尝试连接?那是可能应该处理异常的方法。或者距离应该处理它的方法至少1个。

这取决于您的应用程序,因此这只是非常一般的建议。我的大多数经验都是使用Swing /桌面应用程序,您通常可以根据哪些类正在执行程序逻辑(例如“控制器”的东西)以及谁正在设置对话框(例如“查看”内容)来获得感觉。通常,“控制器”应该捕获异常并尝试做某事。

在网络应用中,这可能会有所不同。

一些非常骨架的代码,大多数类都不存在,我不确定数据库的URL是否有意义,但你明白了。含糊不清......

/*  gets called by an actionListener when user clicks a menu etc... */
public URL openTheDB() {
  URL urlForTheDB = MyCoolDialogUtils.getMeAURL(URL somePreviousOneToFillInTheStart);
  try {
     verifyDBExists(urlForTheDB);
     // this may call a bunch of deep nested calls that all can throw exceptions
     // let them trickle up to here

     // if it succeeded, return the URL
     return urlForTheDB;
  }
  catch (NoDBExeption ndbe) {
    String message = "Sorry, the DB does not exist at " + URL;
    boolean tryAgain = MyCoolDialogUtils.error(message);
    if (tryAgain)
      return openTheDB();
    else
      return null;  // user said cancel...
  }
  catch (IOException joe) {
    // maybe the network is down, aliens have landed
    // create a reasonable message and show a dialog
  }

}

答案 4 :(得分:3)

您应该以尽可能低的级别处理异常。如果方法无法正确处理异常,则应抛出它。

  • catch如果您有连接资源的方法(例如打开文件/网络)
  • 如果等级中的等级需要有关错误的信息
  • ,则抛出

答案 5 :(得分:1)

如果要通知调用方某些失败的方法,通常会抛出异常。

例如无效的用户输入,数据库问题,网络中断,缺少文件

答案 6 :(得分:0)

正如其他人所说的那样,作为一般规则,你应该在实际处理它时捕获异常,否则就抛出异常。

例如,如果您正在编写从保存文件读取有关连接播放器的信息的代码,并且您的一个I / O方法抛出IOException,那么您可能希望抛出该异常以及代码调用load方法会想要捕获该异常并相应地处理它(比如断开播放器,或者向客户端发送响应等)。您不希望在load方法中处理异常的原因是因为在该方法中,您无法有意义地处理异常,因此您将异常委托给调用者,希望他们能够处理它。

答案 7 :(得分:0)

在两种情况下,您应该捕获异常。

1。尽可能降低

这是您与第三方代码集成的级别,例如ORM工具或任何执行IO操作的库(通过HTTP访问资源,读取文件,保存到数据库,命名)。也就是说,您保留应用程序的本机代码与其他组件进行交互的级别。

在此级别上,可能会发生无法控制的意外问题,例如连接失败和锁定的文件。

您可能想通过捕获TimeoutException 来处理数据库连接失败,以便几秒钟后重试。访问文件时的例外情况也是如此,该文件当前可能已被进程锁定,但在下一个瞬间可用。

在这种情况下的准则是:

  • 仅处理特定的例外情况,例如SqlTimeoutExceptionIOException。永远不要处理通用异常(类型为Exception
  • 仅在您有有意义的事情要做时处理它,例如重试,触发补偿操作或向异常添加更多数据(例如,上下文变量),然后将其重新抛出
  • 请勿在此处执行日志记录
  • 让所有其他异常冒出来,因为它们将由第二种情况处理

2。尽可能达到最高水平

这是在直接将异常抛出给用户之前可以处理异常的最后一个地方。

您在这里的目标是记录错误并将详细信息转发给程序员,以便他们识别并更正错误。添加尽可能多的信息,进行记录,然后向用户显示道歉消息,因为他们对此无能为力,尤其是在软件中存在错误时。

第二种情况下的准则是:

  • 处理通用Exception类
  • 从当前执行上下文中添加更多信息
  • 记录错误并通知程序员
  • 向用户道歉
  • 尽快解决

这些准则背后的原因

首先,异常代表不可逆错误。它们代表系统中的错误,程序员的错误或应用程序无法控制的情况。

在这些情况下,用户通常无能为力。因此,您唯一可以做的就是记录错误,采取必要的补偿措施,并向用户道歉。如果程序员犯了一个错误,那么最好让他们知道并修复它,以寻求一个更稳定的版本。

第二,try catch个块可以掩盖应用程序的执行流程,具体取决于它们的使用方式。 try catch块具有与label及其goto伴随对象相似的功能,这导致应用程序执行流从一个点跳到另一个点。


何时引发异常

在开发库的背景下更易于解释。 您应该在遇到错误时抛出错误,您无能为力,除了让API的使用者知道并决定外。

假设您是某些数据访问库的开发人员。当您遇到网络错误时,除了引发异常外,您无能为力。从数据访问库的角度来看,这是一个不可逆的错误。

在开发网站时,情况有所不同。为了重试,您可能会捕获到此类异常,但是如果您从外层接收到无效的参数(因为它们应该在此处进行了验证),则可能会抛出异常。

在Presentation层中,您又希望用户提供无效的参数,这又有所不同。在这种情况下,您只会显示一条友好的消息,而不是抛出异常。


https://roaddd.com/the-only-two-cases-when-you-should-handle-exceptions/

中所述