为什么“记录和抛出”被认为是一种反模式?

116

这个问题是由这篇文章所引起的讨论,我没有得到任何好的答案。

如果你不能以其他方式处理异常,为什么记录异常并重新抛出它(当然要保留原始的堆栈跟踪)会是一个不好的想法?


尝试在 https://softwareengineering.stackexchange.com/ 上搜索答案。 - chharvey
@chharvey: 也许这个可以?Try/Catch/Log/Rethrow - 是否为反模式? - sleske
4个回答

167

我认为答案主要是因为,如果你无法处理它,为什么要捕获它呢?如果有人能够处理它(或者别无选择只能处理它),为什么不让他们记录下来,如果他们觉得值得记录的话?

如果你捕获异常并记录并重新抛出它,那么上游代码就无法知道你已经记录了异常,因此同样的异常可能会被记录两次。或者更糟的是,如果所有上游代码都遵循这个相同的模式,那么异常可能会被记录任意次数,每个决定捕获、记录和重新抛出它的代码层级都会记录一次。

还有一些人可能会认为,由于抛出和捕获异常是相对昂贵的操作,所有这些捕获和重新抛出操作并没有帮助您的运行时性能。它也没有帮助您的代码变得简洁或易于维护。


请查看Jeff的回答的评论获取更多的阐述。 - Manu
14
记录并重新抛出异常的原因是为了缩小问题范围并记录特定的信息。 - Mike Argyriou
26
可能在当前堆栈的某个点有一些有用的调试信息,在堆栈跟踪的另一个点可能无法获得。 - rtconner
1
有时候捕获并重新抛出异常是很有帮助的,如果新的异常提供了更多信息或更具体的信息。 - borjab
2
扩展@rtconner的评论:在异步代码中,捕获器可能没有抛出者可用的上下文。 - Nir Alfasi
3
通常情况下,这不就是为什么你可以在重新抛出异常之前将信息附加到异常或将其包装成另一个异常的原因吗? - S. Mense

83

如果捕获并重新抛出异常的实体有理由认为该异常包含的信息不会在调用栈中进一步记录,除非以最理想的方式记录,那么log-and-throw是一个很好的模式。 以下是可能发生这种情况的几个原因:

  1. 应用程序层边界可能会捕获和重新抛出异常,并可能包含特权信息。 数据库层不允许异常到达外部应用程序层(可能再次将其公开给用户),例如“尝试向字段'users'添加重复键'fnord'” 的异常。 但对于数据库的内部部分来说,抛出这样的异常可能很有用,而应用程序接口则捕获它、进行安全记录并重新抛出一个不太详细的异常。
  2. 外部层可能希望处理某些异常而不记录日志,但内部层可能知道外部层不知道的东西,这表明记录日志可能很有用。 例如,中间应用程序层可以编写成尝试连接一个服务器,如果失败则尝试另一个服务器。 当服务器处于维护状态时,通过“连接失败”消息淹没应用程序的日志可能并不有用,尤其是从应用程序的角度来看,一切都很正常。 将有关连接失败的信息转发到与服务器关联的日志资源可能很有用,这样可以过滤日志以生成服务器上下线报告,而不是记录每个连接尝试。
如果外部层会导致异常信息丢失,则内部层记录和重新抛出异常可能有助于确保其得到保留。

4
#1确实是一个合理的通用情况,但是OP明确要求“重新抛出它(当然要保留原始堆栈跟踪)”,所以#1不是正确的答案。 - Geoffrey Zheng
@GeoffreyZheng:这将取决于安全地记录原始堆栈跟踪是否算作“保留”。 - supercat
5
关于#1:暴露异常内容(例如在UI上显示'e.getMessage()' / stacktrace,以及将其作为REST响应发送)应被视为漏洞本身,因为运行时异常可能包含任何类型的敏感信息。关于#2:您可以重新抛出异常并添加所有您想要客户端知道的信息(+根本原因),无需记录任何内容。 - Nikita Bosik
1
@GrumpyRodriguez:你喜欢最后一段吗? - supercat
1
@supercat,是的,这有助于澄清您的观点。感谢您在10多年后为此答案付出额外的努力! - GrumpyRodriguez
显示剩余3条评论

30

我想最简单的原因是通常有一个顶级处理程序可以为您执行此操作,因此没有必要在代码中污染此异常处理。

交叉关注点的论点基本上是处理与您无关的错误是浪费时间。更好的做法是让错误向上调用栈冒泡,直到找到适当的处理程序为止。

在我看来,唯一应该捕获异常的时间是当您可以使用结果时。仅为记录而捕获是没有用的,因为您可以将其集中在更高层次。


2
我认同你应该有一个顶层的处理器来替你完成这个任务。但在我看来,那个顶层处理器应该是“记录并抛出”异常。因此,争论点在于在哪个时刻执行“记录并抛出”,而不是是否要执行?!? - Manu
@Manu,我想重点是如果它是单个处理程序(集中式),那就没问题。如果你在复制代码,那就不好了。我认为这就是全部! - Jeff Foster
9
在这种情况下,最高级别的处理程序抛出什么?如果有东西可以抛出,那么它似乎并不是最高级别的处理程序。而且你绝对不想将异常重新抛到运行时环境本身。这将导致大多数应用程序崩溃。 - aroth
1
@aroth:我明白了,你的意思是如果你是顶级处理程序,则是“记录并处理”,否则是“不记录并抛出(或以其他方式处理)”。感谢你指出这一点。 - Manu

15

IMO记录和抛出异常明显违反了最少惊讶原则。

如果异常在调用堆栈中的更高层面得到恰当处理,那么它可能根本不值得记录错误日志。然后再找到错误日志条目会变得令人困惑。


非错误日志怎么办? - Su Zhang
8
@SuZhang,我不太明白你的问题。如果没有错误,就没有需要抛出的内容。当然,你可以并且应该编写非错误日志。 - Bastian Voigt

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