7.3 使用异常的提示
关于如何正确使用异常,存在着一定的争议。一些程序员认为所有检查异常都是令人讨厌的,其他人认为似乎扔得不够多。我们认为异常(甚至是检查异常)有它们的用处,并为您提供正确使用它们的提示。
-
异常处理不应该替换简单的测试。
作为一个例子,我们编写了一些代码,尝试10000000次来弹出一个空堆栈。它首先通过找出堆栈是否为空来实现这一点。if (!s.empty()) s.pop();
接下来,我们强制它弹出堆栈,不管发生什么,然后捕获
EmptySackException
,它告诉我们不应该这样做。try { s.pop(); } catch (EmptyStackException e) {}
在我们的测试机器上,调用
isEmpty
的版本在646毫秒内运行。捕获EmptySackException
的版本在21739毫秒内运行。
正如您所看到的,捕获异常比执行简单测试花费的时间要长得多。寓意是:只在特殊情况下使用例外。 -
不要对异常进行微观管理。
许多程序员将每个语句包装在一个单独的try
块中。PrintStream out; Stack s; for (i = 0; i < 100; i++) { try { n = s.pop(); } catch (EmptyStackException e) { // stack was empty } try { out.writeInt(n); } catch (IOException e) { // problem writing to file } }
这种方法会极大地破坏您的代码。考虑一下您希望代码完成的任务。在这里,我们要从一个堆栈中弹出100个数字,并将它们保存到一个文件中。(别介意为什么它只是一个玩具的例子。)如果一个问题出现在它丑陋的头上,我们就无能为力了。如果堆栈为空,则不会被占用。如果文件包含错误,错误不会神奇地消失。因此,将整个任务包装在一个try块中是有意义的。如果任何一个操作失败,则可以放弃该任务。
try { for (i = 0; i < 100; i++) { n = s.pop(); out.writeInt(n); } } catch (IOException e) { // problem writing to file } catch (EmptyStackException e) { // stack was empty }
这个代码看起来更干净。它实现了异常处理的承诺之一:将正常处理与错误处理分开。
-
充分利用异常层次结构。
不要只是抛出RuntimeException
。找到合适的子类或创建自己的子类。
不要只捕获Throwable
。它使代码难以阅读和维护。
尊重检查和不检查异常之间的差异。检查异常本身就很麻烦,不要因为逻辑错误而抛出异常。(例如,反射库出错。调用者通常需要捕获他们知道永远不会发生的异常。)
不要犹豫,把一个例外变成另一个更合适的例外。例如,在分析文件中的整数时,捕获NumberFormatException
并将其转换为IOException
或MySubSystemException
的子类。 -
不要压制异常。
在Java中,让异常闭嘴是一种巨大的诱惑。如果编写的方法调用的方法可能每世纪引发一次异常,编译器会发出呜呜声,因为您没有在方法的throws
列表中声明异常。你不想把它放在throws
列表中,因为编译器会抱怨所有调用你方法的方法。所以你就让它闭嘴:public Image loadImage(String s) { try { code that threatens to throw checked exceptions } catch (Exception e) {} // so there }
现在,您的代码将无障碍地编译。它将正常运行,除非发生异常。然后,异常将被静默地忽略。如果您认为异常非常重要,那么您应该努力正确地处理它们。
-
当你发现一个错误,“tough love”比放纵更有效。
有些程序员担心在检测错误时抛出异常。当使用无效参数调用方法时,返回一个伪值而不是抛出异常可能会更好?例如,Stack.pop
应该返回null
,还是在堆栈为空时引发异常?我们认为在失败点抛出EmptySackException
比在稍后发生NullPointerException
要好。扫描二维码关注公众号,回复: 6436635 查看本文章 -
传播异常并不是羞耻的表现。
许多程序员感到必须捕获抛出的所有异常。如果调用引发异常的方法,例如FileinputStream
构造函数或readLine
方法,则会本能地捕获可能生成的异常。通常情况下,最好传播异常而不是捕获它:public void readStuff(String filename) throws IOException { var in = new FileInputStream(filename, StandardCharsets.UTF_8); . . . }
更高级别的方法通常能够更好地通知用户错误或放弃不成功的命令。
注意
规则5和6可以概括为“早抛,晚抓”。