3

我已经处理了我会抛出/重新抛出异常的情况,因为我知道围绕它的代码会捕获特定的异常。但是有没有任何时候你想抛出一个异常,知道它不会被捕获?

或者至少,不捕获异常?

除非处理正确,否则异常会立即停止应用程序?所以我想我是在问你是否想故意让你的应用程序死掉?

4

14 回答 14

15

如果您的应用程序主要由其他客户端使用并且不是独立的,那么如果出现您不知道如何(或不想)处理的情况,抛出异常通常是有意义的,并且没有明智的让你从中恢复的方法。客户应该能够决定他们希望如何处理您可能抛出的任何异常。

另一方面,如果您的应用程序端点,则抛出异常本质上会成为一种通知机制,以提醒人们发生了严重错误。在这种情况下,您需要考虑以下几点:

  • 应用程序的持续运行有多重要?这个错误真的无法恢复吗?抛出异常并终止你的程序并不是你想在航天飞机上做的事情。

  • 您是否使用异常作为真实日志记录的代理?几乎没有理由这样做。考虑一个真正的日志记录机制。捕获异常并让记录器弄清楚发生了什么。

  • 你想通过自己抛出异常来传达什么?问问自己抛出一个新异常的价值是什么,并仔细考虑是否没有更好的方法来做你想做的事。

  • 不捕获异常可能会使资源处于不良状态。如果你不优雅地退出,事情通常不会为你清理干净。如果你需要这样做,请确保你明白你在做什么——如果你不想抓住它,至少考虑一个try-finally块,这样你就可以做一些整理。

于 2009-03-20T15:42:09.120 回答
8

不久前我遇到了一个非常好的规则:

当一个方法不能按照它的名字做的事情时抛出一个异常。

这个想法是异常表明出现了问题。当你实现一个方法时,知道它是否会被正确使用不是你的责任。使用您的方法的代码是否捕获异常不是您的责任,而是使用您的方法的人的责任。

另一个要遵循的规则是:

除非你知道你想用它做什么,否则不要捕获异常。

显然,你应该在 try...finally 块中包含清理代码,但你不应该仅仅为了捕捉它而捕捉异常。而且你永远不应该默默地吞下异常。虽然有时您可能想要捕获所有异常(例如,通过在 C# 中执行 catch (Exception ex)),但这些异常并不常见,并且通常具有非常具体的技术原因。例如,当您在 .NET 2.0 或更高版本中使用线程时,如果异常从您的线程中逃脱,它将导致整个应用程序域卸载。但是,在这些情况下,您至少应该将异常详细信息记录为错误并在注释中提供解释。

于 2009-03-20T16:15:01.087 回答
5

当然。例如,如果您尝试将一些字节加载到 Java 中的字符串中:

try {
  String myString = new String(byteArray, "UTF-8");
} catch (UnsupportedEncodingException e) {
  // Platform doesn't support UTF-8?  What is this, 1991?
  throw new RuntimeExceptione(e);
}

在这种情况下,没有优雅的降级,平台根本无法支持所需的操作。你可以在初始化时检查这个条件,但是 String 的构造函数仍然抛出这个异常,你必须处理它。要么,要么使用 Charset.forName() :)

于 2009-03-20T15:55:48.247 回答
5

通常,当然在您的应用程序的早期迭代中,不要捕获异常。通常情况下,从异常中恢复需要某种业务规则,而且通常情况下,这些业务规则不是为您定义的。如果您“处理”异常而不是让应用程序死掉,那么您很可能会为您的客户发明业务规则。不好。

仅仅为了捕捉它而捕捉每个异常的一般模式让我感到头疼,我数不清。通常有人会在整个应用程序中放置某种通用异常处理代码,这不可避免地会隐藏错误或创建一些不需要的行为。(顺便说一句,抓住然后不重新抛出更糟糕。)

因此,我建议您改为问:“我应该什么时候捕获异常?”

于 2009-03-20T16:09:07.370 回答
3

事情是这样的……它是关于“层”、“封装”或“低耦合”的。在代码库的某个地方,您正在编写一种方法来做某事。说它是一种公共方法。因此,它不应该对调用者进行太多或任何假设......相反,它应该只做它应该做的工作,而不管是谁在调用它以及调用者在什么上下文中。

如果由于某种原因,它不能完成它的工作,那么它需要告诉调用者“对不起,我不能这样做,这就是原因”。异常是一种很好的机制,可以让它告诉调用者(不是唯一的机制,而是我在大多数情况下见过的最好的机制)。

所以,当你抛出异常时,你不知道它是否会被捕获......因为你公开了一个公共方法,你不知道谁可能会选择调用它以及为什么。

