你觉得自己是一个Java专家吗?是否肯定自己已经全面掌握了Java的异常处理机Ӟ在下面这D代码中Q你能够q速找出异常处理的六个问题吗?
1 OutputStreamWriter out = ...
2 java.sql.Connection conn = ...
3 try { // ?
4 Statement stat = conn.createStatement();
5 ResultSet rs = stat.executeQuery(
6 "select uid, name from user");
7 while (rs.next())
8 {
9 out.println("IDQ? + rs.getString("uid") // ?
10 "Q姓名:(x)" + rs.getString("name"));
11 }
12 conn.close(); // ?
13 out.close();
14 }
15 catch(Exception ex) // ?
16 {
17 ex.printStackTrace(); //_(d)?
18 }
|
作ؓ(f)一个JavaE序员,你至应该能够找Z个问题。但是,如果你不能找出全部六个问题,L(fng)l阅L文?/p>
本文讨论的不是Java异常处理的一般性原则,因ؓ(f)q些原则已经被大多数人熟知。我们要做的是分析各U可UCؓ(f)“反例”Qanti-patternQ的q背优秀~码规范的常见坏?fn)惯Q帮助读者熟(zhn)这些典型的反面例子Q从而能够在实际工作中敏锐地察觉和避免这些问题?/p>
反例之一Q丢弃异?/p>
代码Q?5?18行?/p>
q段代码捕获了异常却不作M处理Q可以算得上Java~程中的杀手。从问题出现的频J程度和害E度来看Q它也许可以和C/C++E序的一?
恶名q播的问题相提ƈ论?Q不查缓冲区是否已满。如果你看到了这U丢弃(而不是抛出)异常的情况,可以癑ֈ之九(ji)十九(ji)地肯定代码存在问题(在极数情况
下,q段代码有存在的理由Q但最好加上完整的注释Q以免引起别解)?/p>
q段代码的错误在于,异常Q几乎)L意味着某些事情不对劲了Q或者说臛_发生了某些不d的事情,我们不应该对E序发出的求救信号保持沉默和
无动于衷。调用一下printStackTrace不?#8220;处理异常”。不错,调用printStackTrace对调试程序有帮助Q但E序调试阶段l束
之后QprintStackTrace׃应再在异常处理模块中担负主要责Q了?/p>
丢弃异常的情形非常普遍。打开JDK的ThreadDeathcȝ文Q可以看C面这D说明:(x)“特别圎ͼ虽然出现ThreadDeath是一
U?#8216;正常的情?#8217;Q但ThreadDeathcLError而不是Exception的子c,因ؓ(f)许多应用?x)捕h有的Exception然后丢弃它不?
理睬?#8221;q段话的意思是Q虽然ThreadDeath代表的是一U普通的问题Q但鉴于许多应用?x)试图捕h有异常然后不予以适当的处理,所以JDK?
ThreadDeath定义成了Error的子c,因ؓ(f)ErrorcM表的是一般的应用不应该去捕获的严重问题。可见,丢弃异常q一坏习(fn)惯是如此常见Q它
甚至已经影响CJava本n的设计?/p>
那么Q应该怎样Ҏ(gu)呢?主要有四个选择Q?/p>
1、处理异常。针对该异常采取一些行动,例如修正问题、提醒某个h或进行其他一些处理,要根据具体的情Ş定应该采取的动作。再ơ说明,调用printStackTrace不上已l?#8220;处理好了异常”?/p>
2、重新抛出异常。处理异常的代码在分析异怹后,认ؓ(f)自己不能处理它,重新抛出异常也不׃ؓ(f)一U选择?/p>
3、把该异常{换成另一U异常。大多数情况下,q是指把一个低U的异常转换成应用的异常(其含义更Ҏ(gu)被用户了解的异常Q?/p>
4、不要捕获异常?/p>
l论一Q既然捕获了异常Q就要对它进行适当的处理。不要捕获异怹后又把它丢弃Q不予理睬?/p>
反例之二Q不指定具体的异?/p>
代码Q?5行?/p>
许多时候h们会(x)被这样一U?#8220;妙?#8221;x吸引Q用一个catch语句捕获所有的异常。最常见的情形就是用catchQException exQ语句。但实际上,在绝大多数情况下Q这U做法不值得提倡。ؓ(f)什么呢Q?/p>
要理解其原因Q我们必d一下catch语句的用途。catch语句表示我们预期?x)出现某U异常,而且希望能够处理该异常。异常类的作用就?
告诉Java~译器我们想要处理的是哪一U异常。由于绝大多数异帔R直接或间接从java.lang.ExceptionzQcatch
QException exQ就相当于说我们惌处理几乎所有的异常?/p>
再来看看前面的代码例子。我们真正想要捕L(fng)异常是什么呢Q最明显的一个是SQLExceptionQ这是JDBC操作中常见的异常。另一个可
能的异常是IOExceptionQ因为它要操作OutputStreamWriter.昄Q在同一个catch块中处理q两U截然不同的异常是不合?
的。如果用两个catch块分别捕获SQLException和IOExceptionp好多了。这是_(d)catch语句应当量指定具体的异常类
型,而不应该指定늛范围太广的ExceptioncR?/p>
另一斚wQ除了这两个特定的异常,q有其他许多异常也可能出现。例如,如果׃某种原因QexecuteQueryq回了nullQ该怎么办?
{案是让它们l箋抛出Q即不必捕获也不必处理。实际上Q我们不能也不应该去捕获可能出现的所有异常,E序的其他地方还有捕获异常的Z(x)Q?直至最后由
JVM处理?/p>
l论二:(x)在catch语句中尽可能指定具体的异常类型,必要时用多个catch.不要试图处理所有可能出现的异常?/p>
反例之三Q占用资源不释放
代码Q??14行?/p>
异常改变了程序正常的执行程。这个道理虽然简单,却常常被Z忽视。如果程序用C文g、Socket、JDBCq接之类的资源,即遇到了异常,也要正确释放占用的资源。ؓ(f)此,Java提供了一个简化这cL作的关键词finally.
finally是样好东西:(x)不管是否出现了异常,Finally保证在try/catch/finally块结束之前,执行清理d的代码L有机?x)执行。遗憄是有些h却不?fn)惯使用finally.
当然Q编写finally块应当多加小心,特别是要注意在finally块之内抛出的异常Q?q是执行清理d的最后机?x),量不要再有难以处理的错误?/p>
l论三:(x)保证所有资源都被正释放。充分运用finally关键词?/p>
反例之四Q不说明异常的详l信?/p>
代码Q??18行?/p>
仔细观察q段代码Q如果@环内部出C异常Q会(x)发生什么事情?我们可以得到_的信息判断@环内部出错的原因吗?不能。我们只能知道当前正在处理的cd生了某种错误Q但却不能获得Q何信息判断导致当前错误的原因?/p>
printStackTrace的堆栈跟t功能显C出E序q行到当前类的执行流E,但只提供了一些最基本的信息,未能说明实际D错误的原因,同时也不易解诅R?
因此Q在出现异常Ӟ最好能够提供一些文字信息,例如当前正在执行的类、方法和其他状态信息,包括以一U更适合阅读的方式整理和l织printStackTrace提供的信息?/p>
l论四:(x)在异常处理模块中提供适量的错误原因信息,l织错误信息使其易于理解和阅诅R?/p>
反例之五Q过于庞大的try?/p>
代码Q??14行?/p>
l常可以看到有h把大量的代码攑օ单个try块,实际上这不是好习(fn)惯。这U现象之所以常见,原因在于有些h囄事,不愿花时间分析一大块代码
中哪几行代码?x)抛出异常、异常的具体cd是什么。把大量的语句装入单个巨大的try块就象是出门旅游时把所有日常用品塞入一个大子Q虽然东西是带上了,
但要扑և来可不容易?/p>
一些新手常常把大量的代码放入单个try块,然后再在catch语句中声明ExceptionQ而不是分d个可能出现异常的D落q分别捕获其异常。这U做法ؓ(f)分析E序抛出异常的原因带来了困难Q因Z大段代码中有太多的地方可能抛出Exception.
l论五:(x)量减小try块的体积?/p>
反例之六Q输出数据不完整
代码Q??11行?/p>
不完整的数据是JavaE序的隐形杀手。仔l观察这D代码,考虑一下如果@环的中间抛出了异常,?x)发生什么事情。@环的执行当然是要被打断的Q?
其次Qcatch块会(x)执行Q?p些,再也没有其他动作了。已l输出的数据怎么办?使用q些数据的h或设备将收到一份不完整的(因而也是错误的Q数据,?
得不CQ何有兌份数据是否完整的提示。对于有些系l来_(d)数据不完整可能比pȝ停止q行带来更大的损失?/p>
较ؓ(f)理想的处|办法是向输备写一些信息,声明数据的不完整性;另一U可能有效的办法是,先缓冲要输出的数据,准备好全部数据之后再一ơ性输出?/p>
l论六:(x)全面考虑可能出现的异总?qing)这些异常对执行程的媄响?/p>
改写后的代码
Ҏ(gu)上面的讨论,下面l出改写后的代码。也许有Z(x)说它E微有点Q嗦Q但是它有了比较完备的异常处理机制?/p>
OutputStreamWriter out = ...
java.sql.Connection conn = ...
try {
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery(
"select uid, name from user");
while (rs.next())
{
out.println("IDQ? + rs.getString("uid") + "Q姓? "
+ rs.getString("name"));
}
}
catch(SQLException sqlex)
{
out.println("警告Q数据不完整");
throw new ApplicationException("d数据时出现SQL错误", sqlex);
}
catch(IOException ioex)
{
throw new ApplicationException("写入数据时出现IO错误", ioex);
}
finally
{
if (conn != null) {
try {
conn.close();
}
catch(SQLException sqlex2)
{
System.err(this.getClass().getName() + ".mymethod - 不能关闭数据库连?
" + sqlex2.toString());
}
}
if (out != null) {
try {
out.close();
}
catch(IOException ioex2)
{
System.err(this.getClass().getName() + ".mymethod - 不能关闭输出文g"
+ ioex2.toString());
}
}
}
|
本文的结Z是放之四L(fng)准的教条Q有时常识和l验才是最好的老师。如果你对自q做法没有癑ֈ之百的信心,务必加上详细、全面的注释?/p>
另一斚wQ不要笑话这些错误,不妨问问你自己是否真地彻底摆׃q些坏习(fn)惯。即使最有经验的E序员偶?dng)也会(x)误入歧途,原因很简单,因ؓ(f)它们确
实实带来?#8220;方便”。所有这些反例都可以看作Java~程世界的恶,它们丽动hQ无孔不入,时刻诱惑着你。也许有Z(x)认ؓ(f)q些都属于鸡皮蒜毛的事Q?
不挂Q但误住:(x)勿以恶小而ؓ(f)之,勿以善小而不为?/p>

]]>