在Java(或任何其他具有已检查异常的语言)中,在创建自己的异常类时,如何确定是应该选中还是取消选中它?
我的直觉是,如果调用者能够以某种富有成效的方式恢复,那么将调用一个已检查的异常,其中未经检查的异常对于不可恢复的情况更多,但我会对其他人的想法感兴趣.
只要您了解应该使用它们,检查的例外情况就很好.Java核心API无法遵循SQLException的这些规则(有时候对于IOException),这就是它们如此糟糕的原因.
Checked Exceptions应该用于可预测但不可避免的错误,这些错误可以从中恢复.
未经检查的例外应该用于其他一切.
我会为你打破这个,因为大多数人误解了这意味着什么.
可预测但不可预防:调用者尽其所能来验证输入参数,但是他们控制之外的某些条件导致操作失败.例如,您尝试读取文件,但有人在您检查文件是否存在以及读取操作开始的时间之间删除它.通过声明已检查的异常,您告诉调用者预测此失败.
合理的恢复:没有必要告诉呼叫者预测他们无法恢复的异常.如果用户尝试从不存在的文件中读取,则调用者可以提示他们输入新文件名.另一方面,如果方法由于编程错误(无效的方法参数或错误的方法实现)而失败,那么应用程序无法在执行中期解决问题.它能做的最好的事情是记录问题并等待开发人员稍后修复它.
除非您抛出的异常符合上述所有条件,否则应使用未经检查的异常.
在每个级别重新评估:有时捕获已检查异常的方法不是处理错误的正确位置.在这种情况下,请考虑对您自己的呼叫者来说什么是合理的.如果异常是可预测的,不可避免且合理的,他们可以从那时恢复,你应该自己抛出一个检查异常.如果不是,则应将异常包装在未经检查的异常中.如果您遵循此规则,您将发现自己将已检查的异常转换为未经检查的异常,反之亦然,具体取决于您所在的层.
对于已检查和未检查的异常,请使用正确的抽象级别.例如,具有两个不同实现(数据库和文件系统)的代码存储库应该避免通过throw SQLException
或者公开特定于实现的细节IOException
.相反,它应该将异常包装在跨越所有实现的抽象中(例如RepositoryException
).
来自Java学习者:
当发生异常时,您必须捕获并处理异常,或通过声明您的方法抛出该异常告诉编译器您无法处理它,那么使用您的方法的代码将必须处理该异常(即使它也可以选择声明它抛出异常,如果它无法处理它).
编译器将检查我们是否完成了两件事之一(catch或declare).所以这些被称为Checked例外.但编译器不会检查错误和运行时异常(即使您可以选择捕获,也可以声明,它不是必需的).所以,这两个被称为未经检查的异常.
错误用于表示在应用程序之外发生的那些条件,例如系统崩溃.运行时异常通常由应用程序逻辑中的错误引起.在这些情况下你不能做任何事情.发生运行时异常时,您必须重新编写程序代码.因此,编译器不会检查这些内容.这些运行时异常将在开发和测试期间发现.然后我们必须重构我们的代码以消除这些错误.
我使用的规则是:永远不要使用未经检查的异常!(或当你看不到任何方式时)
相反的情况非常强烈:绝不使用已检查的异常.我不愿在辩论中偏袒任何一方,但似乎有一个广泛的共识,即在后见之明引入经过检查的例外是一个错误的决定.请不要拍摄信使并参考这些 论点.
在任何足够大的系统上,有很多层,检查异常都是无用的,无论如何,你需要一个架构级策略来处理如何处理异常(使用故障屏障)
使用已检查的异常,您的错误处理策略是微管理的,并且在任何大型系统上都无法忍受.
大多数情况下,您不知道错误是否"可恢复",因为您不知道API的调用者位于哪个层中.
假设我创建了一个StringToInt API,它将整数的字符串表示形式转换为Int.如果使用"foo"字符串调用API,我必须抛出一个已检查的异常吗?它可以恢复吗?我不知道,因为在他的图层中,我的StringToInt API的调用者可能已经验证了输入,并且如果抛出此异常,则可能是错误或数据损坏,并且此层无法恢复.
在这种情况下,API的调用者不希望捕获异常.他只想让例外"冒泡".如果我选择了一个已检查的异常,则此调用者将有大量无用的catch块仅用于人为地重新抛出异常.
可恢复的大部分时间取决于API的调用者,而不是API的写入者.API不应使用已检查的异常,因为只有未经检查的异常允许选择捕获或忽略异常.
你说的没错.
未经检查的异常用于让系统快速失败,这是一件好事.您应该清楚地说明您的方法是什么,以便正常工作.这样,您只能验证输入一次.
例如:
/** * @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"方法中的OtherClass(在第4569行)使用"exit"值调用您的类,即使它不应该等.
否则,您将不得不在代码中进行验证,这很容易出错.此外,有时很难追踪出现问题的原因,您可能会遇到数小时令人沮丧的调试
NullPointerExceptions也会发生同样的事情.如果你有一个包含大约15个方法的700行类,它们使用30个属性,并且它们都不能为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).
这是我的"最终经验法则".
我用:
我的方法代码中由于调用者而导致失败的未经检查的异常(涉及明确和完整的文档)
检查由于被调用者导致的失败的异常,我需要向想要使用我的代码的任何人明确说明
与之前的答案相比,这是使用一种或另一种(或两种)例外的明确理由(人们可以同意或不同意).
对于这两个异常,我将为我的应用程序创建自己的未经检查和检查的异常(这是一个很好的做法,如此处所提到的),除了非常常见的未经检查的异常(如NullPointerException)
因此,例如,下面这个特定函数的目标是使(或得到,如果已经存在)一个对象,
意思是:
必须存在/得到的对象的容器(CALLER
=>未经检查的异常的责任,并清除此被调用函数的javadoc注释)
其他参数不能为空
(选择将编码器放在CALLER上的编码器:编码器不会检查空参数,但编码器是否为文档)
结果不能为空
(责任和被调用者代码的选择,对于调用者
=>检查异常会非常感兴趣的选择,因为如果无法创建/找到对象,每个调用者必须做出决定,并且必须在编译时强制执行决策:他们不能使用此功能而不必处理这种可能性,这意味着使用此检查异常).
例:
/** * Build a folder.
* 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; }
这不仅仅是从异常中恢复的能力问题.在我看来,最重要的是调用者是否有兴趣捕获异常.
如果您编写要在其他地方使用的库或应用程序中的较低级别层,请问自己调用者是否有兴趣捕获(了解)您的异常.如果他不是,那么使用未经检查的例外,这样你就不会给他带来不必要的负担.
这是许多框架使用的哲学.特别是,我想到了Spring和hibernate - 它们将已知的已检查异常转换为未经检查的异常,因为已检查的异常在Java中被过度使用.我能想到的一个例子是来自json.org的JSONException,它是一个经过检查的异常并且非常讨厌 - 它应该是未经检查的,但是开发人员根本没有想到它.
顺便说一句,大多数情况下,调用者对异常的兴趣与从异常中恢复的能力直接相关,但情况并非总是如此.
对于您的Checked/Unchecked困境,这是一个非常简单的解决方案.
规则1:在代码执行之前将未经检查的异常视为可测试条件.例如…
x.doSomething(); // the code throws a NullPointerException
其中x为null ......代码应该具有以下内容......
if (x==null) { //do something below to make sure when x.doSomething() is executed, it won’t throw a NullPointerException. x = new X(); } x.doSomething();
规则2:将Checked Exception视为代码执行时可能出现的不可测试条件.
Socket s = new Socket(“google.com”, 80); InputStream in = s.getInputStream(); OutputStream out = s.getOutputStream();
...在上面的示例中,由于DNS服务器关闭,URL(google.com)可能无法使用.即使在DNS服务器正在运行并将"google.com"名称解析为IP地址的情况下,如果连接到google.com,则在任何时候后续网络都可能会崩溃.在读取和写入流之前,您根本无法一直测试网络.
在我们知道是否存在问题之前,有时候代码必须执行.通过强制开发人员以强制他们通过Checked Exception处理这些情况的方式编写代码,我不得不向发明这个概念的Java的创建者倾斜.
通常,Java中的几乎所有API都遵循上面的2条规则.如果您尝试写入文件,则磁盘可能会在完成写入之前填满.其他进程可能导致磁盘已满.没有办法测试这种情况.对于那些随时与硬件交互的人来说,使用硬件可能会失败,Checked Exceptions似乎是解决这个问题的优雅方案.
这有灰色区域.如果需要进行许多测试(一个令人兴奋的if语句带有大量的&&和||),抛出的异常将是一个CheckedException,因为它过于痛苦才能正确 - 你根本就不能说这个问题是一个编程错误.如果少于10个测试(例如'if(x == null)'),那么程序员错误应该是UncheckedException.
处理语言翻译时,事情变得有趣.根据上述规则,语法错误是否应被视为已检查或未检查的异常?我认为如果语言的语法可以在执行之前进行测试,那么它应该是一个UncheckedException.如果无法测试语言 - 类似于汇编代码在个人计算机上运行的方式,那么语法错误应该是一个Checked Exception.
上述2条规则可能会消除90%的您可以选择的问题.总结规则,遵循这种模式... 1)如果要执行的代码在执行之前可以进行测试以使其正确运行,并且如果发生异常 - 也就是程序员错误,则异常应该是UncheckedException(RuntimeException的子类) ).2)如果要执行的代码在执行之前无法进行测试以使其正确运行,则Exception应该是Checked Exception(Exception的子类).
您可以将其称为已检查或未检查的异常; 但是,程序员可以捕获这两种类型的异常,因此最好的答案是:将所有异常写为未选中并记录它们.这样,使用您的API的开发人员可以选择是否要捕获该异常并执行某些操作.检查异常完全浪费了每个人的时间,这使得您的代码成为令人震惊的噩梦.然后,适当的单元测试将显示您可能必须捕获并执行某些操作的任何异常.
已检查异常: 如果客户端可以从异常中恢复并希望继续,请使用已检查的异常.
未经检查的异常: 如果客户端在异常后无法执行任何操作,则引发未经检查的异常.
示例:如果您希望在方法A()中进行算术运算并基于A()的输出,则必须执行另一个操作.如果输出在方法A()中为null,而在运行时期间您没有预料到,那么您应该抛出Null指针Exception,即运行时异常.
请参考这里