为什么Java人经常默默地使用异常?

时间:2009-05-28 15:26:27

标签: java exception exception-handling

我之前从未做过任何严肃的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 。我只是简单地重新抛出一个未处理的aka RuntimeException(或者更好,AssertionError),然后在最合适的位置捕获并记录它:框架最外层循环。

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

29 个答案:

答案 0 :(得分:191)

我一直认为,这与以下情况类似:

  

“一个人被枪杀。

     

他屏住呼吸,有足够的力量坐公共汽车。

     

10英里后,男子从公共汽车下车,走了几个街区并死亡。“

当警察到达尸体时,他们不知道刚刚发生的事情。他们最终可能会有,但更难。

更好的是:

  

“一名男子被枪杀,他立即死亡,身体正好位于谋杀刚刚发生的地方。”

当警察到达时,所有证据都已到位。

如果系统失败,最好是fail fast

解决问题:

  1. 无知。
      +
  2. 怠惰
  3. 修改

    当然,catch部分很有用。

    如果可以通过异常完成某些事情,那就应该在那里完成。

    可能这不是给定代码的例外,可能它是预期的东西(在我的比喻中就像一个防弹夹克,而男人正在等待首先拍摄)。

    是的,捕获可用于Throw exceptions appropriate to the abstraction

答案 1 :(得分:33)

通常这是因为IDE提供了一个有用的“快速修复”,它将违规代码包装在带有异常处理的try-catch块中。这个想法是你实际做了什么,但懒惰的开发人员没有。

毫无疑问,这是一种糟糕的形式。

答案 2 :(得分:19)

  1. Java强制您明确处理所有异常。如果您的代码调用的方法被声明为抛出FooException和BarException,则代码必须处理(或抛出)这些异常。唯一的例外是 RuntimeException ,它像忍者一样沉默。
  2. 很多程序员都很懒(包括我自己),只需打印堆栈跟踪就很容易了。

答案 3 :(得分:19)

这是一个经典的straw man argumentprintStackTrace()是一个调试辅助工具。如果你在博客或杂志上看到它,那是因为作者更喜欢说明其他而不是异常处理。如果你在生产代码中看到它,那段代码的开发者就是无知或懒惰,仅此而已。它不应该被视为“java世界”中常见做法的一个例子。

答案 4 :(得分:12)

我发现这通常有两个原因

  1. 程序员很懒惰
  2. 程序员希望保护进入该组件的入口点(正确或不正确)
  3. 我不相信这是一种仅限于Java的现象。我也经常在C#和VB.Net中看到这样的编码。

    表面上看起来很震撼,看起来很可怕。但实际上并不是什么新鲜事。它始终出现在使用错误代码返回值与异常的C ++应用程序中。但不同之处在于忽略可能致命的返回值与调用返回void的函数看起来并没有什么不同。

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

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

答案 5 :(得分:11)

因为已检查的例外是一个失败的实验

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

答案 6 :(得分:6)

我不得不说,我略微反感这种暗示这种松散的错误处理行为的语气是Java程序员的基础。当然,Java程序员可能很懒,就像其他程序员一样,而Java是一种流行的语言,所以你可能会看到许多代码吞噬异常。

另外,正如其他地方所指出的那样,Java强制声明已检查的异常是可以理解的,尽管我个人对此没有任何问题。

我想,我遇到的问题是,你在网上轻松浏览一堆文章和代码片段而不必费心考虑上下文。事实是,当你写一篇技术文章试图解释一些特定的API如何工作,或者如何开始使用某些东西时,你很可能会跳过代码的某些方面 - 错误处理不是直接的与您所展示的内容相关的可能是处置的候选者,尤其是在示例场景中不太可能发生异常时。

写这种性质的文章的人必须保持合理的信噪比,而且相当公平,我认为,这意味着他们必须假设你了解一些关于你正在开发的语言的基础知识;如何妥善处理错误,以及其他一些事情。如果您遇到一篇文章,并注意到缺少正确的错误检查,那就没关系;只是确保当你将这些想法(但当然,从来没有确切的代码本身,对吗?)纳入你的生产代码时,你将处理作者明智地遗漏的所有那些碎片和爆炸,以一种最大的方式适合你正在开发的东西。

