Java 程式設計語言的一個特色是儲存自動管理,它把程式員從很容易出錯的釋放使用後的記憶體的工作中解放出來。儘管如此,許多程式還是得處理資源問題,例如檔案和資料庫連接,這些都必須在使用之後明確地釋放掉。跟手工管理儲存一樣,程式員在手工管理資源時也會犯很多錯誤。其中一個就是本周專欄的主題 ― Split Cleaner錯誤模式。
分開還是不分開
在管理諸如檔案和資料庫連接這樣的資源時,您必須在使用完資源後把它釋放掉。當然,對代碼的任何指定的執行,您希望一次獲得資源,然後一次將其釋放。要做到這點,您可以採用兩種方式:
您可以在同一個方法中獲得並釋放資源。用這種方式,可以保證資源每獲得一次,也釋放一次。
您可以跟蹤代碼的每一個可能的執行路徑,並確保在每一個執行個體中資源最後都被釋放掉了。
第二種方式可能會出問題。因為您的程式碼程式庫不可避免地要變大,另一個不熟悉您代碼的程式員或許會添加一條沒把資源釋放掉的執行路徑,其後果當然是資源泄漏。
Split Cleaner 錯誤模式
我把符合這種模式的錯誤稱為 split cleaner,是因為釋放資源的代碼是沿各種可能的執行路徑分開的。因為沿各條路徑的釋放代碼很可能都是一樣的,所以大多數 split cleaner 也是 rogue tile的例子。(Rogue tile 是我對一種錯誤的稱呼,這種錯誤的起因是:起初用拷貝和粘貼的方式編寫代碼,但後來在做了一些更改後卻忘了適當地修改代碼的所有副本。如想更多瞭解 rogue tile,請參閱我的論文“ 錯誤模式:介紹。”)
例如,假設您正用 JDBC 處理一張員工姓名表。您希望執行的許多操作中包括遍曆這張表並對其中包含的資料進行計算。您要完成的最後一個操作可能是列印出所有員工的名字,如下所示:
清單 1. 遍曆一個員工表的代碼
import java.sql.*;
public class Example {
public static void main(String[] args) {
String url = "your database url";
try {
Connection con = DriverManager.getConnection(url);
new TablePrinter(con, "Names").walk();
}
catch (SQLException e) {
throw new RuntimeException(e.toString());
}
}
}
abstract class TableWalker {
Connection con;
String tableName;
public TableWalker(Connection _con, String _tableName) {
this.con = _con;
this.tableName = _tableName;
}
public void walk() throws SQLException {
String queryString =("SELECT * FROM " + tableName);
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(queryString);
while (rs.next()) {
execute(rs);
}
con.close();
}
public abstract void execute(ResultSet rs) throws SQLException;
}
class TablePrinter extends TableWalker {
public TablePrinter(Connection _con, String _tableName) {
super(_con, _tableName);
}
public void execute(ResultSet rs) throws SQLException {
String s = rs.getString("FIRST_NAME");
System.out.println(s);
}
}
先說點題外話。請注意,我已把用來遍曆表的代碼抽出來放到了抽象類別 Walker 中,以使新的子類可以很容易地遍曆表中的行。雖然試圖預測程式被擴充的各種方式並為其編寫代碼通常是浪費時間,但還是讓我們假設在此例中 絕對地,毫無疑問地,無論如何對代碼只做這一類的擴充。(事實上,我可以保證在本文結束前,只會有這樣一種擴充)。