为什么Java程序员经常默默地消耗异常?

85

我之前没有进行过任何严格的Java编程,但在已有技能(Delphi和C#)的基础上学习了语法、库和概念。但有一件事我始终难以理解,就是我看到很多代码在printStackTrace之后静默地处理异常:

    public void process() {
        try {
            System.out.println("test");
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

几乎每个我遇到的Java文章和项目中都有类似于这样的代码。根据我的知识,这是非常糟糕的做法。异常应该几乎总是被转发到外部上下文,就像这样:

    public void process() {
        try {
            System.out.println("test");
        } catch(Exception e) {
            e.printStackTrace();
            throw new AssertionError(e);
        }
    }
大部分情况下,异常应该在属于底层框架(例如Java Swing)的最外层循环中得到处理。为什么在Java世界里编码看起来像这样是一种惯例呢?我感到困惑。
基于我的背景,我更倾向于完全删除printStackTrace。我会将其重新抛出为未处理的RuntimeException(或者更好地说是AssertionError),然后在最合适的位置捕获并记录日志:框架的最外层循环。
    public void process() {
        try {
            System.out.println("test");
        } catch(Exception e) {
            throw new AssertionError(e);
        }
    }

59
我认为当堆栈跟踪被打印出来时,并不真正是“默默无言”的 :) - willcodejavaforfood
14
我可以翻译这句话。这句话的意思是:“我可以引用Isaac Waller的话:'但你的程序没有退出并继续运行,很可能处于未定义状态。'"。 - Sake
16
@willcodejavaforfood,你的评论获得了很多赞,这更让我对典型的Java思维方式感到困惑。 - Sake
14
@willcodejavaforfood - 对于调用者来说,这是无声的。调用者不知道发生了任何事情。如果这是预期的结果,那就没问题,但通常情况下,这是个坏消息。此外,请考虑在小程序中会发生什么-用户永远看不到输出(除非他们碰巧打开了Java控制台,这几乎不可能)。好吧,现在没有人再使用小程序了;) Servlet也差不多-最终用户不知道发生了什么-它只被储存在日志中。 - Scott Stanchfield
4
在Java中,异常抛出是非常普遍的,以至于大多数Java程序员会忽略它们,因为如果你正确地捕获错误,那么你需要编写更多的代码……所以基本上所有这些Java程序员这样做是因为他们懒惰。 - Trevor Boyd Smith
显示剩余7条评论
29个回答

209
我一直认为,这就像以下情况:
“一个人被枪击了。 他屏住呼吸,有足够的力气乘坐公交车。 10英里后,这个人下了车,走了几个街区,死了。”
当警察找到尸体时,他们不知道刚刚发生了什么。他们可能最终会知道,但要困难得多。
更好的做法是:
“一个人被枪击并立即死亡,尸体正好在谋杀发生的地方。”
警察到达时,所有证据都在那里。
如果系统要失败,最好是快速失败
回答问题:
1.无知。
2.懒惰
编辑:
当然,捕获部分很有用。
如果可以使用异常来解决问题,那就应该在那里解决。
也许这对于给定的代码来说不是一个例外,也许这是一些预期的事情(在我的类比中就像防弹衣,而这个人本来就在等待枪声)。
是的,catch可以用来抛出适合抽象的异常

2
吞咽异常并不是失败,更不用说快速失败了。另一方面,如果您的方法合同有一个参数是数字字符串,并且您将其解析为整数,则可以吞咽ParseException。如果您正在编写特定功能,请为该功能定义自定义异常,然后捕获异常并抛出您的自定义异常(带有原始异常作为参数)。然后它可以快速失败到某个有意义的行为发生的地方(显示给用户,发送电子邮件,仅记录日志,退出此服务器线程等)。 - JeeBee
23
等一下......我需要先停止笑声......好的......到目前为止,那是我见过最有趣的回答。它完全表达了要点。虽然将异常“向上传递”以供处理可能非常有帮助,但最好至少先捕获异常,然后再传递它,这样可以包含一些有用的信息。 - Mr. Will
异常并不是错误。抛出异常就像是一个人被枪击,然后在一张纸条上详细描述事件,并将其系在一只训练有素的信鸽身上,让它飞向最近的杀人调查组。 - Jherico
13
@Oscar,如果可以的话我愿意支付100美元。糟糕的异常处理是由于无知和恶意造成的,而不是懒惰。懒惰表示尽可能少的努力。在处理异常方面的愚蠢会导致更多的工作量,而不是减少它...当然,通常会给别人带来更多的工作,这就是为什么它看起来像是“懒惰”。 - James Schek
1
我明白你的观点,奥斯卡,但处理异常或记录异常只是告诉你发生了异常。如果你将它一直抛回到链的顶部,它会让你知道最终调用异常的方法是哪个。 - Mauro

33
通常情况下,这是由于IDE提供了一个有用的“快速修复”功能,它会将有问题的代码包装在try-catch块中并进行异常处理。这样做是为了让您实际上采取行动,但懒惰的开发人员不这样做。
这种做法很不好,毫无疑问。

2
同意,给出的示例看起来像是Eclipse默认的快速修复功能,只是去掉了 // FIXME: 注释。 - JeeBee
2
你说:“这是不好的形式,毫无疑问”,我完全同意。然而,一些其他答案和评论让我对那个非常大的部分(我相信)的Java开发人员背后的哲学感到非常好奇。 - Sake
4
我希望Eclipse在那方面能采取一些激烈的行动。例如,e.printStackTrace(); System.exit(1); - Adam Jaskiewicz
4
我已更改我的 IDE 设置,生成一个将异常重新抛出为 RuntimeException 的 catch 块。这是更好的默认设置。 - Esko Luontola
6
如果我确信异常永远不会发生,通常会抛出AssertionError而不是RuntimeException,以此来记录我的意图。 - Esko Luontola
显示剩余2条评论

22

这是一个典型的扫把式论证printStackTrace()是一个调试工具。如果你在博客或杂志上看到它,那是因为作者更关心的是说明除了异常处理之外的观点。如果你在生产代码中看到它,那么编写该代码的开发人员无知或懒惰,仅此而已。它不应该被作为“Java世界”中常见实践的例子。


1
那么,专业程序员在博客或杂志中写糟糕的代码而不是在生产环境中写是可以的吗?我宁愿在生产环境中写糟糕的代码。如果真正聪明的人正在使用printStackTrace,他会说服其他人也使用它。我知道异常代码多了8个字符,但是... - IAdapter
8
@ABCDE: 我完全不同意。你宁愿在生产环境中编写糟糕的代码吗?那是应该能够工作的代码!博客和杂志文章的目的是阐述一个观点。如果错误处理不是重点,那么它可能只是累赘。很多作者在文章中使用极简化的错误处理或根本不使用。这不是一个应该被效仿的例子。 - Bill the Lizard

19
  1. Java强制要求你显式地处理所有的异常。如果你的代码调用的方法声明了抛出FooException和BarException,那么你的代码必须处理(或者抛出)这些异常。唯一的例外是RuntimeException,它像忍者一样悄无声息。
  2. 许多程序员很懒(包括我自己),直接打印堆栈跟踪信息非常容易。

7
但你的程序没有退出,继续运行,很可能处于未定义状态。 - Isaac Waller
5
如果你知道异常的类型,那么状态为什么会未定义呢?这是一个有趣的小引语,但通常程序可以并且必须继续运行。你不想因为异常而使整个应用程序崩溃——告诉用户并要求他们再次执行相同的操作。 - GreenieMeanie
2
如果异常在最外层得到适当处理,应用程序就不会崩溃。 - Sake
2
至少一半的异常是“RuntimeExceptions”,你让它听起来像只有一个一样。而且它不是静默的,如果未捕获的RuntimeException将打印堆栈跟踪并导致程序崩溃。 - Bill K
@ greenieMeanie 通常在开发过程中,您希望它在最轻微的问题下崩溃-不要让开发人员逃脱调用错误-不要对任何事情都宽容。 另一方面,当您发布它时,您希望采取相反的方法。 暗示记录每个异常并尝试尽力恢复和继续(Java确实非常擅长此项工作)。 - Bill K
显示剩余4条评论

12

因为检查异常是一个失败的实验

(也许printStackTrace()是真正的问题?:)


17
它们不是失败的实验。实际上,我喜欢它们!正如其他答案所指出的那样,程序员倾向于懒惰并忽略错误情况。由于受检异常强制你处理它们,因此你的代码总体质量会更好(如果程序员没有默默地忽略它们)。另外,即使你不懒惰,你也不能忘记捕获重要的异常,因为编译器会告诉你这么做... - Malax
5
被检查异常有几个失败之处:请查看其中一些 http://www.mindview.net/Etc/Discussions/CheckedExceptions - dfa
2
+1 完全同意。检查异常通常会使代码变得不够安全,这是一个例子。 - cletus
2
检查异常绝对不完美,但它们确实迫使你面对现实,即有时事情并不按计划进行。非检查异常只是试图假装我们生活在一个完美无缺、没有任何问题的世界中。 - mcjabberz
2
@Peter Lawrey:检查异常是一种失败,因为像那些创造了惊人的Swing框架的非常聪明的人们会对它们感到恶心并拒绝使用它们。我非常怀疑像Joshua Bloch或Spring框架背后的人们“不知道如何正确使用它们”。当然,您可以正确处理已检查的异常。真正的破碎之处在于某个地方的某个人认为抛出一个已检查的异常是可接受的做法。 - SyntaxT3rr0r
显示剩余10条评论

12

我发现通常有两个原因会这样做:

  1. 程序员懒得写
  2. 程序员想要保护组件的进入点(正确或不正确)

我认为这并不是 Java 才有的现象。我也经常在 C# 和 VB.Net 代码中看到这种写法。

表面上看起来很令人震惊,看起来很糟糕。但实际上这并不是什么新鲜事物。在使用错误码返回值与异常不同的 C++ 应用程序中,这种情况经常发生。区别在于忽略一个可能致命的返回值看起来与调用返回 void 的函数并没有什么不同。

Foo* pFoo = ...;
pFoo->SomeMethod(); // Void or swallowing errors, who knows?

这段代码看起来更好,但是如果SomeMethod()返回一个HResult,那么它在语义上与吞下异常没有什么不同。


3
针对这个问题,我认为原因1比原因2更为普遍。 - matt b
2
@matt b,同意。懒惰负责我这些天修复的很大一部分错误。 - JaredPar
忽略错误代码要快得多,而且使用的样板代码也少得多! - gbjbaanb

6
我必须说,我有点不满意这种暗示此类松散的错误处理行为是Java程序员固有的东西的语气。当然,Java程序员可能会懒惰,就像其他程序员一样,而且Java是一种流行的语言,所以你可能会看到很多代码吞噬异常。
另外,正如其他地方指出的那样,Java强制声明已检查异常的做法令人沮丧,尽管个人认为这没有问题。
我有问题的是,你在浏览网上的一堆文章和代码片段时,没有考虑到上下文。事实上,当你编写一个技术文章,试图解释某个特定API的工作原理,或者如何开始使用某些东西时,你很可能会跳过代码的某些方面——与你演示的内容无关的错误处理是一个可能被丢弃的候选项,特别是如果异常在示例场景中不太可能发生。
撰写此类文章的人必须保持合理的信号与噪声比,而且我认为,这意味着他们必须假设你知道你正在开发的语言的一些基础知识;如何正确处理错误,以及许多其他事情。如果你遇到一篇文章并注意到缺乏适当的错误检查,那么没问题;只要确保在将这些想法(当然,绝不能是精确的代码本身,对吧?)纳入你的生产代码时,你将以最适合你正在开发的内容的方式处理所有这些小细节。
我确实有问题的是,非常高级的入门文章会轻描淡写地忽略这些问题,而从不回头再提。但请注意,Java程序员在错误处理方面没有特定的“心态”;我知道很多你们喜欢的C#程序员也不费力去解决他们所有的问题。

但是使用 try {...} catch (IOException e) {} 来编写高级文章是很愚蠢的(TODO 也没有帮助)。这肯定会误导许多初学者去做同样的事情。此外,它比 throws IOException 更难写,而后者在大多数情况下都是正确的。也许每篇论文只有一次不可能,那么编写 throw wrap(e)(无需定义 wrap)是一个好的解决方案。在 Java 中我学到的第一件事就是默默地吞噬异常是可怕的,我非常小心,即使是暂时的也不会这样做(即使崩溃在长期运行中也更好)。 - maaartinus

5

System.out print或e.printStackTrace() - 这意味着使用System.out通常是一个红旗,意味着某人没有尽职尽责。除了桌面Java应用程序外,大多数Java应用程序最好使用日志记录。

如果方法的故障模式是无操作,则吃掉异常是完全可以的,无论您是否记录原因(和存在)。然而,更典型的情况是,catch子句应该采取某种异常操作。

重新抛出异常最好在以下情况下进行:当您使用catch在仍然可用必要信息的级别上清理部分工作时,或者当您需要将异常转换为对调用者更有利的异常类型时。


3

如果catch块为空,它只会默默地被消耗掉。

就文章而言,它们可能更感兴趣的是证明除了如何处理异常之外的某些观点。他们只想直接进入主题,并编写最短的代码。

显然,你是正确的,如果异常将被“忽略”,那么它们至少应该被记录下来。


3
正如其他人指出的那样,你看到这个问题有以下三个原因之一:
  1. IDE生成了try-catch块
    • 代码被复制和粘贴
    • 开发人员放置了堆栈跟踪以进行调试,但从未回来正确处理异常

最后一点最不可能发生。我这么说是因为我不认为有人真的会用这种方式进行调试。使用调试器逐步执行代码是一种更容易的调试方式。

在catch块中应该做什么的最佳描述可以在Effective Java by Joshua Bloch的第9章中找到。


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