我确实遇到了一些非常高级的介绍性文章的问题,这些文章在没有回复的情况下轻易解决这些问题,但请注意,Java程序员没有一些关于错误处理的特定“思维模式”;我知道很多心爱的C#程序员,他们也不打算处理他们遇到的所有问题。

答案 7 :(得分:5)

System.out print或e.printStackTrace() - 暗示使用System.out通常是一个红旗,意味着有人不愿意做一份勤奋的工作。除了桌面Java应用程序之外,大多数Java应用程序最好使用日志记录。

如果方法的失败模式是无操作,则无论是否记录原因(和存在),都可以完全没问题。然而,更典型的是,catch子句应采取某种特殊行动。

当您使用catch在必要信息仍然可用的级别上清理部分工作时,或者当您需要将异常转换为更适合于异常类型的异常类型时,最好重新抛出异常。呼叫者。

答案 8 :(得分:3)

如果catch块真的是空的,它只会静默消耗。

就文章而言,除了如何处理异常之外,他们可能更有趣地证明了其他一些观点。他们只想直截了当地获得最短的代码。

显然你是对的,如果它们被“忽略”,至少应该记录异常。

答案 9 :(得分:3)

正如其他人所指出的那样,您认为这是出于以下三个原因之一:

  1. IDE生成了try-catch块
    • 代码已复制并粘贴
    • 开发人员将堆栈跟踪放入调试中,但从未回过头来正确处理异常
  2. 最后一点是最不可能发生的。我这样说是因为我认为没有人真的以这种方式调试。使用调试器逐步调试代码是一种更容易调试的方法。

    可以在Effective Java by Joshua Bloch的第9章中找到关于应该在catch块中完成的内容的最佳描述。

答案 10 :(得分:2)

如果程序员正确地完成他的工作,你应该经常看到这一点。忽略异常是一种糟糕的坏习惯!但是有些原因可能会导致一些人可能做到这一点以及更具有适用性的解决方案:

  • “这不会发生!” 当然,有时候你“知道”这个异常不会发生,但它更适合重新抛出一个运行时异常,而将异常的异常作为“原因”而不是忽略它。我打赌它将在未来的某个时候出现。 ; - )

  • 原型代码 如果你只是输入你的东西以确定它是否成功,你可能想要忽略可能出现的所有异常。这是我做一些懒惰捕获(Throwable)的唯一情况。但如果代码变成有用的东西,我会包含适当的异常处理。

  • “我不知道该怎么办!” 我看到很多代码,特别是库代码,它吞噬了异常,因为在应用程序的这一层中没有正确的处理。不要这样做!只需重新抛出异常(通过在方法签名中添加throws子句或将异常包装到特定于库的异常中)。

答案 11 :(得分:2)

已检查的异常和接口的组合导致代码必须处理从未被抛出的执行的情况。 (这同样适用于正常继承,但它更常见,更容易用接口解释)

原因:接口的实现可能不会抛出(检查)除接口规范中定义的异常之外的异常。因此,接口的创建者(不知道实现接口的类的哪些方法实际上可能需要抛出异常)可能会指定所有方法都可能抛出至少一种类型的异常。示例:JDBC,声明所有内容及其祖母都抛出SQLException。

但实际上,许多实际实现的方法都不会失败,因此在任何情况下,它们都不会抛出异常。调用此方法的代码仍必须以某种方式“处理”异常,最简单的方法是执行该操作以避免异常。没有人想用他从未执行的看似无用的错误处理来混淆他的代码。

答案 12 :(得分:2)

我担心大多数java程序员不知道如何处理异常,并且总是认为这会减慢他们对“名义”案例的编码速度。 当然,他们完全错了,但很难说服他们正确处理异常很重要。 每当我遇到这样的程序员(经常发生)时,我会给他两个阅读条目:

  • 着名的思维在java中
  • Barry Ruzek的一篇简短而有趣的文章可在此处获取:www.oracle.com/technology/pub/articles/dev2arch/2006/11/effective-exceptions.html

顺便说一下,我非常同意捕获一个类型化的异常以重新抛出它嵌入在RuntimeException中是愚蠢的:

  • 如果你抓住它,请处理它。
  • 否则,更改您的方法签名以添加您可能/无法处理的可能异常,这样您的调用者就有机会自己完成。

