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

]]>