为什么Java人会经常静静地使用exception呢?
我从来没有做过任何严肃的Java编码,但是我学习了基于现有技能(Delphi&C#)的语法,库和概念。 我很难理解的一件事是,我看过如此多的代码,在printStackTrace
之后默默地使用exception:
public void process() { try { System.out.println("test"); } catch(Exception e) { e.printStackTrace(); } }
在我遇到的几乎所有的Java文章和项目中都有类似的代码。 据我所知,这是非常糟糕的。 应该几乎总是将exception转发到外部环境,如下所示:
public void process() { try { System.out.println("test"); } catch(Exception e) { e.printStackTrace(); throw new AssertionError(e); } }
大多数情况下,exception应该在属于底层框架的最外层循环处理(例如Java Swing)。 为什么它看起来像在Java世界中这样编码的规范? 我感到困惑。
基于我的背景,我宁愿完全删除printStackTrace。 我会简单地重新抛出一个未处理的RuntimeException
(或更好的AssertionError
),然后在最合适的位置捕获并logging它:框架最外层的循环。
public void process() { try { System.out.println("test"); } catch(Exception e) { throw new AssertionError(e); } }
我一直认为,这与以下情况类似:
“一个人被枪杀。
他屏住呼吸,有足够的力量坐公车。
10英里后,这个人下车,走了几个街区,死亡。“
当警察到达尸体时,他们对于刚刚发生的事情一无所知。 他们最终可能会但是更难。
更好的是:
“一名男子被枪杀,他立即死亡,尸体正好位于刚发生的谋杀案的地点。”
警方到达时,所有的证据都已到位。
如果一个系统失败,最好是快速失败
解决这个问题:
- 无知。
- +
- 懒惰
编辑:
当然,catch部分是有用的。
如果有什么可以做的例外,这是应该做的地方。
对于给定的代码来说,这可能不是一个例外,也许这是一个预期的事情(在我的比喻中就像一件防弹夹克,而男人正在等待这个镜头)。
是的,捕获可以用来抛出适当的抽象exception
通常,这是由于IDE提供了一个有用的“快速修复”,将有问题的代码封装在try-catch块中,并进行exception处理。 这个想法是,你实际上做了什么,但懒惰的开发人员不。
毫无疑问,这是糟糕的forms。
- Java迫使你明确地处理所有的exception。 如果你的代码调用的方法被声明为抛出FooException和BarException,那么你的代码必须处理(或抛出)这些exception。 唯一的例外是RuntimeException ,它像一个忍者一样沉默。
- 很多程序员都懒惰(包括我自己),打印堆栈跟踪非常容易。
这是一个经典的稻草人论点 。 printStackTrace()
是一个debugging帮助。 如果你在博客或杂志上看到它,那是因为作者更感兴趣的是说明exception处理以外的一点。 如果你在生产代码中看到它,那么这个代码的开发者就是无知或者懒惰,没有什么更多的了。 在“Java世界”中不应该被当作惯例。
因为检查exception是一个失败的实验
(也许printStackTrace()是真正的问题 ?:)
我发现这通常有两个原因
- 程序员很懒
- 程序员想要保护入口点进入组件(正确或不正确)
我不相信这是一个仅限于Java的现象。 我经常在C#和VB.Net中看到这样的编码。
表面上看起来很震撼,看起来很糟糕。 但真的没什么新意。 它始终在使用错误代码返回值与exception的C ++应用程序中发生。 不同之处在于,忽略可能致命的返回值与调用返回void的函数看起来没有任何区别。
Foo* pFoo = ...; pFoo->SomeMethod(); // Void or swallowing errors, who knows?
这段代码看起来更好,但是如果SomeMethod()要返回一个HResult,它在语义上与吞下exception没有区别。
我不得不说,我略微反感这种松散的error handling行为是Java程序员的根本。 当然,Java程序员可以像其他程序员一样懒惰,Java是一种stream行的语言,所以你可能会看到很多代码吞噬exception。
此外,正如其他地方已经指出的那样,Java对于检查exception的强制声明有一些可以理解的挫折,虽然我个人并没有这个问题。
我想,我有一个问题,就是你正在通过networking上的一些文章和代码片段来sorting,而不必考虑上下文。 事实是,当你写技术文章试图解释一些特定的API是如何工作的,或者如何开始使用某些东西的时候,你很可能会跳过代码的某些方面 – error handling不直接与您正在展示的内容相关的是可能的处置对象,尤其是在示例场景中不太可能发生exception的情况下。
写这种性质的文章的人必须保持合理的信噪比,而且我认为这相当公平,这意味着他们必须假设你了解你正在开发的语言的一些基本知识; 如何正确处理错误,以及其他一些事情。 如果你遇到一篇文章,并注意到缺乏适当的错误检查,那就没事了; 只要确保当你将这些想法(当然,从来没有确切的代码本身,对吧?)合并到你的生产代码中时,你会处理所有那些作者明智地忽略的那些小东西,适合你正在开发的东西。
我在高级别的介绍性文章中遇到了一些问题,这些文章可以轻松地回避这些问题,但是请注意,Java程序员在处理错误方面没有什么特别的“心态”。 我知道很多你喜欢的C#程序员,他们也不用去处理所有的问题。
一个System.out打印或e.printStackTrace() – 这意味着使用System.out通常是一个红旗,这意味着有人没有打扰做一个勤奋的工作。 除了桌面Java应用程序,大多数Java应用程序使用日志logging更好。
如果一个方法的失败模式是无操作,那么无论是否logging原因(和存在),吃掉一个exception都是非常好的。 然而,更典型的是,catch语句应该采取某种特殊的行动。
当您使用catch来清理部分工作时,如果必要的信息仍然可用,或者需要将exception转换为更适合调用者的exceptiontypes,那么重新生成exception是最好的方法。
如果catch块真的是空的,它只会被静静地消耗掉。
就文章而言,除了如何处理exception之外,他们可能更有趣地certificate其他一些观点。 他们只是想直接点,并有尽可能短的代码。
显然你是对的,但是如果将被忽略,至less应该loggingexception。
正如其他人所指出的,你看到这个原因是出于以下三个原因之一:
- 一个IDE生成了try-catch块
- 代码被复制和粘贴
- 开发人员把堆栈跟踪进行debugging,但从来没有回来正确处理exception
最后一点是最不可能发生的。 我这样说是因为我不认为有人真的这样debugging。 用debugging器来代码是一个更简单的debugging方法。
关于在catch块中应该做什么的最佳描述可以在Joshua Bloch的Effective Java的第9章中find。
在C#中,所有exception都是运行时exception,但在Java中,您有运行时exception和检查exception,您必须捕获或在您的方法中声明。 如果你最后调用了任何有“抛出”的方法,你必须捕获那里提到的exception,否则你的方法也必须声明这些exception。
Java文章通常只是打印堆栈跟踪或有一个评论,因为exception处理是不相关的文章的主题。 但在项目中,应根据exception的types进行处理。
如果程序员的工作正确,你应该经常看到这一点。 忽略例外是一个糟糕的,不好的做法! 但是为什么有些人可能会这样做,以及更为合适的解决scheme有一些原因:
-
“这不会发生!” 当然,有时候你“知道”这个exception不会发生,但是它更适合于重新抛出一个运行时exception,而把exception作为“原因”而不是忽略它。 我敢打赌,它将在未来的某个时候。 😉
-
原型代码如果你只是input你的东西,看看它是否可行,你可能想忽略所有的exception,可能occour。 这是唯一的情况,我做了一些懒惰的捕获(Throwable)。 但是,如果代码将变成有用的东西,我包括适当的exception处理。
-
“我不知道该怎么办!” 我看到了很多代码,尤其是库代码,这会吞噬exception,因为在应用程序的这一层,不能进行适当的处理。 不要这样做! 只需重新抛出exception(通过在方法签名中添加throws子句或将exception封装到特定的库中)。
您应该始终将其转发或在现实环境中适当处理。 大量的文章和教程将简化他们的代码,以获得更重要的点,而最简单的事情之一就是error handling(除非你正在写一篇关于error handling的文章:))。 由于java代码将检查exception处理,因此将简单的静默(或日志logging语句)catch块放在最简单的方法来提供一个工作示例。
如果您在示例代码以外的任何地方都可以find它,可以将代码转发到TDWTF,尽pipe现在可能有太多的例子:)
检查的exception和接口的组合导致代码必须处理永远不会抛出的exception的情况。 (这同样适用于正常的inheritance,但是用接口解释更为常见和容易)
原因:接口的实现可能不会抛出(检查)除了在接口规范中定义的exception之外的exception。 出于这个原因,接口的创build者,不知道实现接口的类的哪些方法可能实际上需要抛出exception,可能会指定所有的方法可能抛出至less一种types的exception。 示例:JDBC,其中声明所有内容和祖母都抛出SQLException。
但实际上,很多真正实现的方法根本不能失败,所以在任何情况下都不会抛出exception。 调用这个方法的代码仍然必须以某种方式“处理”这个exception,最简单的方法就是去执行这个exception。 没有人想用看似毫无用处的error handling来干扰他的代码。
正如指出的,调用printStackTrace()并不是真正的无声处理。
这种“吞咽”exception的原因是,如果你不断地将exception传递给链,那么你仍然需要处理exception,或者让应用程序崩溃。 所以,在信息转储的层面处理它并不比在信息转储的顶层处理更糟糕。
它的懒惰练习 – 真的没什么。
当你真的不关心exception情况时,通常会这样做 – 而不是增加你的手指。
我不同意重新抛出检查的exception是一个更好的主意。 捕捉意味着处理; 如果你不得不重新抛出,你不应该赶上。 在这种情况下,我会将throws子句添加到方法签名中。
我想说的是将一个检查过的exception封装在一个未经检查的exception中(例如,Spring将检查过的SQLException封装到未经检查的层次结构的实例中)是可以接受的。
logging可以被认为是处理。 如果更改示例使用log4j而不是写入控制台来logging堆栈跟踪,是否可以接受? 国际海事组织没有太大的改变。
真正的问题是被认为是特殊的和可接受的恢复程序。 如果无法从exception中恢复,则可以做的最好是报告失败。
恐怕大部分的java程序员都不知道如何处理Exceptions,而且总是认为这是一个令人厌烦的事情,会减慢他们对“标称”情况的编码。 当然,他们是完全错误的,但很难说服他们正确处理exception是很重要的。 每次遇到这样的程序员(经常发生),我都给他两个阅读条目:
- 着名的Thinking in java
- Barry Ruzek的一篇简短而有趣的文章可以在这里find:www.oracle.com/technology/pub/articles/dev2arch/2006/11/effective-exceptions.html
顺便说一句,我非常同意,捕获一个typesexception重新抛出embedded到RuntimeException中是愚蠢的:
- 如果你抓住它,处理它。
- 否则,请更改您的方法签名以添加您将无法处理的可能exception,以便您的调用者有机会自行完成。
请永远永远不要将检查的exception包装在未经检查的exception中。
如果你发现自己处理的是你认为不应该的事情,那么我的build议就是你可能在抽象层次上工作。
我将详细说明:选中和取消选中的例外是两个截然不同的野兽。 检查exception类似于返回错误代码的旧方法…是的,它们比错误代码更容易处理,但它们也具有优势。 未经检查的例外是编程错误和严重的系统故障……换句话说就是例外情况。 当试图解释什么是一个例外是很多人陷入了一个完整的混乱,因为他们不承认在这两个非常不同的情况下的差异。
因为他们还没有学到这个技巧:
class ExceptionUtils { public static RuntimeException cloak(Throwable t) { return ExceptionUtils.<RuntimeException>castAndRethrow(t); } @SuppressWarnings("unchecked") private static <X extends Throwable> X castAndRethrow(Throwable t) throws X { throw (X) t; } } class Main { public static void main(String[] args) { // Note no "throws" declaration try { // Do stuff that can throw IOException } catch (IOException ex) { // Pretend to throw RuntimeException, but really rethrowing the IOException throw ExceptionUtils.cloak(ex); } } }
真正意义上的例外是简化error handling并将其从错误检测中分离出来。 这与通过错误代码表示错误相反,其中error handling代码散布在各处,并且每个可能失败的调用都将被检查用于返回代码。
如果exception表示一个错误(这是大多数情况下),通常处理它的最合理的方式是纾困,并把处理留给上层。 如果添加了一些有意义的语义,那么就应该考虑不同的exception,即这个错误是一个不寻常的系统故障/临时(networking)问题/这是客户端或服务器端错误等。
在所有的error handling策略中,最无知的是隐藏或只是打印出错信息,并且没有发生任何事情。
Sun的人希望代码更加明确,迫使程序员写出哪种方法可能抛出哪些exception。 这似乎是正确的举动 – 任何人都会知道从任何方法调用返回的期望,因为它的原型(它可能会返回此types的值或抛出一个指定的类(或它的子类)的实例)。
但是,由于很多无知的Java程序员发现,他们现在将exception处理视为一种语言错误/“特性”,需要采取一种解决方法,并以最差或几乎最差的方式编写代码:
- 错误在上下文中被处理,不适合决定如何处理它。
- 即使在进一步的代码没有机会正常运行时,它也会默默显示或忽略,并继续计算。
- 方法的调用者不能区分是否成功完成。
如何写“正确的方式”比?
- 指出可以在方法头中引发的每个基类的exception。 Eclipse可以自动执行。
- 使方法原型中的throw列表有意义。 长列表是毫无意义的,“抛出exception”是懒惰的(但对于例外情况你没有什么麻烦)。
- When writing the "wrong way" simple "throw Exception" is much better and takes less bytes than "try{ … } catch(Exception e) { e.printStackTrace(); }".
- Rethrow chained exception if needed.
If you want your exception to be handled outside the scope of the current method you don't need to to catch it actually, instead you add 'throws' statement to the method signature.
The try/catch statements that you've seen are only in the code where programmer explicitly decided to handle the exception in place and therefore not to throw it further.
I think developers also try to consider the importance of "doing the right thing" in a particular context. Often times for throw away code or when propagating the exception upwards wouldn't buy anything because the exception is fatal, you might as well save time and effort by "doing the wrong thing".
You would usually swallow an exception when you cannot recover from it but it is not critical. One good example is the IOExcetion that can be thrown when closing a database connection. Do you really want to crash if this happens? That's why Jakarta's DBUtils have closeSilently methods.
Now for checked exception that you cannot recover but are critical (usually due to programming errors), don't swallow them. I think exceptions should be logged the nearest as possible to the source of the problem so I would not recommend removing the printStackTrace() call. You will want to turn them into RuntimeException for the sole purpose of not having to declare these exceptions in you business method. Really it doesn't make sense to have high level business methods such as createClientAccount() throws ProgrammingErrorException (read SQLException), there is nothing you can do if you have typos in your sql or accessing bad indexes.
From experience, Swallowing an exception is harmful mostly when it's not printed. It helps to bring attention if you crash, and I'll do that deliberately at times, but simply printing the exception and continuing allows you to find the problem and fix it, and yet usually doesn't negatively effect others working on the same codebase.
I'm actually into Fail-Fast/Fail-HARD, but at least print it out. If you actually "Eat" an exception (which is to truly do nothing: {}) It will cost someone DAYS to find it.
The problem is that Java forces you to catch a lot of stuff that the developer knows won't be thrown, or doesn't care if they are. The most common is Thread.sleep(). I realize there are holes that might allow threading issues here, but generally you know that you are not interrupting it. 期。
There can be many reasons why one would use catch Exception. In many cases it is a bad idea because you also catch RuntimeExceptions – and well you don't know in what state the underlying objects will be after this happens ? That is always the difficult thing with unexpected conditions: can you trust that the rest of the code will not fail afterwards.
Your example prints the stacktrace so at least your will know what the root cause might have been. In bigger software projects it is a better idea to log these things. And lets hope that the log component does not throw exceptions either our you might end up in an infinite loop (which will probably kill your JVM).
If you have a checked exception and you don't want to handle it in a method, you should just have the method throw the exception. Only catch and handle exceptions if you are going to do something useful with it. Just logging it is not very useful in my book as users rarely have time to be reading logs looking for exceptions or know what to do if an exception is thrown.
While wrapping the exception is an option, I would not suggest you do this unless; you are throwing a different exception to match an exist interface or there really is no way such an exception should be thrown.
BTW: If you want to re throw a checked exception you can do this with
try { // do something } catch (Throwable e) { // do something with the exception Thread.currentThread().stop(e); // doesn't actually stop the current thread, but throws the exception/error/throwable }
Note: if you do this, you should make sure the throws declaration for the method is correct as the compiler is unable to do this for you in this situation.
because it is a best practice. I thought everybody knew.
but the cold truth is that nobody really understands how to work with exceptions. The C error handing style made so much more sense.