Working Effectively with Legacy Code — Sprout Class

上一篇講到如何在時間不充裕的情況下使用 Sprout Method 來修改原本的程式碼,不過有時候事情總是不像憨人想的那麼簡單。假設今天想要修改某個 類別內的程式,偏偏這個類別的 instance 很難在測試程式裡面被建立,例如要建立這個類別的 instance 需要很多其他類別的 instances,而要建立這些 instances 又需要更多其他類別的 instances(Onion Parameters):

class SchedulingTaskPane: SchedulerPane {
init(tasks: [SchedulingTask]) {
...
super.init(tasks: tasks)
}
}
class SchedulingTask: SerialTask {
init(scheduler: Scheduler, resolver: MeetingResolver) {
...
}
}

像上面的例子,如果要用 Sprout Method 來修改 SchedulingTaskPane 的程式,馬上會遇到的問題就是要在測試程式裡面建立 SchedulingTaskPane 物件很麻煩,在時間有限的情況下上述的例子很難使用 Sprout Method 去修改 SchedulingTaskPane 的程式。


Sprout Class

假設在上面範例裡的 SchedulingTaskPane 類別裡面有一個負責輸出目前 tasks 資訊網頁的 method 叫做 display:

func display() -> String {
var displayString = “<html><head><title>Scheduling Tasks</title>
</head><body><table>”
    if tasks.count > 0 {
for task in tasks {
displayString.append(“<tr>”)
displayString.append(
String(format: “<td> %@ </td>”, task.identifier))
displayString.append(
String(format: “<td> %@ </td>”, task.name))
let stateString = task.state?.description() ?? “Unknow”
displayString.append(
String(format: “<td> %@ </td>”, stateString))
displayString.append(“</tr>”)
}
} else {
displayString.append(“No Scheduling Tasks”)
}
    displayString.append(“</table>”)
displayString.append(“</body>”)
displayString.append(“</html>”)
    return displayString
}

現在希望這個網頁裡面的表格可以加上表頭,當然不希望新程式碼跟舊程式碼混在一起,馬上想到的是使用 Sprout Method 來修改現有的程式。但是 SchedulingTaskPane instance 很難在測試程式裡面被建立,所以很難使用 Sprout Method 來進行修改。

這時候可以建立一個小的類別 SchedulingTaskHeaderPane,讓這個類別來做輸出表頭的工作,這樣就可以很容易用 TDD 來完成需要的 method:

class SchedulingTaskHeaderPane {
func display() -> String {
return “<tr><td>Identifier</td><td>Name</td><td>State</td>
</tr>”
}
}
func display() -> String {
var displayString = “<html><head><title>Scheduling Tasks</title>
</head><body><table>”
if tasks.count > 0 {
let hearPane = SchedulingTaskHeaderPane()
displayString.append(hearPane.display())

...
}
...
}

然後在原本的 method 裡面建立 SchedulingTaskHeaderPane instance 來完成增加表頭的工作,這樣的做法可以讓原本程式碼變動降到很低,而這個 SchedulingTaskHeaderPane 就叫做 Sprout Class。


不過 Sprout Class 的缺點是會製造出很多細小的類別,讓程式架構變得複雜。但如果往後有時間可以將原本的類別也放進測試程式內時,可以考慮是否將 Sprout Class 也合併回原本的類別內。又或者這個 Sprout Class 在專案不斷的修改下新增了其他功能,或是被跟其他相似功能的類別合併變成另外一個類別。

Sprout Method 一樣的是,Sprout Class 並沒有替原本的程式碼寫測試程式,而是想辦法在不變動現有程式碼太多的情況下去修改程式,並且替這些新增的程式寫測試。