67

我已经用 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 中捕获它?

4

8 回答 8

90

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

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

所以你有一堂课,说:

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);
        }
    }
}

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

于 2013-09-08T00:12:54.717 回答
18

有两种情况应该捕获异常。

1. 尽可能低的水平

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

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

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

这种情况下的指导方针是:

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

2. 尽可能高的水平

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

您的目标是记录错误并将详细信息转发给程序员,以便他们识别和纠正错误。添加尽可能多的信息,记录下来,然后向用户显示道歉信息,因为他们可能对此无能为力,特别是如果它是软件中的错误。

第二种情况的指导方针是:

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

这些指南背后的原因

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

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

其次,try catch块可以根据使用方式屏蔽应用程序执行流程。try catch块具有与 a 及其伙伴类似的功能,labelgoto会导致应用程序执行流从一个点跳转到另一个点。


何时抛出异常

在开发图书馆的背景下更容易解释。当你遇到错误时你应该抛出,除了让你的 API 的使用者知道它并让他们决定之外,你无能为力。

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

这在您开发网站时有所不同。您可能会捕获此类异常以重试,但如果您从外层收到无效参数,则希望抛出异常,因为它们应该已在那里验证。

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


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

于 2019-12-28T13:50:13.453 回答
7

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

功能是一个工作单元,故障应该被视为错误或基于它们对功能的影响。在函数f中,当且仅当它阻止f满足其被调用者的任何先决条件、实现f自己的任何后置条件或重新建立f共同维护的任何不变量时,失败才是错误。

存在三种不同类型的错误:

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

任何其他情况都不是错误,不应报告为错误。

只要函数检测到它无法自行处理并阻止它以任何形式的正常或预期操作继续进行,就报告错误。

在有足够知识来处理错误、翻译错误或强制执行错误策略中定义的边界的地方处理错误,例如在主线或线程主线上。

资料来源:C++ 编码标准:101 条规则、指南和最佳实践

于 2014-07-10T14:12:54.367 回答
6

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

你想怎么处理?也许通过设置一个对话框说“对不起,无法连接到服务器/数据库”或其他什么。是创建此 SERVER/DB 信息(例如,通过读取设置文件或要求用户输入)并尝试连接的方法 A、B 或 C?这可能是应该处理异常的方法。或者至少远离应该处理它的方法。

它确实因您的应用程序而异,因此这只能是非常一般的建议。我的大部分经验是使用 Swing / 桌面应用程序,您通常可以根据哪些类正在执行程序逻辑(例如“控制器”内容)以及谁在放置对话框(例如“视图”内容)来获得感觉。通常“控制器”应该捕获异常并尝试做一些事情。

在 Web 应用程序中,这可能会有所不同。

一些非常简单的代码,大多数类都不存在,我不确定数据库的 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
  }

}
于 2013-09-08T00:11:02.137 回答
6

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

动机

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

方法

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

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

使用这种方法,您的 GUI 或业务层的方法签名不会因为必须为与数据库相关的异常声明“抛出”而变得混乱。

在现实生活中如何运作的一个例子:

假设我的代码的工作是更新许多保险单的自动化过程。该架构支持 GUI 手动触发一个策略的更新。还可以说,对于这些政策之一,评级区域的邮政编码在数据库中已损坏。

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

日志消息:由于错误而标记策略 1234 以进行手动干预:

来自堆栈跟踪:错误更新策略 1234。回滚事务...... 此捕获还将涵盖错误,例如保存错误或生成信件。

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

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

来自堆栈跟踪:原因:JPA:字段“邮政编码”中的意外空值

于 2013-09-08T01:09:02.200 回答
4

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

  • 如果您有连接到资源的方法(例如打开文件/网络)
  • 如果层次结构中较高的类需要有关错误的信息,则抛出
于 2013-09-08T00:36:21.287 回答
1

当你想通知调用者一些失败的方法时,你通常会抛出一个异常。

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

于 2013-09-08T00:13:43.297 回答
1

正如其他人所说,作为一般规则,您应该在可以实际处理时捕获异常,否则,只需抛出它。

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

于 2013-09-08T01:01:39.317 回答