捕获异常是“上下文”的工作。例如,假设您正在编写一个包含可能引发异常的公共方法的库。然后,假设您正在使用 Windows 窗体应用程序中的该库。Windows 窗体应用程序可能会捕获异常并向用户显示消息框。

但稍后,您可能会使用来自 Windows 服务的相同库。服务更有可能捕获异常,记录它,向原始调用者返回错误,但继续运行以便它可以处理进一步的请求。

所以例外就像调用者和提供者之间的合同协议。提供者说:“我要么做这项工作,要么告诉你为什么我不能。你从那里做什么是你自己的事。” 来电者说:“好吧,如果你不能完成这项工作,请告诉我原因,我会决定在这种情况下该怎么做。”

于 2009-03-20T16:26:26.613 回答
1

但是有没有任何时候你想抛出一个异常,知道它不会被捕获?

我想说的是,如果您手动抛出异常,大多数时候您不知道它是否会被捕获。如果您知道它会被捕获,您可以自己处理它,而不是首先抛出异常。

公平地说,我认为这部分取决于您正在执行的编程类型,有时同一个程序员最终会同时构建库和使用该库的代码。

你永远不会捕捉到异常吗?

如果您没想到/不知道可能会引发异常。但抛开这一点并假设您知道异常,有时您在一层就知道它,但知道下一层是处理它的更合适的地方。

于 2009-03-20T15:37:00.490 回答
1

这取决于应用程序的类型。即使异常已经冒泡到执行上下文,Web 应用程序也可以继续运行。

如果您在无法处理的级别捕获异常,则“抛出/重新抛出”异常是常见的做法。但是,您几乎总是会为问题添加上下文,至少在更高级别添加一些日志记录以说明它已被捕获并重新抛出。

例如

A调用B调用C(抛出异常)

B 接球/再掷

一个抓住。

在这种情况下,您可能希望 B 添加一些日志记录,以便您可以区分 B 生成和引发错误,以及 C 生成和引发错误。这将使您有更大的能力在以后调试和修复问题。

一般来说,你几乎永远不会想要一个异常来杀死你的程序。最佳实践是捕获异常并优雅地退出。这使您可以保存任何当前打开的信息并释放正在使用的资源,以免它们被损坏。如果您打算退出,您可以创建自己的“核心转储”信息报告,其中包括您在捕获致命异常时所做的事情。

如果您让异常杀死您的进程,您将失去获得自定义崩溃信息的机会,并且您还将跳过向用户提供友好错误消息然后退出的部分。

因此,我建议始终捕获异常,并且永远不要自愿让它们在您的程序中运行异常。

编辑

如果你正在编写一个库,你必须提前选择你的函数是抛出异常还是异常安全。在这些情况下,有时您会抛出异常并且不知道调用方是否会捕获它。但在这种情况下,捕捉它不是你的责任,只要 api 声明该函数可以抛出异常。(我正在寻找一个意思是“可能抛出异常”的词......有人知道它是什么吗?它会让我整天烦恼。)

于 2009-03-20T15:39:34.180 回答
1

首先,在某些情况下最好不要捕获异常。

有时,异常有时会告诉您您的程序处于未知状态。考虑到异常类型,有许多异常本质上是正确的。ANullReferenceException基本上告诉你“有一个错误”。通过捕获这样的异常,您可能会隐藏错误,这在短期内听起来不错,但从长远来看,您会更乐意修复它。该产品可能不会崩溃,但它肯定不会有预期的行为。

但对于我们为自己发明的异常类型也是如此。有时,抛出异常 A 的事实应该是“不可能的”——但它已经发生了,所以有一个错误。

此外,当您捕获异常时会发生一些非常重要的事情:finallytry 块内的整个调用堆栈的块(以及它调用的任何东西)都将被执行。那些 finally 块有什么作用?嗯,什么都行。如果程序处于未知状态,我真的是什么意思。他们可以从磁盘中删除有价值的客户数据。他们可以抛出更多异常。它们可能会破坏内存中的数据,使错误无法诊断。

因此,当异常指示未知状态时,您不想再运行任何代码,所以无论您做什么,都不要捕获异常。让它飞过去,您的程序将无害地终止,Windows 错误报告将能够捕获程序的状态,就像最初检测到问题时一样。如果你捕捉到异常,你将导致更多的代码执行,这将进一步搞砸程序的状态。

