續前篇,這篇談 TDD 帶來的改變,以及我自己的個案心得。
TDD 的影響
只要用 TDD 重寫以前寫過的小專案,就會發現最後的產出,和原本寫的程式不同。舉例來說,會發現少了很多 setter function,因為大多情況只要用 constructor 放值即可。更進一步,會發現少寫了許多功能,而這些功能其實是原本不需用到的。
TDD 同時也幫助設計出易用的介面,透過寫測試程式的過程,可以釐清程式的使用方式。當發現程式不好測試時,通常意味著程式設計不良。測試程式也可以當作文件使用,提供使用程式的範例程式碼。
除此之外,在 TDD 的過程中,開發的速度很順暢,很少會落入長時間的除錯裡,因為程式被強迫切成許多小單位,並且每一單位都有被測試。一但出錯馬上會發現,而且知道錯在那裡。不像以往得用 debugger 追個老半天,最後發現 bug 在很遠的一小段程式碼裡,有時還是簡單的打錯字。在除錯的過程,藉由自動化測試的幫助,修改和執行可以快速地反覆交替進行,減少除錯以外的精神損耗(如手動準備執行需要的資料、設置和操作 debugger )。即使寫測試程式花的時間和除錯一樣多(我相信會是較少),至少以 TDD 的方式進行,心情會比較愉快,不會卡在一個地方太久。
TDD 的個案心得
以我最近的實作案例來說,我一開始知道會用到數個算數函數,而且這些函數日後需要抽換為不同的算法,方便我比較何種組合效果最好。舉例來說,我需要實作排序演算法 Sort(),由於資料分佈特性的改變,我可能需要實作各種演算法如 quick sort、heap sort、insertion sort、radix sort 等,並讓這些演算法的輸入輸出介面一致,方便視情況抽換不同的演算法。在這個案例裡,我需要寫多個這類型的演算法,把它們整合成一個較大的程式。
若照我原本的習慣,大概就全部套 strategy pattern,於是我可能會先寫數個 interface 或是寫個 abstract class 再套多型。至少會花半天完成這些工作,並且增加一堆「未來可能會用到」的 class。但這回我忍住了,我遵從 TDD 的三個步驟,我先統統用 static method 寫,因為這是最簡單的實作。結果寫個兩天後,我發現這幾個算數函數,其實只有一個真的需要套 strategy pattern,其它用 static method 就夠了。也許那一個最複雜的函數也不需用到 strategy pattern,總之,由於目前仍無需求,我全部都保留原樣。於是我將時間花在刀口上,優先完成必要的事。並且,我的 class space 沒有被一堆「也許有用」的 interface 和 class 汙染,避免 over-design,提高整體的可讀性。
相信大家看到這都會大喊,全部都等用到才寫,日後難道不會付出更大的代價做變動?改程式碼的代價很高,所以應該多費些心力設計好才對吧?但別忘了,「改程式碼的代價很高」是一般的情況,在有充沛的測試程式做為後盾的情況下,改程式的代價其實沒那麼高。況且,程式碼隨時都有重構,程式應該是保持在容易修改的情況,改程式的代價又更低了。
至於初期的設計要精確到什麼程度才開始寫程式,仍需經驗拿捏。我目前的作法是先有個大概的整體設計,再開始用 TDD 的方式實作各個 class,並完成細部的設計。
沒有留言:
張貼留言