Answers:
首先,像其他任何编程范例一样,您需要正确地进行操作才能使其正常工作。
对我而言,检查异常的优点是Java运行时库的作者已经为我决定了可以合理预期在调用点处可以解决哪些常见问题(与顶级catch-print-死块),并尽早考虑如何处理这些问题。
我喜欢检查异常,因为它们使我不得不尽早考虑错误恢复,从而使我的代码更健壮。
确切地说,对我来说,这使我的代码更加健壮,因为它迫使我在处理过程的早期就考虑奇怪的极端情况,而不是说基于:生产中的错误,然后必须重新处理代码以进行处理。在进行维护时,将错误处理添加到现有代码中可能不是一件容易的事-因此非常昂贵,而不是一开始就做。
丢失的文件可能是致命的,应该导致程序崩溃。但是,您可以使用
} catch (FileNotFoundException e) {
throw new RuntimeException("Important file not present", e);
}
这也显示出非常重要的副作用。如果您包装了一个异常,则可以在堆栈跟踪中添加一个解释!这种功能非常强大,因为您可以添加有关例如丢失文件名或传递给此方法的参数的信息或其他诊断信息,并且该信息直接出现在堆栈跟踪中,而这通常只是您要做的事情当程序崩溃时获取。
人们可能会说“我们可以在调试器中运行此程序以进行重现”,但是我发现很多时候以后无法重现生产错误,并且除非在非常讨厌的情况下,否则您就不能在生产中运行调试器,这在本质上会危及您的工作。
堆栈跟踪中的信息越多越好。受检查的异常有助于我尽早获得该信息。
编辑:这也适用于图书馆设计师。我每天使用的一个库包含许多检查过的异常,这些异常本可以设计得更好,从而减少了使用时的乏味。
您有两个很好的答案,它们解释了实践中已检查的异常。(两者均为+1。)但是,从理论上来说,检查它们的意图也是值得的,因为意图实际上是值得的。
检查异常实际上是为了使语言更安全。考虑一个简单的方法,例如整数乘法。您可能会认为此方法的结果类型将是整数,但是严格来说,结果可能是整数或溢出异常。仅将整数结果视为方法的返回类型不能表示函数的完整范围。
从这个角度来看,并不是说被检查的异常没有被其他语言发现是不正确的。他们只是没有采用Java使用的形式。在Haskell应用程序中,通常使用代数数据类型来区分功能的成功完成与失败。尽管这本身并不是一个例外,但其意图与已检查的例外非常相似。它是一个旨在强制API使用者在不成功的情况下成功处理这两个问题的API,例如:
data Foo a =
Success a
| DidNotWorkBecauseOfA
| DidNotWorkBecauseOfB
这告诉程序员该函数除成功外还有两个其他可能的结果。
IMO,checked异常是一个有缺陷的实施什么可能是一个相当不错的主意(下完全不同的情况-但真的不是在目前的情况下,即使是一个好主意)。
好的主意是,让编译器验证是否可以捕获程序可能引发的所有异常会很好。有缺陷的实现是这样做的,Java迫使您直接在任何类调用声明为引发已检查异常的任何类中处理异常。异常处理的最基本的思想(和优点)之一是,在发生错误的情况下,它允许与特定错误无关的中间代码层完全忽略其本身的存在。受检查的异常(用Java实现)不允许这样做,因此破坏了从异常处理开始的主要优势(主要优势)。
从理论上讲,通过仅在整个程序范围内强制执行检查的异常,它们可以使检查异常有用-即,在“构建”程序的同时,构建程序中所有控制路径的树。树中的各个节点将使用1)可以生成的异常和2)可以捕获的异常进行注释。为了确保程序正确,您需要遍历树,验证在树中任何级别抛出的每个异常都在树中更高的级别被捕获。
他们之所以没有这样做(无论如何,我想是因为这样)非常困难-实际上,我怀疑有人编写了一个构建系统,该系统可以实现极简单的操作(对“玩具”进行边框处理) “)语言/系统。对于Java,诸如动态类加载之类的事情使它比许多其他情况更加困难。更糟糕的是,(至少与正常情况不同,)Java(至少在正常情况下)不是静态编译/链接到可执行文件的。这意味着没有在系统中真正具有全球意识,甚至尝试做这样的执法,直到最终用户真正开始加载程序来运行它。
由于以下几个原因,在那时执行执法是不切实际的。首先,即使充其量也可以延长启动时间,这已经是对Java的最大,最常见的投诉之一。其次,要做很多事情,您确实需要尽早发现问题,以便程序员加以解决。当用户加载的程序,这是来不及做多好-你在这一点上唯一的选择是忽视的问题,或拒绝加载/在所有运行程序,上关的机会,有可能是一个例外没有被抓住。
实际上,检查异常比没有用要糟,但是检查异常的替代设计甚至更糟。虽然checked异常的基本思想似乎是一个不错的一个,它真的不是很好,恰好是一个特别不合身的Java。对于像C ++这样的东西,通常可以将其编译/链接到一个完整的可执行文件中,而没有像反射/动态类加载这样的东西,您将有更多的机会(尽管坦率地说,即使正确执行它们,也不会造成混乱) Java中的当前原因可能仍然超出当前的技术水平)。
UnexpectedException
从派生的类型中RuntimeException
。请注意,“抛出”和“不期望”并不矛盾。如果foo
抛出BozException
并发出呼叫bar
,那并不意味着它期望bar
抛出BozException
。
它们确实非常适合烦人的程序员和鼓励吞咽异常。异常的一半是让您拥有处理错误的默认方法,而不必考虑显式传播无法处理的错误条件。(理智的默认设置是,如果您从不在代码中的任何位置处理错误,则可以有效地断言该错误不会发生,并且如果您错了,则会留下理智的退出和堆栈跟踪。)检查异常失败这个。他们感觉很像必须在C中显式传播错误。
throws FileNotFoundException
。硬性修正:catch (FileNotFoundException e) { throw RuntimeException(e); }
我认为可以说检查异常是一个失败的实验。他们出了错的地方是他们打破了层次:
关注点之间很好的分离被打破了,因为现在必须将错误的知识原本仅限于A和C散布在B上。
布鲁斯·埃克尔(Bruce Eckel)也有一篇不错的文章。这是一个报价:
堆积在程序员身上的随机规则越多,与解决当前问题无关的规则,程序员产生的速度就越慢。这似乎不是一个线性因素,而是一个指数因素
我相信对于C ++,如果所有方法都有throws
规范子句,那么您可以进行一些优化。但是我不相信Java会具有这些优势,因为RuntimeExceptions
未经检查。无论如何,现代的JIT应该能够优化代码。
AspectJ的一个不错的功能是异常软化:您可以将已检查的异常转换为未检查的异常,专门用于增强分层。
也许这是具有讽刺意味的Java通过这一切的麻烦,当它可能检查的去null
替代,这可能是更有价值的。
我喜欢例外。
但是,我经常被他们的误用所困扰。
不要将它们用于流处理。您不希望代码尝试执行某项操作并使用异常作为触发器来执行其他操作,因为异常是需要创建并使用内存等的对象,等等,只是因为您不必费心编写一些打电话之前先检查一下if()的种类。那只是懒惰,给光荣的Exception一个坏名字。
不要吞下它们。曾经 在任何情况下。绝对最低限度,您在日志文件中粘贴一些内容以指示发生了异常。试图通过吞噬异常的代码来跟踪自己的方式是一场噩梦。再次,诅咒您使异常强大的异常声名狼藉的异常破坏者!
戴上OO帽子,不要对待异常。创建具有有意义的层次结构的自己的Exception对象。并行处理您的业务逻辑和异常。我曾经发现,如果我是从系统的新领域开始的,那么我会发现需要在编码的半小时内将Exception子类化,所以如今,我开始编写Exception类。
如果您要编写“框架式”的代码,请确保编写了所有初始接口,以在适当的地方抛出自己的新异常...,并确保您的编码人员在其代码编写位置附近有一些示例代码引发IOException,但是他们正在写入的接口希望引发'ReportingApplicationException'。
一旦摸索了如何使Exceptions正常工作,您将永远不会回头。
initCause
或初始化引起异常的异常。示例:您有一个I / O实用程序,只有在写入失败时才需要一个异常。但是,有多种原因导致写操作可能失败(无法访问,写错误等)。将自定义异常与原因1一起抛出,以免原始问题被吞没。
受检查的异常也在ADA中。
(警告,此帖子包含您可能会遇到的坚定信念。)
程序员不喜欢他们并抱怨,也不编写吞咽异常的代码。
存在已检查的异常是因为事情不仅会失败,您还可以进行故障模式/效果分析并事先确定。
文件读取可能会失败。RPC调用可能会失败。网络IO可能会失败。解析时数据的格式可能不正确。
代码的“快乐之路”很容易。
我认识一个大学里的人,他可以编写出色的“幸福之路”代码。没有一个极端案例能奏效。如今,他为一家开源公司使用Python。纳夫说。
如果您不想处理检查的异常,那么您真正要说的是
While I'm writing this code, I don't want to consider obvious failure modes.
The User will just have to like the program crashing or doing weird things.
But that's okay with me because
I'm so much more important than the people who will have to use the software
in the real, messy, error-prone world.
After all, I write the code once, you use it all day long.
因此,程序员不会喜欢检查异常,因为这意味着更多工作。
当然,其他人可能希望完成这项工作。
即使文件服务器出现故障/ USB记忆棒死掉,他们也可能希望找到正确的答案。
在编程社区中,一个奇怪的信念是,当您的工作是编写软件时,您应该使用一种使生活更轻松,更享受的编程语言。您的工作是在解决某人的问题,而不是让您从事程序化的爵士即兴演奏。
如果您是一名业余程序员(不是为了钱而编程),请随时使用C#或其他语言进行编程,没有经过检查的异常。哎呀,剪掉中间人和徽标中的程序。您可以使用乌龟在地板上绘制漂亮的图案。
受检查的异常应该鼓励人们处理源头附近的错误。离源越远,在运行过程中终止的功能越多,并且系统进入不一致状态的可能性越大。此外,更有可能是您偶然捕获了错误的异常。
可悲的是,人们倾向于忽略异常或增加不断增加的抛出规范。这两个都错过了应该鼓励检查异常的意义。它们就像将过程代码转换为使用许多Getter和Setter函数(据说使其成为OO)一样。
本质上,检查异常正在尝试证明代码中的一定程度的正确性。它与静态类型几乎相同,它试图在代码运行之前证明某些事情是正确的。问题在于,所有这些额外的证明都需要付出努力,值得付出努力吗?