在Java中,什么时候应该创build一个检查的exception,什么时候应该是一个运行时exception?
可能重复:
何时select已选中和未选中的例外
什么时候应该创build一个检查的exception,什么时候应该创build一个运行时exception?
例如,假设我创build了以下类:
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(); /* ... */ } }
我应该如何创build我的NotEnoughBalanceException
? 是否应该扩展Exception
或RuntimeException
? 还是应该使用IllegalArgumentException
呢?
这个话题有很多分歧。 在我上一份工作中,我们遇到了一些运行时exception被遗忘的真正问题,直到它们出现在生产环境中(在agewards.com上),所以我们决定专门使用检查的exception。
在我目前的工作中,我发现在很多情况下或者所有情况下都有很多运行时exception。
这是我的想法:使用CheckedExceptions,我被迫在编译时至less承认在调用者的exception。 有了运行时exception,我不会被编译器强制执行,但可以编写一个unit testing来处理它。 由于我仍然相信越早发现一个错误,修复它就越便宜,所以我更喜欢CheckedExceptions。
从哲学的angular度来看,方法调用是主叫方和被叫方之间在某种程度上的契约。 由于编译器强制传入的参数types,似乎是对称的,因此可以在出口时强制types。 也就是返回值或例外。
我的经验告诉我,当我使用检查的exception时,我得到更高的质量,也就是只有正常工作的代码。 检查exception可能会混淆代码,但是有技巧来处理这个问题。 我喜欢在传递层边界时转换exception。 例如,如果我从持久层传递,我想将SQLexception转换为持久性exception,因为下一层不应该在意我坚持SQL数据库,但会想要知道是否有东西不能坚持。 我使用的另一种技术是创build一个简单的exception层次结构。 这让我可以把更简洁的代码编写成一层,因为我可以捕获超类,只有在真正重要时才处理个别的子类。
一般来说,我认为Joshua Bloch在Effective Java中的build议最好总结一下你的问题的答案: 对可恢复的条件使用checked checked,对编程错误使用runtime exception (第二版中的Item 58)。
所以在这种情况下,如果你真的想使用exception,应该是一个检查。 (除非transferTo()
的文档清楚地表明,如果不使用其他Account
方法检查足够的余额, 就不能调用该方法 – 但这看起来有些尴尬。)
但是还要注意项目59: 避免不必要地使用检查的exception和57: 仅在特殊情况下使用exception 。 正如其他人所指出的那样,这个案子根本就不是一个例外。 如果没有足够的信用,请考虑返回false
(或者也许是一个关于发生了什么的细节的状态对象)。
何时使用检查的exception? 说实话? 在我的愚见中,从来没有。 我认为自从我上次创build一个检查的例外已经过去了大约6年。
你不能强迫某人处理错误。 可以说它使代码变得更糟,而不是更好。 我不能告诉你我遇到这样的代码的次数:
try { ... } catch (IOException e) { // do nothing }
而我有无数次这样写的代码:
try { ... } catch (IOException e) { throw new RuntimeExceptione(e); }
为什么? 因为一个条件(不一定是IOException;这仅仅是一个例子)是不可恢复的,但无论如何我被迫下来,我经常被迫做出上述和污染我的API之间的select,只是传播一个检查的exception,一路(正确地)致命,并将被logging。
有一个原因,Spring的DAO帮助程序类将选中的SQLException转换为未经检查的DataAccessException。
如果你对磁盘缺乏写权限,缺less磁盘空间或其他致命的条件,你想尽可能多的噪音,做到这一点的方式是…未经检查的exception(甚至错误)。
另外, 检查exception会破坏封装 。
这个检查exception的想法应该被用于“可恢复的”错误,真是天才的一厢情愿的想法。
在Java中检查exception是一个实验…一个失败的实验。 我们应该减less亏损,承认我们犯了一个错误,继续前进。 恕我直言,.Net通过只有未经检查的例外得到了正确的。 再次,它有Java的错误学习的第二个采用者的优势。
恕我直言,它不应该是一个例外。 在我看来,例外事件发生时应该使用例外情况,而不是stream量控制。
在你的情况下,有人试图转移更多的钱比余额允许的并不是一个特殊的地位。 我认为这些事情经常发生在现实世界中。 所以你应该针对这些情况进行编程。 一个例外可能是你的if语句评估的是好的结余,但是当钱真的被从帐户中扣除时,由于一些奇怪的原因,余额不再好。
在调用transferTo()
之前,你可能会遇到一个例外情况,那就是检查该行是否对银行开放。 但是在transferTo()
,代码注意到该行不再是开放的,尽pipe通过所有的逻辑,它应该是。 这是一个例外。 如果线路不能打开,那不是一个例外,这是可能的情况。
恕我直言回顾:例外==奇怪的黑魔法。
是build设性编辑:
所以,不要太矛盾,这个方法本身很可能会抛出exception。 但是应该控制这个方法的使用:你首先检查balance( transferTo()
方法之外)的余额,如果余额好,只有调用transferTo()
。 如果transferTo()
注意到由于某种奇怪的原因,余额不再好,你抛出你认真抓住的exception。
在这种情况下,你将所有的鸭子连在一起,并且知道没有什么可以做的了(因为true
变成了false
,就像它本身一样),除了loggingexception,发送通知给某个人,告诉客户礼貌地表示有人在最后的满月期间没有妥善处理自己的处女,问题将在第一时间得到解决。
不太enterprisey暗示编辑:
如果你是为了自己的喜好而做这个事情的(这个案例看起来像是这样,请参阅注释),我build议你返回一个布尔值。 用法是这样的:
// ... boolean success = transferTo(otherAccount, ONE_MILLION_DOLLARS_EXCLAMATION); if (!success) { UI.showMessage("Aww shucks. You're not that rich"); return; // or something... } else { profit(); } // ...
我最近有一个exception的问题,代码抛出了NullPointerException,我不知道为什么,经过一番调查,事实certificate,真正的exception吞噬(这是在新的代码,所以它仍然在做),方法只是返回null。 如果你检查exception,你必须明白,坏的程序员只会试着去捕捉exception。
我的规则是
- 如果声明业务逻辑错误(如你的代码)
- 在应用程序可以恢复的环境错误中引发exception
-
对于没有恢复的环境错误来说,这是一个非常规的例外
- 检查的exception示例:可以脱机工作的应用程序的networking已closures
- 未经处理的exception示例:数据库在CRUD Web应用程序中closures。
关于这个问题有很多文件。 你可以通过浏览Hibernate网页find很多,因为他们在版本3.x中将Hibernate 2.x的所有exception从checked变为unchecked
从未经检查的例外 – 争议 :
如果客户可以合理预期从exception中恢复,请将其作为检查exception。 如果客户端无法做任何事情来恢复exception,使其成为一个unchecked例外。
请注意,未经检查的exception是从RuntimeException
派生的Exception
并且检查的exception是从Exception
派生的Exception
。
为什么抛出一个RuntimeException
如果客户端不能做任何事情来恢复exception? 文章解释说:
运行时exception表示由于编程问题而导致的问题,因此API客户端代码不能合理地从它们中恢复或以任何方式处理它们。 这样的问题包括算术例外,例如除以零; 指针exception,比如试图通过空引用访问一个对象; 和索引exception,比如试图通过太大或太小的索引访问数组元素。
我的感觉是,检查的例外是一个有用的合同,应该谨慎使用。 我认为检查exception的经典例子是一个好主意,是一个InterruptedException 。 我的感觉是,我希望能够停止一个线程/进程,当我想要停止,无论多长时间有人指定Thread.sleep()。
所以,试图回答你的具体问题,这是你绝对要确保每个人都处理的东西吗? 在我看来,一个Account
没有足够的钱的情况是一个严重的问题,你必须处理它。
为了回应Peter的评论:下面是一个使用InterruptedException作为应该被处理的exception的具体例子的例子,你需要一个有用的默认处理程序。 这是我强烈build议,当然在我的真正的工作。 你至less应该这样做:
catch (InterruptedException ie) { Thread.currentThread().interrupt(); }
该处理程序将确保代码捕获检查的exception,并确实做到了你想要的:让这个线程停止。 不可否认的是,如果上游还有另一个exception处理程序/处理程序,那么处理exception情况就不太好。 即使如此, FindBugs可以帮助你find那些。
现在,现实是:你不一定会强制每个为你的exception处理写exception处理的人来处理它。 这就是说,至less你可以“查找使用情况”,并知道它在哪里使用,并给出一些build议。
简短forms:如果使用检查的exception,则正在为方法的用户造成负载。 确保有一个很好的理由,build议一个正确的处理方法,并广泛logging所有这一切。
检查的exception意味着你的类的客户端被编译器强制处理。 他们的代码不能编译,除非他们添加一个try / catch块。
C#的devise者已经决定,非检查的例外是首选。
或者你可以按照C风格和检查返回值,而不是抛出exception。
例外是有成本的,所以它们不应该用于控制stream程,如前所述。 但是他们为他们做的一件事就是他们不能被忽视。
如果您决定在这种情况下避免例外情况,那么您将冒着在您尝试转移之前,您的class级的客户将忽略返回值或未能检查余额的风险。
我build议一个未经检查的exception,我会给它一个像InsufficientFundsException这样的描述性名称,以便清楚地说明发生了什么。
行不总是清楚,但对我来说,通常运行时exception=编程错误,检查exception=外部错误。 虽然这是非常粗糙的分类。 和其他人一样,检查exception会迫使你处理,或者至less想一小会儿。
简而言之,只有在客户需要/需要捕获的情况下,才将检查的exception作为库的外部契约的一部分使用。 请记住,当使用检查的exception时,您正在强制调用者。 有了运行时exception,如果他们有充分的logging,您可以给调用者一个select。
在Java中检查exception被过度使用是一个已知的问题,但这并不意味着它们都是不好的。 这就是为什么它是Spring哲学的组成部分,例如( http://www.springsource.org/about )
检查exception的好处是,编译器会迫使开发人员尽早处理它们。 在我看来,缺点是开发人员往往懒惰,不耐烦,把意外返回并修复的exception处理代码编出来。 当然,这很less发生。
Java中的Thinking的作者Bruce Eckel在这个话题上写了一篇很好的文章 。
我不认为这种情况(资金不足)有必要抛出一个Exception
—这个Exception
不足够,应该由程序的正常控制stream程来处理。 但是,如果我真的不得不抛出一个exception,我会select一个检查exception ,通过扩展Exception
,而不是未经检查的RuntimeException
。 这迫使我明确地处理exception(我需要声明它被抛出,或者在某处捕获)。
IllegalArgumentException
是RuntimeException
一个子类,这使得它成为一个未经检查的exception。 如果调用者有一些方便的方法来判断方法参数是否合法,我只会考虑抛出这个问题。 在您的特定代码中,调用者是否有权获取balance
还不清楚,或者整个“检查余额和转账资金”是否是一个primefaces操作(如果不是这样,那么调用者确实没有方便的方法来validation参数) 。
编辑:澄清我的立场抛出IllegalArgumentException
。
我自己,我更喜欢使用检查的例外。
如果您是API开发人员(后端开发人员),则使用检查的exception,否则使用运行时exception。
还要注意的是,在某些情况下使用运行时exception将被视为一个很大的错误,例如,如果要从会话bean中抛出运行时exception(请参阅: http : //m-hewedy.blogspot.com/2010/01/ avoid-throwing-runtimeexception-from.html以获取更多关于在会话bean中使用运行时错误的信息)。