治疗及预防措施
当您发现有一条执行路径中没有包含适当的清除代码时,您可能会上当,只是简单地把它添加到那条路径中。
例如,您可以把 walk 方法的程序正文包到一个 try 程序块中,并加入一条 finally 子句以 确保关闭了连接。但这样一个解决方案却不是个好办法。
我们的 TableWalker 完全不必担心关闭连接的问题。即使每个 TableWalker 都 确实设法关闭了连接,我们也会陷入到第二种方式中,这种方式可以让这类错误模式自动现身 ― 当我们运行多个 walker 时,就会有 太多walker 试图关闭连接。
更糟的是,如果我们两次调用 con.close() (一次在 try 块中,一次在 catch 块中,而不是在 finally 语句中简单地单独调用),我们就会把 rogue tile 引入到代码中。
如果代码中添加了很多这种 rogue tile,那么要成功地重新组织代码将变得很困难。即使在测试期间,其中一些 rogue tile 可能处理的是基本上不会出现的执行路径。
一个好得多的解决方案是重新组织代码,用第一种方式来管理这些资源:把获得和释放资源的代码放到同一个方法中。
Andrew Hunt 和 Dave Thomas 在他们的一本优秀书籍 The Pragmatic Programmer 中用一个成语 ― “有始有终”来提倡这种思想。每个方法都要负责把它获得的资源释放掉。在我们的示例中,就是把对 con.close() 的调用移到类 Example 的 main 方法中,如下所示:
清单 3. 通过重新组织代码使资源的获得和释放发生在同一个方法内
public class Example {
public static void main(String[] args) {
String url = "your database url";
// con must be declared and initialized here, so as to be in
// the scope of both the try and finally clauses.
Connection con = null;
try {
con = DriverManager.getConnection(url, "Fernanda", "J8");
new TablePrinter(con, "Names").walk();
}
catch (SQLException e) {
throw new RuntimeException(e.toString());
}
finally {
try {
con.close();
}
catch (Exception e) {
throw new RuntimeException(e.toString());
}
}
}
} |
这里,对 con.close() 的调用是在创建连接的相同 try 块的 finally 子句中,避开了没调用它的任何可能的执行路径。
总结
我们把本周的错误模式总结如下:
•模式:Split Cleaner
•症状:程序没能正确地管理资源,表现为泄漏或过早地释放了它们。
•起因:程序的一些执行路径没有做到它们应该做的工作:释放资源 正好一次。
•治疗和预防措施:把负责释放资源的代码移到获得资源的同一方法中。
不再赘述。