何时选择已检查和未检查的异常
在Java(或者任何其他具有检查的异常的语言)中,当创建自己的异常类时,如何决定应将其选中还是取消选中?
我的直觉是,在调用者能够以某种有效的方式进行恢复的情况下,将要求使用检查异常,而对于无法恢复的情况,非检查异常会更多,但是我会对其他人的想法感兴趣。
解决方案
回答
从Java学习者那里:
When an exception occurs, you have to either catch and handle the exception, or tell compiler that you can't handle it by declaring that your method throws that exception, then the code that uses your method will have to handle that exception (even it also may choose to declare that it throws the exception if it can't handle it). Compiler will check that we have done one of the two things (catch, or declare). So these are called Checked exceptions. But Errors, and Runtime Exceptions are not checked for by compiler (even though you can choose to catch, or declare, it is not required). So, these two are called Unchecked exceptions. Errors are used to represent those conditions which occur outside the application, such as crash of the system. Runtime exceptions are usually occur by fault in the application logic. You can't do anything in these situations. When runtime exception occur, you have to re-write your program code. So, these are not checked by compiler. These runtime exceptions will uncover in development, and testing period. Then we have to refactor our code to remove these errors.
回答
我使用的规则是:永远不要使用未经检查的异常! (或者当我们看不到周围的东西时)
从使用库的开发人员或者使用库/应用程序的最终用户的角度来看,遇到由于意外情况而崩溃的应用程序确实很糟糕。指望全面发展也没有好处。
这样,最终用户仍然可以看到错误消息,而不是应用程序完全消失。
回答
对于要向调用者提供信息的可恢复性情况(即权限不足,找不到文件等),可检查的异常很有用。
很少使用未经检查的异常(如果有的话)来在运行时通知用户或者程序员严重的错误或者意外情况。如果我们正在编写供他人使用的代码或者库,则不要抛出它们,因为他们可能不希望软件抛出未经检查的异常,因为编译器不会强制捕获或者声明它们。
回答
The rule I use is: never use unchecked exceptions! (or when you don't see any way around it)
相反的情况非常有力:永远不要使用检查的异常。我不愿意参加辩论,但似乎已经达成广泛共识,即事后看来,引入检查例外是一个错误的决定。请不要开枪打发使者并参考那些论点。
回答
这是我的"最终经验法则"。
我用:
- 我的方法代码中的未经检查的异常(由于调用程序导致的失败)(涉及显式且完整的文档)
- 由于被调用方而导致的失败检查异常,我需要向任何想要使用我的代码的人明确说明
与先前的答案相比,这是使用一种或者另一种(或者两者)例外的明确理由(一个人可以同意或者不同意)。
对于这两种异常,我都会为我的应用程序创建自己的未检查和检查后的异常(如此处所述,这是一种很好的做法),但非常常见的未检查异常(如NullPointerException)
因此,例如,下面这个特定功能的目标是制作(或者获取(如果已经存在的话)一个)对象,
意义:
- 使/获取的对象的容器必须存在(CALLER的责任=>未检查的异常,并为此调用函数清除javadoc注释)
- 其他参数不能为空(编码器可以选择将其放在CALLER上:编码器将不检查空参数,但编码器会记录文件)
- 结果不能为NULL(被调用方的责任和选择代码,对于调用方来说非常重要的选择=>已检查的异常,因为每个调用方都必须决定是否无法创建/找到对象,并且必须在编译时强制执行此决定:他们必须先使用此功能,而不必处理这种可能性,即带有此检查的异常)。
例子:
/** * Build a folder. <br /> * Folder located under a Parent Folder (either RootFolder or an existing Folder) * @param aFolderName name of folder * @param aPVob project vob containing folder (MUST NOT BE NULL) * @param aParent parent folder containing folder * (MUST NOT BE NULL, MUST BE IN THE SAME PVOB than aPvob) * @param aComment comment for folder (MUST NOT BE NULL) * @return a new folder or an existing one * @throws CCException if any problems occurs during folder creation * @throws AssertionFailedException if aParent is not in the same PVob * @throws NullPointerException if aPVob or aParent or aComment is null */ static public Folder makeOrGetFolder(final String aFoldername, final Folder aParent, final IPVob aPVob, final Comment aComment) throws CCException { Folder aFolderRes = null; if (aPVob.equals(aParent.getPVob() == false) { // UNCHECKED EXCEPTION because the caller failed to live up // to the documented entry criteria for this function Assert.isLegal(false, "parent Folder must be in the same PVob than " + aPVob); } final String ctcmd = "mkfolder " + aComment.getCommentOption() + " -in " + getPNameFromRepoObject(aParent) + " " + aPVob.getFullName(aFolderName); final Status st = getCleartool().executeCmd(ctcmd); if (st.status || StringUtils.strictContains(st.message,"already exists.")) { aFolderRes = Folder.getFolder(aFolderName, aPVob); } else { // CHECKED EXCEPTION because the callee failed to respect his contract throw new CCException.Error("Unable to make/get folder '" + aFolderName + "'"); } return aFolderRes; }
回答
在具有足够多层的足够大的系统上,已检查的异常是无用的,因为无论如何,我们都需要一种体系结构级策略来处理异常的处理方式(使用故障屏障)
使用已检查的异常,错误处理策略是微管理的,在任何大型系统上都是无法承受的。
大多数时候,我们不知道错误是否是"可恢复的",因为我们不知道API的调用者位于哪一层。
假设我创建了一个StringToInt API,该API将整数的字符串表示形式转换为Int。如果用" foo"字符串调用API,是否必须抛出一个检查过的异常?是否可以恢复?我不知道,因为在他的层中,我的StringToInt API的调用者可能已经验证了输入,并且如果抛出此异常,则它是错误或者数据损坏,并且在此层不可恢复。
在这种情况下,API的调用者不希望捕获异常。他只想让异常"冒泡"。如果我选择了一个已检查的异常,则此调用方将有很多无用的catch块,只能人为地抛出异常。
可恢复的内容大部分时间取决于API的调用者,而不取决于API的编写者。 API不应使用已检查的异常,因为只有未检查的异常才允许选择捕获或者忽略异常。
回答
这不仅仅是从异常中恢复的能力。在我看来,最重要的是调用方是否有兴趣捕获异常。
如果编写了要在其他地方或者应用程序中较低层使用的库,请问问自己调用者是否有兴趣捕获(了解)异常。如果他不是,请使用未经检查的异常,这样就不会给他带来不必要的负担。
这是许多框架使用的哲学。尤其是Spring和Hibernate,想到它们将已知的已检查异常转换为未检查异常的原因恰恰是因为Java中过度使用了已检查异常。我能想到的一个示例是json.org中的JSONException,这是一个已检查的异常,并且大多数情况下很烦人,应对其进行检查,但开发人员根本没有考虑过。
顺便说一句,在大多数情况下,呼叫者对异常的兴趣与从异常中恢复的能力直接相关,但并非总是如此。
回答
没错
未检查的异常用于使系统快速故障,这是一件好事。我们应该清楚地说明方法期望什么才能正常工作。这样,我们只能验证一次输入。
例如:
/** * @params operation - The operation to execute. * @throws IllegalArgumentException if the operation is "exit" */ public final void execute( String operation ) { if( "exit".equals(operation)){ throw new IllegalArgumentException("I told you not to..."); } this.operation = operation; ..... } private void secretCode(){ // we perform the operation. // at this point the opreation was validated already. // so we don't worry that operation is "exit" ..... }
仅举一个例子。关键是,如果系统快速失败,那么我们将知道它在何处以及为何失败。我们将得到如下的堆栈跟踪:
IllegalArgumentException: I told you not to use "exit" at some.package.AClass.execute(Aclass.java:5) at otherPackage.Otherlass.delegateTheWork(OtherClass.java:4569) ar ......
而且我们会知道发生了什么。 " delegateTheWork"方法(在第4569行)中的OtherClass使用" exit"值调用了类,即使它不应该如此,也是如此。
否则,我们将不得不在整个代码中进行验证,这很容易出错。另外,有时很难跟踪出了什么问题,并且我们可能会期望数小时令人沮丧的调试
NullPointerExceptions也发生同样的事情。如果我们有700行的类,其中包含15种方法,则使用30种属性,并且它们都不可以为null,我们可以将所有这些属性设置为只读并在构造函数中验证它们,而不用对每个方法中的null属性进行验证。工厂方法。
public static MyClass createInstane( Object data1, Object data2 /* etc */ ){ if( data1 == null ){ throw NullPointerException( "data1 cannot be null"); } } // the rest of the methods don't validate data1 anymore. public void method1(){ // don't worry, nothing is null .... } public void method2(){ // don't worry, nothing is null .... } public void method3(){ // don't worry, nothing is null .... }
受检查的异常当程序员(我们或者同事)正确执行所有操作,验证输入,运行测试且所有代码完美无缺,但代码连接到可能已关闭的第三方Web服务(或者文件)时,此功能非常有用我们正在使用的文件已被另一个外部进程等删除)。在尝试连接之前,甚至可以验证Web服务,但是在数据传输过程中出了点问题。
在这种情况下,我们或者同事将无能为力。但是,我们仍然必须做一些事情,而不是让应用程序死掉并消失在用户眼中。为此,我们可以使用已检查的异常并处理该异常,那该怎么办?在大多数情况下,只是为了尝试记录错误,可能会保存工作(应用工作)并向用户显示一条消息。 (网站blabla关闭,请稍后重试,等等。)
如果检查的异常被过度使用(通过在所有方法签名中添加" throw Exception"),则代码将变得非常脆弱,因为每个人都将忽略该异常(因为过于笼统),并且代码的质量将受到严重影响。妥协。
如果我们滥用未检查的异常,则会发生类似的情况。该代码的用户不知道是否可能出问题,将出现很多try {...} catch(Throwable t)。