答案 13 :(得分:2)

在C#中,所有异常都是运行时异常,但在Java中,您有运行时异常和已检查的异常,您必须在方法中捕获或声明。如果你在最后调用任何具有“throws”的方法,你必须要么捕获那里提到的异常,要么你的方法也必须声明那些异常。

Java文章通常只打印堆栈跟踪或发表评论,因为异常处理与文章的主题无关。但是在项目中,应根据异常的类型对其进行一些处理。

答案 14 :(得分:2)

您应该始终将其转发或在现实世界中适当地处理它。许多文章和教程将简化他们的代码以获得更重要的观点,并且最简单的一个简化是错误处理(除非你正在做的是写一篇关于错误处理的文章:))。由于java代码将检查异常处理,然后放置一个简单的静默(或记录语句)catch块是提供工作示例的最简单方法。

如果您在示例代码之外的其他任何地方找到它,请随时将代码转发到TDWTF,尽管它们现在可能有太多的示例:)

答案 15 :(得分:1)

它的懒惰练习 - 真的没什么。

当你真的不关心异常时,通常会这样做 - 而不是增加你的指法。

答案 16 :(得分:1)

我不同意重新抛出已检查的异常是一个更好的主意。捕捉意味着处理;如果你必须重新抛出,你就不应该抓住。在这种情况下,我会将throws子句添加到方法签名中。

我想说,在未经检查的异常中包装一个已检查的异常(例如,Spring将已检查的SQLException包装到其未经检查的层次结构的实例中)是可以接受的。

记录可以视为处理。如果更改示例以使用log4j而不是写入控制台来记录堆栈跟踪,那么这是否可以接受?没什么变化,IMO。

真正的问题是被认为是例外和可接受的恢复程序。如果无法从异常中恢复,则可以做的最好是报告失败。

答案 17 :(得分:1)

正如所指出的那样,调用printStackTrace()并不是真正的静默处理。

这种“吞下”异常的原因是,如果你继续将异常传递到链上,你仍然必须处理异常某处或让应用程序崩溃。因此,在信息转储发生的级别处理它并不比使用信息转储在顶层处理它更糟糕。

答案 18 :(得分:1)

异常的真正意义在于简化错误处理并将其与错误检测分开。这与错误代码表示错误相反,其中错误代码 处理代码分散在各处,每个可能失败的呼叫都应该被检查 返回代码。

如果异常表示错误(大多数情况下),通常最合理的处理方法是纾困并将处理留给某些上层。 如果添加了一些有意义的语义,则应该考虑重新抛出不同的异常,即,此错误是一个异常的系统故障/临时(网络)问题/这是客户端或服务器端错误等。

在所有错误处理策略中,最无知的是隐藏或只是打印错误信息,并且没有发生任何事情。


Sun人员希望代码更加明确,强制程序员可以编写哪种方法可以抛出异常。这似乎是正确的举动 - 任何人都会 知道在给出它的原型的任何方法调用的回报期望(它可能返回 此类型的值或抛出其中一个指定类(或它的子类)的实例。)

但是,随着大量无知的Java程序员的出现,他们现在将异常处理视为语言错误/“功能”,需要解决方法并以最差或最差的方式编写代码:

  • 错误会立即处理,不适合决定如何处理错误。
  • 静默显示或忽略它,即使有更多代码,计算仍会继续 没有机会正常运行。
  • 方法的调用者无法区分是否成功完成。

如何写出“正确的方式”而不是?

  • 表示可以在方法头中抛出的每个基类异常。 AFAICR Eclipse可以自动完成。
  • 使方法原型中的throw列表有意义。 长列表毫无意义,“抛出异常”是懒惰的(但是当你不打扰时很有用 很多关于例外的事情)。
  • 当写“错误的方式”时,简单的“抛出异常”就好多了 比“try {...} catch(Exception e){e.printStackTrace();}”更少的字节。
  • 如果需要,Rethrow链接异常。

答案 19 :(得分:1)

请永远不要将已检查的异常包装在未经检查的异常中。

如果你发现自己处理了你认为不应该做的异常,那么我的建议是你可能在错误的抽象层面上工作。

