文章

TDD 之辯

DHH 說 TDD 已/該死,再看他在 RailsConf 2014 的 talk,也漸明白他說的是甚麼。他質疑的,是以 test-first 來驅動的設計,先決條件是要令該單元可測試,而這樣子產生出來的程式碼,反而是不夠清晰 (clarity),引入了很多複雜元件和不必要的 indirection。他說 TDD 是軟件工程師 (engineer) 用的,但實際上我們更像是「軟件作家」(software writer),而該追求程式碼清晰簡潔。

有趣的是 TDD 派的人,通常不會自稱工程師 (engineer),而是工匠 (craftsman),技與藝相輔相承。軟件工匠運動其中重心就是反對將軟件活動工程化。工程化並非不好,對於超大型專案 (如太空計劃) 要管理幾百人,專案開發期跨數十年,工程學會有用。但一般而然軟體多是十幾二十人團隊的活動,付運週其也短,所以開發活動要靈活應變,瀑布式開發並不適合,迭代開發 (iterative) 更能貼緊用戶需求。而 TDD 就是迭代開發的其中一個技巧。

工匠們對程式碼追求適應性,對於需求變動,程式改動要夠小,不要改一功能改一百處。換句話說,就是要將程式中一再出現的概念、模式抽取成獨立的表達,消除程式重複,也就是所謂 DRY (Don’t repeat yourself) 原則。越少重複,未來的改動會越簡單。累積經驗後便有了設計模式(design pattern),歸納一些開發常見的問題。

可是人愛預想未來,腦裏想著將來可能會有的變動,便先將程式碼寫成設計模式的樣子,可是明明是簡單的功能,卻寫成複雜的。預想準確的話沒問題,但預想落空的話,那複雜的程式碼便存留朽壞。工匠們也知道杞人憂天開發,跟瀑布式大設計一樣,都是過度工程化了,所以他們提倡 simple design 或 evolutionary design,不要預想可能出現的重複,到你真正見到重複出現,才將程式重構 (refactor) 成模式。但空手重構是很危險的,所以要有測試為安全網。

不過工匠們會說,測試不是為了安全而已,而是為了設計。他們相信一個好的設計是可方便測試的設計,有定義好的 interface,高內聚鬆耦合。設計也夠彈性,高度模組化,可迎合改變。不過 DHH 說那樣子的程式設計,有時有太多的 “Needless indirection and conceptual overhead”,不是好的設計。他覺得好的程式碼,最好一看而懂,表達清晰,一如好作家能夠我手寫我心。

我想起初出茅廬之時,看一些大型 codebase,順藤摸瓜追蹤,都會覺得有點難,一層又一層,沒完沒了似的。不是在一個 function 內都寫出來比較「清晰」嗎?為什麼要跳來跳去呢?後來看多了才知道,那些寫法是為了管理日漸複雜的程式碼。面對需求快速變更,程式碼也得有相應的體質,盡量消除重複、分隔關注點 (separtion of concerns),既要乾淨又要管用(”Clean code that works.”)。 原來對程式碼的「美感」是需要培養的,除了看得多有經驗,也得從他人學習。重構書藉會教你辨別爛代碼,他們形容為 code smell,但現實上並不象真的臭味人類與生俱來就聞得到,是要經歷和訓練才得來的。

在此,爭論點似乎就在於大家對於程式碼的價值觀不同,一方為清晰直觀,一方為了日後維護。不過我反到可以再問,其實為什程式碼要清晰直觀呢?其實不就是為了日後好維護嗎?以作家做比喻,似乎著重了作者的自主,而缺乏了跟讀者/客戶的互動,始終現實上很少作者,會不斷重寫修改作品吧?也很少團隊式寫作吧?

DHH 特別提到一個特別為了「脫 rails」的設計模式,為的只是快速測試,令到有很多中間件出現,傷害了設計。我不是 Rails 背景,但令我想起的是 java 界為了「脫 ejb」花了何其多的力氣,發展到現在已可用簡單 POJO 組合程式,簡單、好測之外,更重要的是程式更可靈活組合了,不用被某某框架鎖定。在談 Clean Architecture 時,Uncle Bob 談到 FitNesse 開發時只用簡單 testing 包圍先寫好核心,外接 IO 就先用 text file 算了,幾次想要引入 DB 時,他們都將決定延後,結果成品竟然不用 DB 了,後來用戶社群要求引入 DB,因為介面清晰,所以也不用太花氣力就寫好 DB driver 插件了。

當然你也可以說殺雞焉用牛刀?明明是簡單一個小網絡程式罷了,在未預見會脫離某框架就先脫,不又是違反 simple design 的原則嗎?的確,所有設計問題,都是要看情景 (context) 而定,殺雞還是殺牛,就看開發者的決定。我們都知道,no silver bullet。不過在這此,無論是 unit 或 integration test,都讓你有改變心意的機會,是否 TDD 在此反而不是重點了,有 test/spec 就好。

(文章太長了,其實都沒有怎說到 TDD,有機會另文再談…)

相關連結:

*