其次,您是否应该在知道不会被捕获的情况下抛出异常?我认为这个问题误解了可重用方法的性质。方法的整体思想是它遵循一个“契约”:它接受某些参数并返回某个值,此外它还在某些条件下抛出某些异常。这就是合同——这取决于调用者如何处理它。对于某些调用者,异常 A 可能表示可恢复的情况。对于其他调用者,它可能表明存在错误。而且从我上面所说的,应该清楚的是,如果一个异常表明一个错误,它一定不能被捕获

如果您想知道这对Microsoft 企业库的异常处理块意味着什么:是的,它非常糟糕。他们告诉您catch (Exception x),然后根据您的政策决定是否重新投掷;太晚了 -finally块已经执行到那个时候。不要那样做。

于 2009-03-20T15:57:34.153 回答
0

您可能不希望在最终用户可以看到的任何地方出现未捕获的异常,但让您的 API 的客户端(其他程序员)决定如何处理异常通常是可以接受的。

例如,假设您正在设计一个 Java 类库。您公开了一个接受字符串的公共方法。在您的应用程序中,空输入值会导致错误。与其自己处理错误,不如检查空值,然后抛出 IllegalArgumentException。

当然,您必须记录您的方法在这种情况下会引发此异常。这种行为成为你的方法契约的一部分。

于 2009-03-20T15:41:33.273 回答
0

这取决于您所说的“被抓住”是什么意思。某处某处最终捕获了异常,无论是底层操作系统还是其他东西。

我们有一个工作流系统,可以执行由单个作业组成的作业计划。每个作业运行一个代码单元。对于某些异常,我们不想在代码中处理它们,而是将其抛出堆栈,以便外部工作流系统捕获它(这完全发生在抛出者进程之外)。

于 2009-03-20T15:43:51.207 回答
0

如果您正在编写整个应用程序,那么您的理由是您自己的。我可以想到一些您可能想要抛出异常并让应用程序死掉的情况,但其中大多数都不是很好的理由。

最好的原因通常是在调试时。我经常在调试时禁用异常,以让我更好地知道哪里出了问题。如果您在带有调试器的机器上运行它,您也可以在调试器中打开抛出的异常中断。

另一个可能的原因是在抛出异常后继续没有意义或可能导致不可恢复的数据损坏或更糟(想想带有激光束的机器人,但你应该确定你的应用程序处理这些情况 IMO,崩溃程序只是懒惰的方式)。

如果您正在编写自己不会使用的 API 代码或框架代码,那么您不知道是否有人会捕获您的异常。

于 2009-03-20T15:48:44.050 回答
0

是的,这是我唯一的机会来打击使用服务/对象的开发人员,告诉他们“你做错了!!!!”。

那并摆脱您不想允许或看似“不可能”的可能性。捕获所有异常并继续运行的应用程序只是一个被混乱包围的围墙花园。

于 2009-03-20T15:57:03.767 回答
0

如果我需要一个中等大小的系统,它以某种我认为一致的方式处理数据。

在某个地方,我检测到应用程序的状态变得不一致。

系统(尚)不知道如何修复不一致并优雅地恢复

然后,是的,我会抛出一个尽可能详细的异常并导致应用程序尽快终止,以避免对数据造成任何进一步的损害。如果它可以恢复,重要的是不要因为试图掩盖混乱而加剧问题。

稍后,一旦更好地理解了导致不一致的事件链,更高的设施可以捕获该异常,修复状态,并以最小的中断继续。

于 2009-03-20T17:31:09.903 回答
0

如果出现应用程序代码不应该出现的情况,库通常会基于防御性编程检查抛出异常。应用程序代码通常会编写成大多数无效条件永远不会出现,因此永远不会抛出异常,因此捕获它们是没有意义的。

取决于语言(我主要考虑 C++ 而不是 C#,并不清楚它们的区别是什么)实际抛出的未捕获异常的效果可能与异常前几天所做的相同被发明了。例如,在 C 库中防御性编程的一个常见策略是立即终止程序并显示错误消息。

不同之处在于,如果异常抛出确实是可能的(希望这将通过单元测试发现),通常相对容易添加一个异常处理程序,该处理程序可以以更具建设性的方式从问题中恢复。您不必重写库,或在应用程序代码中添加复杂的检查,以确保在抛出异常调用之前不会出现条件。

我有很多从未被捕获的异常抛出。它们都是出于防御目的,虽然不被捕获对于确实发生的异常是不好的,但这只会在开发和测试期间发生,因为到目前为止我未能在应用程序代码中考虑错误情况。并且当它发生时,修复的尴尬是不寻常的——不需要大规模重构,不需要通过错误条件检查使应用程序代码变得非常复杂,只需一个具有相对简单恢复的 catch 子句或“对不起,戴夫,我恐怕做不到。” 没有失败整个应用程序。

于 2011-01-02T23:35:00.680 回答