你是否会不捕获异常或抛出不会被捕获的异常?

3

我曾经遇到过这样的情况,我会抛出/重新抛出一个异常,因为我知道周围的代码会捕获特定的异常。但是有没有任何时候你想要抛出一个异常,明知道它不会被捕获?

或者至少,不捕获异常?

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


请参见https://dev59.com/m3RB5IYBdhLWcg3wl4Oc - Brian
14个回答

15

如果您的应用程序主要由其他客户端使用,而不是独立的,则通常情况下,如果出现您不知道如何处理或不想处理的条件,并且没有合理的方式可以从中恢复,那么抛出异常是有意义的。客户端应该能够决定如何处理您可能抛出的任何异常。

另一方面,如果您的应用程序是终点,则抛出异常实际上成为一种通知机制,以提醒人们发生了非常严重的问题。在这种情况下,您需要考虑一些事情:

  • 应用程序的持续运行有多重要?这个错误是否真的无法恢复? 在太空船上抛出异常并终止程序不是您想做的事情。

  • 您是否将异常用作实际日志记录的代理? 几乎没有理由这样做;请考虑使用真正的日志记录机制。捕获异常并让日志记录器找出发生了什么。

  • 通过自己抛出异常,您要传达什么? 问问自己抛出一个新异常的价值,认真考虑是否有更好的方式来实现您想要的功能。

  • 不捕获异常可能会使资源处于糟糕的状态。 如果您没有优雅地退出,通常情况下,事情不会为您清理。如果您需要这样做,请确保了解您正在做什么 - 如果您不打算捕获它,请考虑使用try-finally块以便可以做一些清理工作。


5
你有一个令人沮丧的习惯,就是说出了我想说的话,并且比我说得更好。 - DevinB

8

我曾经遇到过一个非常好的规则:

当一个方法不能完成它名字所表示的功能时,抛出异常。

这个想法是异常表示了某些问题已经发生。当你实现一个方法时,你不需要关心它是否被正确使用。代码是否捕获这个异常并不是你的责任,而是使用你方法的人的责任。

还有一个需要遵循的规则是:

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

显然,在try…finally块中应该包括清理代码,但你永远不应该只是为了捕获异常而捕获异常。你也不应该悄悄地吞噬异常。虽然有时候你可能需要捕获所有异常(例如在C#中执行catch(Exception ex)),但这种情况相对较少,通常具有非常特定的技术原因。例如,在.NET 2.0或更高版本中使用线程时,如果异常从线程中逃逸,它将导致整个应用程序域卸载。但在这些情况下,至少你应该将异常详细信息记录为错误,并在注释中提供解释。


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() :)

5
通常,在应用程序的早期版本中,不要捕获异常。从异常恢复通常需要某种业务规则,而这些业务规则往往没有为您定义。如果您“处理”异常而不是让应用程序崩溃,则很可能会为客户发明业务规则。这是不好的。
仅仅为了捕获异常而采用的一般模式给我带来了无数的头痛。通常情况下,有人会在整个应用程序中放置某种通用的异常处理代码,这不可避免地会隐藏一个错误或创建一些不需要的行为。(顺便说一句,捕获异常然后不重新抛出更糟糕。)
因此,我建议您改为询问:“何时应该捕获异常?”

3
这里有个问题...它与“层”,“封装”或“低耦合”有关。在代码库中的某个地方,您正在编写一个方法来执行某些操作。假设这是一个公共方法。因此,它不应该对调用者做出太多或任何假设...相反,它应该只是完成它应该完成的工作,而不管谁在调用它以及调用者所处的上下文是什么。
如果由于某种原因它无法完成其工作,那么它需要告诉调用者“抱歉,我不能这样做,原因是...”。异常是让它告诉调用者的绝佳机制(不是唯一的机制,但是在大多数情况下是最好的机制)。
因此,当您引发异常时,您不知道它是否会被捕获...因为您正在公开一个公共方法,而您不知道谁可能选择调用它以及为什么。
捕获异常是“上下文”的工作。例如,假设您正在编写具有可能引发异常的公共方法的库。然后,假设您正在从Windows Forms应用程序中使用该库。Windows Forms应用程序可能会捕获异常并向用户显示消息框。
但是稍后,您可能会从Windows服务中使用同一库。服务更有可能捕获异常,记录它,向原始调用者返回错误,但仍在运行以便可以处理进一步的请求。
因此,异常就像调用方和提供方之间的合同协议。提供方说:“我要么完成工作,要么告诉您为什么不能完成。从那里开始,您做什么是您自己的事情。”而呼叫者则说:“好的,如果您无法完成工作,请告诉我原因,我会决定在那种情况下该怎么做。”

1
但是有没有任何时候你会想要抛出一个异常,明知道它不会被捕获呢?
我认为,如果你手动抛出一个异常,大部分情况下你并不知道它是否会被捕获。如果你知道它会被捕获,你可以直接处理它,而不是首先抛出异常。
公平地说,这可能在一定程度上取决于你所做的编程类型,有时同一个程序员会同时构建库和使用该库的代码。
你会不会永远不捕获异常呢?
如果你没有预料到/没有意识到可能会抛出异常。但是撇开这个问题,假设你知道异常的存在,有时你会在一个层次上知道异常,但是知道更合适的地方是在上一层处理它。

