Possible Duplicate:
When to choose checked and unchecked exceptions
我应该何时创建一个已检查的异常,何时应该生成运行时异常?
例如,假设我创建了以下类:
1 2 3 4 5 6 7 8 9 10 11
| public class Account {
private float balance;
/* ... constructor, getter, and other fields and methods */
public void transferTo(Account other, float amount) {
if (amount > balance)
throw new NotEnoughBalanceException();
/* ... */
}
} |
我该如何创建NotEnoughBalanceException? 它应该延伸Exception还是RuntimeException? 或者我应该只使用IllegalArgumentException?
-
真的很期待这里的答案。 我已经被充分剥夺了被剥夺了的例外情况,我不认为我的回答实际上会有用/有帮助,但我会很高兴看到别人的答案:)
-
嗨乔恩。 对不起,"被剥夺了权利"是什么意思?:)
-
这意味着乔恩正在休息让我们其他人赶上来。=)
-
谢谢@cletus,它没有出现在我的搜索中。 但是我会把问题留下来,因为我从不同的角度得到了更多的答案。
-
@Hosam:我的意思是让人失望。 被剥夺权利的是您失去投票权(或者当您被非法拒绝时)。
-
这真的是非法论据或任何形式的例外吗? 您在问题的两个方面都得到了一些好的答案,但您的具体示例看起来不像任何异常的候选者。
关于这个话题存在很多分歧。在我上一份工作中,我们遇到了一些实际问题,运行时异常被遗忘,直到它们出现在生产中(在agedwards.com上),因此我们决定仅使用已检查的异常。
在我目前的工作中,我发现在很多或所有情况下都有很多人会遇到运行时异常。
这就是我的想法:使用CheckedExceptions,我在编译时被迫至少在调用者中确认异常。对于运行时异常,我不会被编译器强迫,但可以编写一个单元测试,让我处理它。因为我仍然认为越早发现一个bug就越便宜,我就更喜欢CheckedExceptions。
从哲学的角度来看,方法调用是调用者和被调用者之间某种程度的契约。由于编译器强制执行传入的参数类型,因此它似乎是对称的,以便在出路时强制执行类型。也就是说,返回值或异常。
我的经验告诉我,当我使用经过检查的异常时,我获得了更高的质量,也就是代码。检查异常可能会使代码混乱,但有一些技术可以解决这个问题。我喜欢在传递图层边界时翻译异常。例如,如果我从持久层中传出,我想将SQL异常转换为持久性异常,因为下一层不应该关心我是否持久化到SQL数据库,但是想要知道是否有东西不能坚持下去。我使用的另一种技术是创建一个简单的异常层次结构。这让我可以在一层编写更清晰的代码,因为我可以捕获超类,并且只在真正重要时处理各个子类。
-
Don Branson写道:"我的经验告诉我,当我使用经过检查的异常时,我会获得更高的质量,也就是代码。" - 不确定通过简单地使用已检查的异常并最终使用大量空/重新捕获块来代码污染......可能会导致更高质量的代码。我会说更多单元测试会产生更高质量的代码 - 而不是选择异常类型!
-
"结束你的代码污染与大量的空/重新捕获块" - 如果你看到这一点,他们没有被正确使用。你可以采取任何技术,使用错误,并提出批评。
-
就单元测试而言,我同意代码应该进行单元测试。 TDD,实际上。但要记住费用的连续性。同样的连续性使得单元测试中的错误检测比生产中更便宜,使得编译器检测到的错误比UT捕获的错误便宜。
总的来说,我认为Joshua Bloch在Effective Java中的建议最好地总结了您的问题的答案:使用已检查的可恢复条件和编程错误的运行时异常(第2版中的第58项)。
所以在这种情况下,如果你真的想要使用异常,它应该是一个经过检查的异常。 (除非transferTo()的文档清楚地表明,如果不首先使用其他Account方法检查足够的平衡,就不能调用该方法 - 但这看起来有点尴尬。)
但另请注意项目59:避免不必要地使用已检查的例外和57:仅在例外条件下使用例外。正如其他人所指出的那样,这个案件可能根本不需要例外。如果没有足够的信用,请考虑返回false(或者可能是状态对象,其中包含有关所发生情况的详细信息)。
-
我认为在Java中应该没有返回boolean的方法来指示成功或失败。机会太高,调用者可能忘记检查返回的值。在这种情况下抛出异常似乎更合适,然后此异常可能会带有您建议的状态对象。
-
@Roland:公平点;没有强烈的意见,所以我不会争论。 :-)我主要说的是,如果在这里抛出一些"NotEnoughBalanceException",应该按照我引用的经验法则检查它。
-
@Roland:我不同意你的一揽子断言,但不同意你的原则。 :-)这与Jonik的Bloch引用没有什么不同:在异常条件下使用例外。如果该方法在正常操作中可能合理地"失败"(例如Collection.add),则成功返回是合理的。如果失败是由于意外编码(例如保存到文件)而不是异常是合适的。糟糕的开发人员是否可以自己投篮,这是一个微不足道的问题,因为他们会找到一种方法。
-
不强迫开发人员检查大多数调用的返回值是为什么首先引入异常...
-
@Basic但是,异常应该是例外 - 它们不应该在大多数调用中出现,只需简单的流量控制。因此,虽然异常是有价值的,但它们不会也不应该取代指示某事成功或失败的返回值,如果失败并不意味着应该抛出异常。如果可以成功处理或减轻故障,以便程序执行可以继续,并且这是正常的预期发生,那么该故障不是例外,并且不值得抛出异常,并且它应该是返回值。
-
@ErikE我很清楚这一事实:stackoverflow.com/a/4799779/156755
-
@Basic好的。在我看来,无论任何人的个人哲学或知识是什么,我的评论都会在这个答案评论中平衡一些事情。
何时使用已检查的例外?说实话?以我的拙见......从不。我认为自从我上次创建一个检查异常以来已经过去了大约6年。
你不能强迫某人处理错误。可以说,它使代码变得更糟,而不是更好。我不能告诉你我遇到这样的代码的次数:
虽然我有无数次编写这样的代码:
1 2 3 4 5
| try {
...
} catch (IOException e ) {
throw new RuntimeExceptione (e );
} |
为什么?因为一个条件(不一定是IOException;这只是一个例子)是不可恢复的,但是无论如何都被迫放下我的喉咙而且我经常被迫在执行上述操作和污染我的API之间做出选择,只是为了传播一个检查过的异常到顶部它(正确地)致命并将被记录。
Spring的DAO帮助程序类将检查的SQLException转换为未经检查的DataAccessException是有原因的。
如果您对磁盘缺少写入权限,缺少磁盘空间或其他致命条件,您希望尽可能多地产生噪声,并且执行此操作的方法是使用...未经检查的异常(甚至是错误)。
此外,已检查的异常会破坏封装。
检查异常的这个想法应该被用于"可恢复的"错误,这实际上是一种天空般的一厢情愿的想法。
Java中检查的异常是一个实验......一个失败的实验。我们应该减少损失,承认我们犯了错误并继续前进。恕我直言.Net通过仅具有未经检查的异常使其正确。然后,它再次获得了从Java的错误中学习的第二个优势。
-
我同意这一点;我已经在广泛的代码库上使用了运行时异常(大约2000个类在库和应用程序之间平均分配),并且在生产中没有未处理的异常时遇到实际问题。
-
Cletus:+1给出了一个很好的答案,但我认为ckwop的链接文章是完全废话 - 我的建议是删除它。认真。为了天堂,Simon(ckwop)建议我们回到返回码所指示的错误!
-
未经检查的异常会使您的抽象泄漏...如果您拥有它们。如果您的设计相对不稳定,并且需要更具体的代码,那么未经检查的异常就可以了。在系统的稳定的抽象层中,经过检查的异常保留了抽象。
-
未经检查的异常与检查的异常相比没有或多或少泄漏。声明代码抛出IOException或SQLException会说明您的实现,因此您可以拥有一个特殊的已检查异常,但您也可以拥有一个特殊的未经检查的异常。这没什么不同。
-
如果服务层声明UserAlreadyExistsException,则客户端可以捕获并恢复。如果数据访问层只是让未经检查的Hibernate ConstraintViolationExceptions冒泡,那么任何想要从该特定问题中恢复的调用者都会对特定实现具有编译时依赖性。经过检查的异常会鼓励实现抽象,将低级异常重写为适合抽象的形式,从而防止实现泄漏。
-
UserAlreadyExistsException也可以是未经检查的异常。客户端仍然可以捕获它。唯一的区别是,当他们可能没有兴趣去抓它时,他们不会被迫处理它。
-
cletus - 有关"例外打破封装"链接的有趣之处在于,如果使用已检查的异常,他的大多数异议(包括同名异议)都会失效。如果检查异常抛出是不可见的。如果方法声明它将其异常作为其合同的一部分抛出,则异常不会破坏封装。如果开发人员无法被信任正确处理已检查的异常,那么在编译器的帮助下,为什么他们会被要求正确处理未经检查的异常?
-
从根本上说,已检查异常和未经检查的异常之间的唯一区别是编译器强制您记录已检查的异常。您应该记录代码可以抛出的任何异常;我不明白为什么有人会反对这种静态可检查,以防止它陈旧或任何例外被遗漏。无论是否检查,例外都是您的API的一部分;检查只是确保您准确地声明您的API。
-
Andrzej Doyle写道:"无论是否经过检查,异常都是您的API的一部分;检查只是确保您准确地声明您的API。" - 不是这样......如果检查异常声明编译器也强制捕获该异常,无论你是否知道如何处理它!这是最大的不同!它将使用大量空/重新捕获块来污染您的代码......
-
@ user1697575,与其他所有交易一样,需要权衡利弊。让编译器执行检查的优点意味着无法意外泄漏异常(并且人类确实会犯错误)。如果调用代码不处理异常,则必须明确泄漏它。这与我们从松散类型和强类型语言中得到的完全相同。
-
@AndrzejDoyle检查异常使得无法为测试获得100%的代码覆盖率,当你想要异常冒泡时强制无意义的try / catch块,并且通常会增加噪点/杂乱的价值。现在,如果您可以在签名中声明异常但不强制它们被处理,那将是理想的,因为它会记录但不会污染代码
我最近遇到了异常问题,代码抛出了NullPointerException,我不知道为什么,经过一些调查后发现真正的异常被吞噬了(它是在新代码中,所以它仍在完成)并且方法只返回n??ull。如果你检查了异常,你必须明白坏程序员只会尝试捕获它并忽略异常。
恕我直言,它应该不是一个例外。在我看来,应该在特殊情况发生时使用异常,而不是作为流控制。
在你的情况下,有人试图转移比余额允许更多的钱,这并不是一个特殊的状态。我认为这些事情在现实世界中经常发生。所以你应该针对这些情况进行编程。一个例外可能是你的if语句评估余额是好的,但是当实际从账户中扣除钱时,由于一些奇怪的原因,余额不再好了。
例外情况可能是,在调用transferTo()之前,您检查了该行是否已向银行开放。但是在transferTo()中,代码注意到该行不再打开,但是,按照所有逻辑,它应该是。这是一个例外。如果线路无法打开,那也不例外,这是一个看似合理的情况。
恕我直言回顾:例外==奇怪的黑魔法。
是建设性编辑:
所以,不要太矛盾,方法本身可能会抛出异常。但是应该控制该方法的使用:首先检查余额(在transferTo()方法之外),如果余额良好,则只调用transferTo()。如果transferTo()注意到余额,由于某些奇怪的原因,不再是好的,你抛出了你努力捕获的异常。
在这种情况下,你有你的所有鸭子,并且知道你无能为力(因为什么是true变成false,就像它本身一样),除了记录异常,发送通知给有人,礼貌地告诉顾客,有人在上一次满月期间没有正确地牺牲他们的处女,问题将在第一个可能的时刻得到解决。
不太enterprisey暗示编辑:
如果你这样做是为了你自己的乐趣(并且情况似乎是这样,请参阅注释),我建议返回一个布尔值。用法是这样的:
1 2 3 4 5 6 7 8 9 10
| // ...
boolean success = transferTo(otherAccount, ONE_MILLION_DOLLARS_EXCLAMATION);
if (!success) {
UI.showMessage("Aww shucks. You're not that rich");
return; // or something...
} else {
profit();
}
// ... |
-
谢谢,但是你比我想的更认真。尽管如此,信息量很大,但它并没有真正解决我的问题。这是一个非常简单的课程,我正在为个人预算跟踪应用程序做,而且它更像是一个学习项目,而不是一个成熟的应用程序。
-
但是除了例外之外,你建议使用什么?假设帐户不允许负余额,当参数导致这种情况时我该怎么办?
-
除了例外,您只有错误代码。但是异常为您提供了可以构造和返回的完整对象。
-
非常感谢@wolfie。我最初让我的方法返回类Transaction的实例(虽然我没有在我的示例中包含它)。你会建议检查返回值是否为空?我认为,从账户角度来看,这是一个相当非法的论点。我感谢您的帮助。
-
@Hosam:如果方法返回一个对象,是的,我会使用返回的null作为错误状态的指示符,而不是抛出异常。
-
检查异常是因竞争条件而被告知错误的最佳方式,例如在您检查它们之间以及实际尝试转移它们之间捕获资金的另一个交易。在并发系统中进行"预检"确实没有意义。
从未经检查的例外 - 争议:
If a client can reasonably be expected
to recover from an exception, make it
a checked exception. If a client
cannot do anything to recover from the
exception, make it an unchecked
exception.
请注意,未经检查的异常是从RuntimeException派生的异常,而已检查的异常是从Exception派生的异常。
如果客户端无法执行任何操作以从异常中恢复,为什么抛出RuntimeException?文章解释说:
Runtime exceptions represent problems
that are the result of a programming
problem, and as such, the API client
code cannot reasonably be expected to
recover from them or to handle them in
any way. Such problems include
arithmetic exceptions, such as
dividing by zero; pointer exceptions,
such as trying to access an object
through a null reference; and indexing
exceptions, such as attempting to
access an array element through an
index that is too large or too small.
-
不幸的是,在许多情况下,一些客户将被期望从异常中恢复,但许多客户不会。此外,在许多情况下,抛出异常的方法的可能性将取决于传递给它的参数。对于在许多用例中永远不会抛出的东西使用checked异常会强制调用者添加代码,这些代码只有在出现严重错误以至于无法进行有意义的恢复的情况下才能执行。
-
try {URL u = new URL("http://example.com/");} catch (MalformedURLException e) { /*Can _never_ execute as the url is static but we have to handle it anyway*/ }。这可以防止测试中代码覆盖率达到100%,并且可以完全消除无意义的混乱。 URL无法知道它将如何被使用,因此实现会假设某些时候一定会出错。这怎么一个好主意?
我的规则是
-
if语句的业务逻辑错误(如代码)
-
应用程序可以恢复的环境错误的异常
-
没有恢复的环境错误的未经检查的异常
-
已检查例外的示例:对于可脱机工作的应用程序,网络已关闭
-
未经检查的异常的示例:数据库在CRUD Web应用程序上关闭。
有很多关于这个主题的文档。你可以通过浏览Hibernate找到很多东西
网页,因为他们在版本3.x中将Hibernate 2.x的所有异常从已检查更改为未检查
-
可恢复性是呼叫者的"眼睛";即,实施者没有业务假设客户能够或不能从中恢复。惯例是编程错误导致运行时异常,而其余的则被检查。
-
大会根据谁?
-
公约是一种普遍遵循的惯例,没有权威的授权,事实上的标准而不是标准的公约。但如果你正在寻找持有这种观点的权威人士,那么James Gosling和Josh Bloch就是其中之一。
-
我认为你混淆了两件事。库中的已检查/未检查的异常(其中可恢复性确实在调用者眼中)以及我的内部代码中已检查/未检查的异常,其中我既是生产者又是消费者。
我的感觉是,经过检查的例外是一个有用的合同,应该谨慎使用。我认为检查异常是一个好主意的经典示例是InterruptedException。我的感觉是,我希望能够在我希望它停止时停止线程/进程,无论有人指定Thread.sleep()多长时间。
所以,试图回答你的具体问题,这是你绝对想要确保每个人都处理的问题吗?在我看来,Account没有足够的钱的情况是一个严重的问题,你必须处理它。
回应Peter的评论:这是一个使用InterruptedException作为应该处理的异常的具体情况的例子,你需要有一个有用的默认处理程序。这是我强烈推荐的,当然是在我的实际工作中。你应该至少这样做:
该处理程序将确保代码捕获已检查的异常并完全按照您的要求执行:使此线程停止。不可否认,如果上游还有另一个异常处理程序/食者,那么它将不太可能处理异常。即便如此,FindBugs可以帮助您找到这些。
现在,现实开始了:你不一定强迫每个为你的检查异常编写异常处理程序的人都能很好地处理它。也就是说,至少你可以"查找用法"并知道它的使用位置并给出一些建议。
简短形式:如果使用已检查的异常,则会对方法的用户造成负担。确保有充分的理由,建议正确的处理方法并广泛记录所有这些。
-
不幸的是,很多人不理解InterruptedException :-(我也相信它应该被检查,但很多人只是通过空catch块忽略它。
-
就个人而言,我认为InterruptedException是一个很好的例子,说明应该使用unchecked。
-
怎么可能更好?如果你在没有超时的情况下处于get()之类的中间并且你收到InterruptedException,那么你应该打断该线程。已检查的异常是一个尖锐的提示,这是一个必须处理的特殊且重要的案例。
我不认为情景(资金不足)保证抛出Exception ---它根本不够特别,应该由程序的正常控制流来处理。但是,如果我真的不得不抛出异常,我会选择一个已检查的异常,通过扩展Exception,而不是RuntimeException,这是未选中的。这迫使我明确地处理异常(我需要声明它被抛出,或者在某处捕获它)。
IllegalArgumentException是RuntimeException的子类,使其成为未经检查的异常。如果调用者有一些方便的方法来确定方法参数是否合法,我只会考虑抛出这个。在您的特定代码中,不清楚调用者是否可以访问balance,或者整个"检查余额和转移资金"是否是原子操作(如果不是,那么调用者真的没有方便的方法来验证参数)。
编辑:澄清我投掷IllegalArgumentException的立场。
-
谢谢@Zach。我编辑了我的问题,以澄清该类提供了一个getter来检索值。所以你的建议是抛出IllegalArgumentException?或者你会建议创建我自己的未经检查的异常?
-
你怎么处理竞争条件?
-
@erickson:我不明白这个问题。你能详细说明吗?
-
@Hosam:我不建议抛出IllegalArgumentException,因为它是运行时未经检查的异常。对于这种情况,检查的异常(例如自定义异常)更合适,因为您必须显式处理它。更好的是,根本不要使用例外。
-
我想你已经通过关于原子性的编辑解决了我的问题。由于比赛,有些情况下调用者无法在请求操作之前检查操作是否成功。例如,我可以测试文件是否存在,但是在我打开文件之前,另一个进程可以删除它。
检查异常的优点是编译器强制开发人员更早地处理它们。在我看来,缺点是开发人员倾向于懒惰和不耐烦,并且存在异常处理代码,以便稍后返回并修复它。当然,这很少发生。
Thinking in Java的作者Bruce Eckel在这个主题上有一篇很好的文章。
-
try {URL u = new URL("http://example.com/");} catch (MalformedURLException e) { /*Utterly pointless catch block we're forced to create*/ }
简单地说,仅将检查异常用作库的外部合同的一部分,并且仅在客户希望/需要捕获它时使用。请记住,使用已检查的异常时,您会强制自己在呼叫者身上。对于运行时异常,如果它们有详细记录,则可以为调用者提供选择。
已知问题是检查异常在Java中被过度使用,但这并不意味着它们都是坏的。这就是为什么它是Spring哲学中不可或缺的一部分,例如(http://www.springsource.org/about)
行并不总是清楚,但对我来说通常是RuntimeException =编程错误,检查异常=外部错误。这是非常粗略的分类。像其他人说的那样,经过检查的例外情况会迫使你处理,或者至少只考虑一小部分时间。
已检查的异常意味着您的类的客户端被编译器强制处理它。除非他们添加try / catch块,否则他们的代码无法编译。
C#的设计者已经决定优先选择未经检查的异常。
或者,您可以遵循C样式并检查返回值,而不是抛出异常。
如前所述,例外确实有成本,因此它们不应用于控制流程。但他们为他们做的一件事是他们不能被忽视。
如果您决定在这种情况下避开异常,则会冒着类的客户端忽略返回值或在尝试传输之前无法检查余额的风险。
我建议一个未经检查的异常,我会给它一个像InsufficientFundsException这样的描述性名称,以便清楚地知道发生了什么。
-
您不必添加try-catch。如果调用者无法处理异常,则应该简单地声明它并且什么都不做。
-
有人必须处理它的某个地方。说你做不到,并把责任推到了一线,也在做出决定。你强迫他们承认需要处理和选择。
我自己,我更喜欢使用经过检查的例外情况。
如果您是API开发人员(后端开发人员),请使用已检查的异常,否则,请使用运行时异常。
另请注意,在某些情况下使用运行时异常会被视为一个大错误,例如,如果要从会话bean中抛出运行时异常(请参阅:http://m-hewedy.blogspot.com/2010/01/ avoid-throwing-runtimeexception-from.html,了解有关在会话bean中使用运行时激活的问题的更多信息)。