在書中 3.1 節是談論重複的程式碼,這當然是程式的壞味道,是重構的標的物,底下將舉出我實際遇到的例子,以及我在重重限制下進行的重構。
這是一個有上百個 tables 的資料庫 web 系統,用 java 開發,沒有採用任何 MVC 的 framework,程式也沒有將 view、model 等區隔開,所以整個系統絕大多數的程式碼是寫在 jsp 裡,在這些 jsp 裡會看到大量的 html、javascript、jquery、css、java 摻雜,連接資料庫直接使用 JDBC API,底下的例子就是一個將 JDBC API 相關的程式,經過重構減少重複程式碼的例子。
String sql;
sql = "UPDATE FoudItem "
+ " SET "
+ " name = NVL(?,' '),"
+ " type = ?, "
+ " rate = ? , "
+ " empNo = NVL(?,' '), "
+ " mtime = SYSTIMESTAMP "
+ " WHERE fundNo = RPAD(?,3) "
;
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, fundName);
pstmt.setString(2, fundType);
pstmt.setDouble(3, rate);
pstmt.setString(4, empNo);
pstmt.setString(5, fundNo);
pstmt.executeUpdate();
像這樣存取資料庫的程式碼在整個系統中有數百個,全部是同樣邏輯,先有一段用 string 一直相加產生的 sql statement,在產生 PreparedStatement 後,傳入參數並執行。我是後續接手維護的人,也被告知不能動大架構,所以我只簡單的如下重構,以去除重複的程式碼。
- 將 sql statement 移到 xml 檔
<?xml version="1.0" encoding="UTF-8"?>
<sql>
<statement id="update">
<![CDATA[
UPDATE FoudItem "
SET "
name = NVL(?,' '),"
type = ?, "
rate = ? , "
empno = NVL(?,' '), "
mtime = SYSTIMESTAMP "
WHERE fundno = RPAD(?,3)
]]>
</statement>
</sql>
- 自 xml 中讀取 sql statement
File file = new File(path + name + ".xml");
SAXReader reader = new SAXReader();
Document document;
try {
document = reader.read(file);
Element sqlElmt = document.getRootElement();
Iterator<Element> iter = sqlElmt.elementIterator("statement");
while (iter.hasNext()) {
Element elmt = iter.next();
if (id.equals(elmt.attributeValue("id"))) {
return elmt.getText();
}
}
} catch (DocumentException e) {
logger.error(e.getMessage(), e);
}
return StringUtils.EMPTY;
}
這段程式碼很簡單的讀取指定目錄下 xml 檔,以取得程式需要的 sql statement。
- 傳入參數到 PreparedStatement 並執行
String sql = getSqlStatement(name, id);
PreparedStatement pstmt = conn.prepareStatement(sql);
for(int i=1; i<=params.length; i++) {
Object param = params[i-1];
if (param instanceof String) {
pstmt.setString(i, (String) param);
}
else if (param instanceof Long) {
pstmt.setLong(i, (Long) param);
}
else if (param instanceof Double) {
pstmt.setDouble(i, (Double) param);
}
else if (param instanceof BigDecimal) {
pstmt.setBigDecimal(i, (BigDecimal) param);
}
else {
throw new SQLException("field's type is invalid");
}
}
return pstmt.execute();
}
- 改寫原 JDBC 相關的程式
SQLExecutor.execute(
conn, "fund", "update",
fundName, fundType, rate, empNo, fundNo
);
改寫完了之後,一大段的程式最後只剩一行指令! (從命名為 fund.xml 的檔案裡,讀取 id = update 的 sql statement,產生 PreparedStatement 後,將相關參數傳入執行。)
底下是重構一書 3.1 節的原文,有興趣的可以看看,沒看過這本書的人,建議買一本,絕對值得!
Duplicated Code
Number one in the stink parade is duplicated code. If you see the same code structure in more than one place, you can be sure that your program will be better if you find a way to unify them.
The simplest duplicated code problem is when you have the same expression in two methods of the same class. Then all you have to do is Extract Method and invoke the code from both places.
Another common duplication problem is when you have the same expression in two sibling subclasses. You can eliminate this duplication by using Extract Method in both classes then Pull Up Field. If the code is similar but not the same, you need to use Extract Method to separate the similar bits from the different bits. You may then find you can use Form Template Method. If the methods do the same thing with a different algorithm, you can choose the clearer of the two algorithms and use Substitute Algorithm.
If you have duplicated code in two unrelated classes, consider using Extract Class in one class and then use the new component in the other. Another possibility is that the method really belongs only in one of the classes and should be invoked by the other class or that the method belongs in a third class that should be referred to by both of the original classes. You have to decide where the method makes sense and ensure it is there and nowhere else.
沒有留言:
張貼留言