1

这取决于应用程序的类型。Web应用程序即使在异常已经到达执行上下文后仍然可以继续运行。

通常的做法是,如果你在一个无法处理异常的级别捕获了异常,就会“抛出/重新抛出”异常。但是,你几乎总是会为问题添加一些上下文,至少在更高的级别上添加一些日志,以表明它被捕获并重新抛出。

例如

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

B捕获/重新抛出

A捕获。

在这种情况下,你希望B添加一些日志,以便你可以区分B生成和抛出错误以及C生成和抛出错误。这将允许你更好地调试和修复问题。

通常情况下,你几乎永远不想让异常终止你的程序。最佳实践是捕获异常并优雅地退出。这样可以保存任何当前打开的信息并释放正在使用的资源,以防止它们变得损坏。如果你打算退出,可以创建自己的“核心转储”信息报告,其中包括在捕获致命异常时正在进行的事情。

如果你让异常终止你的进程,那么你就失去了获取定制化崩溃信息的机会,也跳过了向用户提供友好错误消息并退出的部分。

因此,我建议始终捕获异常,永远不要让它们在程序中肆虐。

编辑

如果你正在编写一个库,你必须事先选择你的函数是抛出异常还是具有异常安全性。在这些情况下,有时你会抛出异常,但不知道调用方是否会捕获它。但在这种情况下,只要API声明该函数可能会抛出异常,捕获它就不是你的责任。 (我正在寻找一个意思是“可能会抛出异常”的词...有人知道是什么吗?这将困扰我一整天。)


“捕获并重新抛出”与“不捕获”不同-它会触发finally块的执行。 - Daniel Earwicker

1
首先,有些情况下最好不要捕获异常。
有时,异常可以告诉你程序处于未知状态。对于某些异常类型来说,这几乎是内在的真相。例如,NullReferenceException基本上告诉你“存在错误”。通过捕获这样的异常,你可能会隐藏错误,短期内看起来很好,但长期来看,你会更愿意修复它。产品可能不会崩溃,但肯定不会有预期的行为。
但对于我们自己发明的异常类型也是如此。有时候,抛出异常A应该是“不可能”的-但它确实发生了,所以存在一个错误。
另外,当你捕获异常时,整个调用堆栈中try块(以及它调用的任何东西)的finally块都将被执行。那些finally块做什么呢?呃,任何事情。如果程序处于未知状态,我确实是指任何事情。它们可能从磁盘中删除有价值的客户数据。他们可能抛出更多的异常。他们可能会损坏内存中的数据,使错误无法诊断。

因此,当异常表示未知状态时,您不希望运行任何其他代码,所以无论如何,不要捕获异常。让它飞过去,您的程序将安全终止,Windows错误报告将能够捕获在最初检测到问题时程序的状态。如果捕获异常,将导致更多代码执行,进一步破坏程序状态。

其次,您是否应该抛出异常,知道它不会被捕获?我认为这个问题误解了可重用方法的本质。整个方法的想法是它有一个遵循的“合同”:它接受某些参数并返回某个值,还在某些条件下抛出某些异常。这就是合同-由调用者决定他们要做什么。对于某些调用者,异常A可能表示可恢复的条件。对于其他调用者,它可能表示错误。从上面所说的内容可以清楚地看出,如果异常表示错误,则不能捕获

如果你想知道这对于微软企业库的异常处理块意味着什么:是的,它相当有问题。他们告诉你去catch (Exception x),然后根据你的策略决定是否重新抛出;太晚了 - 在那个时候finally块已经执行了。不要这样做。


-1:嗯,这不是finally块的作用吗?通常它们的作用是参与确保您的应用程序处于一致状态(回滚事务等)。 - Joe
如果出现错误,您如何知道 finally 块将能够正确地撤消状态更改?如果它们破坏了您诊断问题所需的信息,该怎么办? - Daniel Earwicker

0
一个库通常会基于防御性编程检查抛出异常,如果应用程序代码引发了不应该出现的条件。应用程序代码通常会被编写成大多数无效条件永远不会出现,因此异常将永远不会被抛出,所以没有捕获它们的意义。
根据语言(我主要考虑的是C++而不是C#,并不清楚差异是什么),未捕获的异常实际上被抛出的影响可能与在异常被发明之前的日子里所做的相同。例如,在C库中进行防御性编程的常见策略是立即终止带有错误消息的程序。
区别在于,如果异常抛出确实可能发生(希望通过单元测试发现),则通常很容易添加异常处理程序,以更具建设性的方式从问题中恢复。您不必重写库,也不必在调用引发异常的应用程序代码中添加复杂的检查,以确保在异常抛出调用之前不会出现该条件。

我有很多未被捕获的异常抛出。它们都是为了防御目的,虽然未被捕获的异常对于确实发生的异常来说是不好的,但这只会在开发和测试期间发生,因为在应用程序代码中我还没有考虑到错误条件。当它发生时,修复通常不会很麻烦 - 不需要大规模重构,也不需要在应用程序代码中添加大量的错误条件检查,只需要一个相对简单的恢复或“对不起,戴夫,我恐怕做不到。”而不会使整个应用程序崩溃。


0

在任何用户可以看到的地方都不应该出现未捕获的异常,但通常可以让您 API 的客户端(其他程序员)决定如何处理异常。

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接