我已经用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中捕获它吗?
答案 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)
您应该以尽可能低的级别处理异常。如果方法无法正确处理异常,则应抛出它。
答案 5 :(得分:1)
如果要通知调用方某些失败的方法,通常会抛出异常。
例如无效的用户输入,数据库问题,网络中断,缺少文件
答案 6 :(得分:0)
正如其他人所说的那样,作为一般规则,你应该在实际处理它时捕获异常,否则就抛出异常。
例如,如果您正在编写从保存文件读取有关连接播放器的信息的代码,并且您的一个I / O方法抛出IOException
,那么您可能希望抛出该异常以及代码调用load
方法会想要捕获该异常并相应地处理它(比如断开播放器,或者向客户端发送响应等)。您不希望在load
方法中处理异常的原因是因为在该方法中,您无法有意义地处理异常,因此您将异常委托给调用者,希望他们能够处理它。
答案 7 :(得分:0)
在两种情况下,您应该捕获异常。
这是您与第三方代码集成的级别,例如ORM工具或任何执行IO操作的库(通过HTTP访问资源,读取文件,保存到数据库,命名)。也就是说,您保留应用程序的本机代码与其他组件进行交互的级别。
在此级别上,可能会发生无法控制的意外问题,例如连接失败和锁定的文件。
您可能想通过捕获TimeoutException
来处理数据库连接失败,以便几秒钟后重试。访问文件时的例外情况也是如此,该文件当前可能已被进程锁定,但在下一个瞬间可用。
在这种情况下的准则是:
SqlTimeoutException
或IOException
。永远不要处理通用异常(类型为Exception
)这是在直接将异常抛出给用户之前可以处理异常的最后一个地方。
您在这里的目标是记录错误并将详细信息转发给程序员,以便他们识别并更正错误。添加尽可能多的信息,进行记录,然后向用户显示道歉消息,因为他们对此无能为力,尤其是在软件中存在错误时。
第二种情况下的准则是:
首先,异常代表不可逆错误。它们代表系统中的错误,程序员的错误或应用程序无法控制的情况。
在这些情况下,用户通常无能为力。因此,您唯一可以做的就是记录错误,采取必要的补偿措施,并向用户道歉。如果程序员犯了一个错误,那么最好让他们知道并修复它,以寻求一个更稳定的版本。
第二,try catch
个块可以掩盖应用程序的执行流程,具体取决于它们的使用方式。 try catch
块具有与label
及其goto
伴随对象相似的功能,这导致应用程序执行流从一个点跳到另一个点。
在开发库的背景下更易于解释。 您应该在遇到错误时抛出错误,您无能为力,除了让API的使用者知道并决定外。
假设您是某些数据访问库的开发人员。当您遇到网络错误时,除了引发异常外,您无能为力。从数据访问库的角度来看,这是一个不可逆的错误。
在开发网站时,情况有所不同。为了重试,您可能会捕获到此类异常,但是如果您从外层接收到无效的参数(因为它们应该在此处进行了验证),则可能会抛出异常。
在Presentation层中,您又希望用户提供无效的参数,这又有所不同。在这种情况下,您只会显示一条友好的消息,而不是抛出异常。
如https://roaddd.com/the-only-two-cases-when-you-should-handle-exceptions/
中所述