Answers:
只要您知道何时应使用检查异常,它就很棒。Java核心API未能遵循SQLException(有时甚至是IOException)的这些规则,这就是它们如此糟糕的原因。
检查异常应该用于预测的,但不可预防是错误的合理歇着。
未经检查的异常应用于其他所有内容。
我将为您详细说明这一点,因为大多数人都误解了这意味着什么。
除非您抛出的异常满足以上所有条件,否则应使用Unchecked Exception。
在每个级别进行重新评估:有时,捕获已检查异常的方法不是处理错误的正确位置。在这种情况下,请考虑对您自己的呼叫者而言合理的选择。如果异常是可预测的,无法预防的且使它们从中恢复的合理,那么您应该自己抛出已检查的异常。如果不是,则应将异常包装在未经检查的异常中。如果遵循此规则,您将发现自己将检查的异常转换为未检查的异常,反之亦然,具体取决于您所在的层。
对于已检查和未检查的异常,请使用正确的抽象级别。例如,具有两个不同实现(数据库和文件系统)的代码存储库应避免通过抛出SQLException
或来公开特定于实现的细节IOException
。相反,它应该将异常包装在涵盖所有实现的抽象中(例如RepositoryException
)。
来自Java学习者:
当发生异常时,您必须捕获并处理该异常,或者通过声明您的方法抛出该异常来告诉编译器您无法处理该异常,然后使用该方法的代码将必须处理该异常(即使它也可以选择声明无法处理的异常。)
编译器将检查是否已完成以下两项操作之一(捕获或声明)。因此,这些被称为Checked异常。但是编译器不会检查错误和运行时异常(即使您可以选择捕获或声明它也不是必需的)。因此,这两个称为未检查的异常。
错误用于表示在应用程序外部发生的那些情况,例如系统崩溃。运行时异常通常是由应用程序逻辑中的错误引起的。在这种情况下,您将无能为力。当运行时异常发生时,您必须重新编写程序代码。因此,编译器不会检查它们。这些运行时异常将在开发和测试期间发现。然后,我们必须重构代码以消除这些错误。
我使用的规则是:永远不要使用未经检查的异常!(或者当您看不到周围的东西时)
相反的情况非常有力:永远不要使用检查异常。我不愿意参加辩论,但似乎已经达成广泛共识,即事后看来,引入检查例外是一个错误的决定。请不要射击信使并提及这些 论点。
foo
被记录为抛出异常barException
,并且foo
调用了barException
即使foo
不期望抛出异常的方法,调用的代码foo
将认为已到达文件的末尾,并且不知道发生了意外的情况。我认为这种情况是检查异常应该最有用的一种情况,但这也是编译器允许未处理的检查异常的唯一情况。
在具有足够多层的足够大的系统上,已检查的异常是无用的,因为无论如何,您都需要一种体系结构级别的策略来处理异常的处理方式(使用故障屏障)
使用已检查的异常,您的错误处理策略是微管理的,在任何大型系统上都是无法承受的。
大多数情况下,您不知道错误是否“可恢复”,因为您不知道API的调用者位于哪一层。
假设我创建了一个StringToInt API,该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”方法(在第4569行)中的OtherClass使用“ exit”值调用了您的类,即使它不应该等等。
否则,您将不得不在整个代码中进行验证,这很容易出错。另外,有时很难跟踪出了什么问题,并且您可能会期望数小时令人沮丧的调试
NullPointerExceptions也发生同样的事情。如果您有700行的类,其中包含15种方法,则使用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)
因此,例如,下面这个特殊功能的目标是创建(或获取,如果已经存在)一个对象,
这意味着:
例:
/**
* 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;
}
这不仅仅是从异常中恢复的能力。在我看来,最重要的是调用方是否有兴趣捕获异常。
如果您编写要在其他地方或应用程序中较低层使用的库,请问问自己调用者是否有兴趣捕获(了解)您的异常。如果他不是,请使用不受检查的异常,这样就不会给他带来不必要的负担。
这是许多框架使用的哲学。尤其是想到Spring和Hibernate时,它们正是将已知的已检查异常转换为未检查异常,这恰恰是因为Java中过度使用了已检查异常。我可以想到的一个示例是json.org中的JSONException,它是一个已检查的异常,并且通常很烦人-应该将其取消检查,但是开发人员根本没有考虑过。
顺便说一句,在大多数情况下,呼叫者对异常的兴趣与从异常中恢复的能力直接相关,但并非总是如此。
这是解决“已检查/未检查”难题的非常简单的解决方案。
规则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:将检查异常视为代码执行期间可能发生的不可测试条件。
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条规则。如果尝试写入文件,则磁盘可能在完成写入之前已装满。其他进程可能导致磁盘已满。根本没有办法测试这种情况。对于那些在任何时候与硬件进行交互的人来说,使用硬件可能会失败,检查异常似乎是解决此问题的理想方法。
这里有一个灰色区域。如果需要进行许多测试(if语句中包含许多&&和||的情况令人不寒而栗),则抛出的异常将是CheckedException,仅仅是因为它太难了而无法正确执行-您根本无法说出这个问题是编程错误。如果少于10个测试(例如'if(x == null)'),则程序员错误应该是UncheckedException。
与语言口译员打交道时,事情变得很有趣。根据上述规则,应将语法错误视为已检查异常还是未检查异常?我认为如果该语言的语法可以在执行之前进行测试,则应为UncheckedException。如果无法测试该语言(类似于汇编代码在个人计算机上的运行方式),则语法错误应为“检查异常”。
上面的2条规则可能会消除您90%的选择担忧。要总结规则,请遵循以下模式…1)如果要执行的代码可以在执行之前对其进行测试以使其正确运行,并且如果发生异常(又称为程序员错误),则该异常应该是UncheckedException(RuntimeException的子类) )。2)如果要执行的代码在执行之前无法进行测试以使其正常运行,则异常应为Checked Exception(Exception的子类)。
您可以将其称为已检查或未检查的异常。但是,程序员都可以捕获两种类型的异常,因此最佳答案是:将所有异常都写为未检查并记录下来。这样,使用您的API的开发人员可以选择是否要捕获该异常并执行某些操作。受检查的异常完全浪费了每个人的时间,这使您的代码成为令人震惊的噩梦。正确的单元测试将提出您可能必须捕获并执行某些操作的所有异常。
我同意优先选择非检查异常,尤其是在设计API时。调用方始终可以选择捕获已记录的未经检查的异常。您只是不必要地强制呼叫者进行呼叫。
我发现较低的级别(作为实现细节)很有用的检查异常。与必须管理指定的错误“返回码”相比,这似乎是一种更好的控制机制。有时它也可以帮助您了解低级代码更改想法的影响...在下游声明已检查的异常,并查看谁需要进行调整。如果有很多泛型,则最后一点不适用: catch(Exception e) 或抛出Exception ,通常无论如何都不会考虑到这一点。
这是我想分享我多年的开发经验后的观点:
已检查的异常。这是业务用例或调用流程的一部分,这是我们期望或不期望的应用程序逻辑的一部分。例如,连接被拒绝,条件不满足等。我们需要处理它,并向用户显示相应的消息,并指示发生了什么以及下一步该怎么做(稍后重试等)。我通常称其为后处理异常或“用户”异常。
未经检查的异常。这是编程异常的一部分,是软件代码编程中的一些错误(错误,缺陷),反映了程序员必须如何按照文档使用API的方式。如果外部的lib / framework文档说它希望获取某个范围的数据且非null,因为将引发NPE或IllegalArgumentException,程序员应期望它并根据文档正确使用API。否则将引发异常。我通常称其为预处理异常或“验证”异常。
按目标受众。现在,让我们谈谈目标受众或人群,这些例外是经过设计的(根据我的观点):
在应用程序开发生命周期阶段。
框架通常使用未经检查的异常(例如,Spring)的原因是,框架无法确定应用程序的业务逻辑,这取决于开发人员是否可以捕获并设计自己的逻辑。
我们必须根据是否是程序员错误来区分这两种类型的异常。
FileNotFoundException是了解细微差别的好例子。如果找不到文件,则抛出FileNotFoundException。此异常有两个原因。如果文件路径是由开发人员定义的,或者是通过GUI从最终用户那里获取的,则应为Unchecked Exception。如果文件被其他人删除,则应为“检查异常”。
Checked Exception有两种处理方式。这些正在使用try-catch或传播异常。在传播异常的情况下,由于异常处理,调用堆栈中的所有方法将紧密耦合。因此,我们必须谨慎使用Checked Exception。
如果您开发了分层的企业系统,则必须选择大多数未经检查的异常来引发,但不要忘记使用经过检查的异常以防万一。
对于要向调用者提供信息(例如,权限不足,找不到文件等)的可恢复情况,检查异常非常有用。
未经检查的异常很少(如果有的话)用于在运行时通知用户或程序员严重的错误或意外情况。如果您正在编写供他人使用的代码或库,请不要抛出它们,因为他们可能不希望您的软件抛出未经检查的异常,因为编译器不会强制捕获或声明它们。
如果不太可能发生异常,那么即使在捕获到异常之后我们也可以继续进行,并且我们无法做任何事情来避免该异常,那么我们可以使用已检查的异常。
每当我们希望在发生特定异常时以及在预期但不确定某个异常时做出有意义的事情时,就可以使用检查异常。
每当异常在不同的层中导航时,我们都不需要在每个层中捕获它,在这种情况下,我们可以使用运行时异常或将异常包装为未检查的异常。
当最有可能发生异常时,将使用运行时异常,它无法进行进一步处理且任何内容都无法恢复。因此,在这种情况下,我们可以针对该异常情况采取预防措施。例如:NUllPointerException,ArrayOutofBoundsException。这些更有可能发生。在这种情况下,我们可以在编码时采取预防措施,以避免此类异常。否则,我们将不得不在每个地方编写try catch块。
可以将更多常规例外设置为“未选中”,而将“较少常规”设置为“未选中”。
我认为我们可以从几个问题中考虑例外情况:
为什么会发生异常?当它发生时我们该怎么办
错误地,一个错误。例如一个null对象的方法被调用。
String name = null;
... // some logics
System.out.print(name.length()); // name is still null here
这种异常应在测试期间修复。否则,它将破坏生产,并且您会收到一个非常高的错误,需要立即修复。无需检查这种异常。
通过外部输入,您将无法控制或信任外部服务的输出。
String name = ExternalService.getName(); // return null
System.out.print(name.length()); // name is null here
在这里,如果您想在名称为null时继续,则可能需要检查名称是否为null,否则,可以不理会它,它将在此处停止并为调用方提供运行时异常。无需检查这种异常。
根据外部的运行时异常,您无法控制或信任外部服务。
在这里,如果要在发生外部事件时继续运行,可能需要从ExternalService捕获所有异常,否则,可以不理会它,它将在此处停止并为调用方提供运行时异常。
通过外部检查异常,您将无法控制或信任外部服务。
在这里,如果要在发生外部事件时继续运行,可能需要从ExternalService捕获所有异常,否则,可以不理会它,它将在此处停止并为调用方提供运行时异常。
在这种情况下,我们是否需要知道ExternalService中发生了哪种异常?这取决于:
如果您可以处理某些异常,则需要捕获它们并进行处理。对于其他人,将它们冒泡。
如果您需要日志或对用户特定执行的响应,则可以捕获它们。对于其他人,将它们冒泡。
我认为在声明应用程序异常时,它应该是未经检查的异常,即RuntimeException的子类。原因是它不会用try-catch使应用程序代码混乱,并在方法上引发声明。如果您的应用程序使用的是Java Api,它会抛出经过检查的异常,无论如何都需要处理这些异常。对于其他情况,应用程序可能会引发未经检查的异常。如果应用程序调用者仍然需要处理未检查的异常,则可以完成。