我将详细说明:已检查和未检查的异常是两种非常不同的野兽。检查异常类似于返回错误代码的旧方法...是的,它们比错误代码更难处理,但它们也有优势。未经检查的异常是编程错误和关键系统故障......换句话说,异常异常。当试图解释什么是异常时,许多人陷入了完全混乱,因为他们不承认这两个非常不同的案例的区别。

答案 20 :(得分:1)

因为他们还没有学会这个技巧:

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);
        }
    }
}

答案 21 :(得分:0)

当你无法从中恢复时,你通常会吞下一个例外,但这并不重要。一个很好的例子是关闭数据库连接时可以抛出的IOExcetion。如果发生这种情况,你真的想要崩溃吗?这就是为什么Jakarta的DBUtils有closeSilently方法。

现在,对于已检查的异常,您无法恢复但是很关键(通常是由于编程错误),请不要吞下它们。我认为异常应该尽可能地记录到问题的根源,所以我不建议删除printStackTrace()调用。您将需要将它们转换为RuntimeException,其唯一目的是不必在业务方法中声明这些异常。实际上,使用createClientAccount()之类的高级业务方法抛出ProgrammingErrorException(读取SQLException)是没有意义的,如果你的sql中有拼写错误或访问坏索引,你就无能为力。

答案 22 :(得分:0)

如果希望在当前方法的范围之外处理异常,则不需要实际捕获它,而是将“throws”语句添加到方法签名中。

你看到的try / catch语句只存在于程序员明确决定处理异常的代码中,因此不会进一步抛出它。

答案 23 :(得分:0)

根据经验,吞咽异常主要是在没有打印时有害。如果你崩溃有助于引起注意,我会故意这样做,但只是打印异常并继续允许你找到问题并修复它,但通常不会对在同一代码库上工作的其他人产生负面影响

我实际上已进入Fail-Fast / Fail-HARD,但至少打印出来。如果你真的“吃”了一个例外(这是真的什么都不做:{})它会花费某人DAYS来找到它。

问题在于Java会强迫你抓住很多开发人员知道不会抛出的东西,或者不关心它们是不是。最常见的是Thread.sleep()。我意识到这里有漏洞可能允许线程问题,但通常你知道你没有打断它。周期。

答案 24 :(得分:0)

为什么会使用catch异常可能有很多原因。在许多情况下,这是一个坏主意,因为你也捕获了RuntimeExceptions - 而且你不知道在发生这种情况之后底层对象将处于什么状态?对于意想不到的情况来说,这总是困难的事情:你能相信其余的代码不会在之后失败。

您的示例打印了堆栈跟踪,因此至少您会知道根本原因可能是什么。在更大的软件项目中,记录这些东西是更好的主意。并且希望日志组件不会抛出异常,否则我们可能最终会陷入无限循环(这可能会导致您的JVM崩溃)。

答案 25 :(得分:0)

我认为开发者也会尝试考虑在特定环境中“做正确的事”的重要性。抛弃代码或向上传播异常通常不会购买任何东西,因为异常是致命的,你可以通过“做错事”来节省时间和精力。

答案 26 :(得分:0)

如果您有一个已检查的异常并且您不想在方法中处理它,那么您应该让该方法抛出异常。如果要对它做一些有用的事情,只捕获和处理异常。在我的书中记录它并不是很有用,因为用户很少有时间阅读日志以查找异常或知道如果抛出异常该怎么做。

虽然包装异常是一种选择,但我不建议你这样做,除非;你正在抛出一个不同的异常来匹配一个存在的接口,或者实际上没有办法抛出这样的异常。

BTW:如果你想重新抛出一个已检查的异常,可以用

执行此操作
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
}

注意:如果执行此操作,则应确保方法的throws声明正确,因为在这种情况下编译器无法为您执行此操作。

答案 27 :(得分:0)

想象一种情况,您有 200 万行要处理。每行有 10 列。您知道只有 10 行在某些列中有一个字符,该字符不正确且不重要,适用于后续步骤的非处理项目。您是否检查每列的所有行以搜索此字符?包裹无声方法成本较低。当然,您有不同的方法来应对这种情况。但其中之一是静默方法。

答案 28 :(得分:-2)

因为这是最佳做法。我以为每个人都知道 但冷酷的事实是,没有人真正理解如何处理异常。 C错误处理方式更有意义。