jim yeh on 一月 7th, 2010

昨天寫的〈測試驅動開發要徹底重構〉,曾經提到 Steven 說到「如果像閣下文中說:『系統不斷演進及需求不斷改變之下,也可能會使架構或設計愈來愈複雜而變得難以維護』,我則認為是在進行 TDD 時候沒有徹底去重構系統。」,同人認為這段話有根本上的邏輯的繆誤,於是回應:

假如上面的話是成立的,那麼代表只要徹底重構系統就不會造成「系統不斷演進及需求不斷改變之下,也可能會使架構或設計愈來愈複雜而變得難以維護」嗎?如果是這樣,那 XP 根本就不需要 Refactoring 這個實務來改善程式的結構,因為你都徹底重構程式,程式結構變差的情況是根本不可能發生。

而且依照本人 20 年來軟體開發的經驗,我還不沒有看到有程式一開始就寫得很好,到後來可以不用改變架構而符合新需求的。倒是隨著對問題的更清楚,或是程式需求的變化,讓程式必須重構的現象時常履見不鮮!

所以如果自以為自己博覽群書,很懂得TDD的實務。也不要忘了用心思考,以免自己對TDD的最佳實務的認知,不小心犯了根本上的邏輯繆誤?

結果,後來同人在 Facebook 的 Scrum community in Taiwan 看到 Steven 做了以下的回應:

先說件簡單易明的事情,其實我很不喜歡閣下把 TDD 放到 "精神" 層次,卻忘記了基本步驟。

之前的討論根本連事實層次都被忽略,根本談不上是什麼多少年的經驗或者如何用心思考,連 TDD 的最基本步驟也忘記,TDD 的基本步,不是閣下做多少年工作就可以把人家的定義去改變的,我也不明白為何有 20 年工作經驗就可以把 "Refactoring" 說成 "並不必然是 TDD 的必要的步驟",這不是邏輯問題,更不是有過什麼開發經驗然後用心思考就可以改變的事情,這是就算對軟體開發的認知不夠閣下那麼 "全面" 的都能看出的謬誤,如果閣下這樣就認為是因為說不過閣下就建議多看書本,本人深表遺憾。

我是來討論問題的,我沒有興趣去傷害閣下感情,好好閱讀書本,只是反映 TDD 三個步驟是什麼根本不存在爭議,更沒有 "好好閱讀什麼書或文章才能跟我討論" 的意思,我還未自大得要別人看過多少書才可以討論問題,亦正如討論問題我也不用跟別人說我有多少年工作經驗一樣,而且本人是衷心認為多讀書是有益的(不管是閣下還是什麼人),多讀書亦不是為上來辯論的,不過閣下如果感到有所不悅的,我就先行道歉,還是希望冷靜一點討論問題。

而簡單的例子也是思辦的過程的一部份,一方面是簡單易明地討論問題,另一方面是如果連簡單的例子也說不通,又怎麼能去談更複雜的問題呢?

上面提到:"那XP根本就不需要 refactoring 這個實務來改善程式的結構,因為你都徹底重構程式,程式結構變差的情況是根本不可能發生。"

不如冷靜一點再讀讀這句子,"XP 不需要 Refactoring 是因為徹式地進行 Refactoring",我就看不明白這是什麼邏輯,一邊說不需要,另一邊說徹底地進行。

這裡的問題是軟件是會改變的,可能是新增功能,也可能發現有其他問題,每次帶來的變更其實都需要進行重構的。所以說 XP 不需要 Refactoring 也不正確。

世上的確沒有一寫就好的代碼,而且世界是會變的,Refactoring 就是避免以後的更改越來越困難。把 "徹底地進行重構" 理解成 "XP 不需要重構這實踐" 完全沒有邏輯可言。

我也沒有反對不用改變程式架構就能滿足新需求,亦沒有否定 Refactoring 的重要性,只不過我還是建議新的功能以 TDD 方式進行開發,有測試、有代碼、然後進行重構的。

在足夠測試覆蓋下進行重構是可使系統在不斷演進及需求不斷改變之下,使架構或設計仍然處於可以維護的狀態,相反我指出的是,如果程式架構和設計越來越難維護,是重構的力度不足夠。

前面還提到:"但重構的目的為何?就重構的定義在不改變功能的情況下改善程式結構,以增加程式碼的彈性以利未來增加或改變功能。因此如同那第三步所言,為了去掉重覆性而重構。"

如果重構只是為了去掉重覆性,那 TDD 的第三步不如叫 "Remove Duplication" 好了,無可否認代碼重覆是很常見的問題,但把這裡的重構限制成消除代碼其實會局限系統的將來發展,而且到了 TDD 的第三步,系統是應該有足夠的測試去覆蓋系統,重構的力度沒理由只局限於新增功能和現有程式的重覆。

我就相信閣下是 Refactoring 的專家,也應該會知道一些 Refactoring 的模式是完全相反的,例如 "Pull Up Method" 和 "Push Down Method",更是需要觀察當時的情況來作決定,而沒有一面倒那個才是好的模式,我實在不明白為何要把 TDD 的 Refactoring 局限到只做 "去掉重覆性"。

這是實務上會發生的事情,消除代碼重覆以外的重構還是會發生的,如果只是單單只是 "消除重覆",這會是另一個我認為進行重構不夠徹底的事情。

上面已經不單單是用心思考,而是由理論到實踐都可以看得到的事情。

跟討論 Refactoring 和 TDD 觀點以外的聲音,就引用 Chet Henderickson 的說法,全部都是我錯好了,現在可以解決問題嗎?

看了 Steven 的回應,同人當下的反應是不想浪費時間與心力與他周旋下去,但後來想到或許是因為 1/9 敏捷開發分享會我要分享實施 XP 的經驗與心得,也許這是一個巧妙的同步事件。我可以趁這個機會,導正對 XP 或是 Agile 的一些錯誤觀念。

例如敏捷開發並不是教條式的照本宣科,開發者要懂得變通最重要的是用心思考,而非把必要的思考都看成精神層面的問題,這並非適用於敏捷開發的心智模式。以下是同人在 Facebook 的 Scrum community in Taiwan 的回應,但一些詞句有略為做過一番修飾,以清楚表達我對測試驅動開發步驟的看法。

呵呵,Steven Mak 的回應很有趣,把別人說成錯的不代表自己就是對的。這跟他先前面對觀點的差異就要人好好的看書的行為是如出一轍,但問題是要別人好好看書也不代表說這句話的人書看得比別人廣泛或是深入。更何況,看很多書是一回事,有沒有讀懂書中作者所傳達的意思又是另外一回事。從 Steven 喜歡用斷章取義的方式解讀我的觀點,以抽取他想要的意義來看,恐怕他看書的目的只是揀選他要的部分,沒有弄懂作者想要傳達的意思的可能性可能居多吧!

對了,我忘了 Steven 的中文可能不夠好,沒辦法弄懂我所表達的意思。如果是這樣,他其實大可以告訴我,我不會叫他去好好地學中文,或是嘲笑他那麼簡單的句子都看不懂,而是想辦法要怎麼表達才會讓他了解我的想法。省去他猜錯我的意思,而顯露出他並沒用心體會或是根本不想思考別人的觀點的窘態。

當然,以上的假設可能是不成立的,他的中文其實很好。但如果是這樣,他的邏輯思維能力真的要加強,因為思考為什麼是最基本的能力,不是什麼經神層次。

記得去年同人應邀到中山大學演講,有機會與鄭炳強教授在餐敍之中交流軟體開發的觀念。他特別強調軟體工程最重要的不是 know how,而是 know why。因為遇到不同需要而要採用適當的方法,唯有具備 know why 的能力才能做到。以 Steven 那麼重視看書來增加知識的態度來看,他應該也很重視軟體工程的學界權威的看法吧?可是如果鄭教授看到 Steven 把 know why 的思維定位成精神層次,我想也很可能會令他很搖頭吧!

回歸正題,Robert C. Martin 在《敏捷軟體開發》這本書(林昆穎,吳京子,2005)中提到測試驅動開發法,他說:

所有的產出的程式碼都是為了讓失敗的單元測試通過而編寫的。首先,我們先寫一個單元測試案例,由於它所要測試的功能並不存在,所以它不會通過,然後我們再編寫程式碼,使得測試案例通過。

編寫測試案例與程式碼之間的反覆來回非常快速。只花費一會兒的功夫。測試案例與程式碼一起演進,而測試案例會比程式碼更早一點。

結果,一組非常完整的測試案例隨著程式碼一起增長,這些測試可讓程式員檢驗程式是否正常運作。如果有一組小搭檔做了些小修改,他們可執行這些測試案例,以確保修改並未對程式碼造成任何破壞。這會極有利於進行重整。

咦,堂堂一位敏捷開發大師級人物,怎麼他也沒提到 TDD 要重構,只說可以有助於未來的重構,難道他也擅改了 TDD 的步驟了嗎?讓我們看另外一本書。點空間的朱子傑和陳盈學翻譯的《The object primer 3/e 中文版-靈活模型驅動開發與UML2》中有更具體說明TDD的步驟,而且還附了流程圖(有興趣自己買書來看)。

  1. 快速地加入一段測試,基本上只要能夠測試一段程式碼即可,此時你的測試結果會是失敗的。
  2. 執行你的測試,一般是全部的測試套件(test suite)。有時候為了速度上的考量,你可能只決定執行這一部分的測試。目的是確認新的測試結果確實是失敗的。
  3. 更新你的功能程式碼,使得測試可以通過。
  4. 再次執行你的程式。
  5. 如果測試還是失敗,再執行步驟3。
  6. 如果通過測試,便重新開始(此時若有重複的程式碼,你便需要重構你的設計)

上面的步驟有提到重構,但也提到重構的前提是如果有重覆的程式碼才要重構。那如果沒有重覆程式碼,那還需要重構嗎?還是如 Steven 所說,去除重覆的程式碼的重構還是不夠徹底的重構?

答案很顯而易見,TDD的步驟本來就沒有規定一定要重構。事實上,重構對有經驗的開發者就是像吃飯或喝水般如此直覺,在開發程式的過程中,諸如像改變函數或變數的名字讓它們變得有意義或容易了解,一段重覆被呼叫的程式碼把它們提取成函數或類別。他們經常無意識地去重構,但有意識地面對他們開發過程所遇到的問題,諸如程式碼的重覆或不易了解等問題,而不是漫無目的的為重構而重構。

是以在開發功能的程式碼,因為編程手法的熟練,重覆碼根本就不存在,難道還需要一個重構的 process 嗎?由此可知,Steven 的徹底重構之說,其實就是邏輯上所謂的以偏概全,用部分的事實擴大到全面性的觀點。但真相是 TDD 未必要進行重構,除非你需要它。所以這當然是邏輯的問題,其實從我指出他邏輯的矛盾之後,他的反應更顯出欠缺邏輯的思辨能力。

不如冷靜一點再讀讀這句子,"XP 不需要 Refactoring 是因為徹式地進行 Refactoring",我就看不明白這是什麼邏輯,一邊說不需要,另一邊說徹底地進行。

Steven 上面的質疑乍看來似乎有道理,但仔細看看,原來他把時間的因素拿掉了,有偷換觀念的嫌疑。我原先的說法是說因為先前徹底地重構,所以未來就不需要重構了,被他偷換成一邊說不需要,另一邊說徹底進行。

照他原來的邏輯,如果先前徹底重構的程式碼到後來還需要再次重構,那原來的重構還能叫徹底重構嗎?我們常看到有些人會在問題發生的時候,會說因為別人寫的程式碼出問題,是因為沒有徹底的重構,但在明眼人的眼中,這種說法可以用台語說「出一支嘴」!要嘛有徹底重構先見之明的人,你就再一開始就告訴大家什麼叫做徹底的重構,保證以後不會出問題,省得以後出了問題再說重構不夠徹底,後見之明說重構不夠徹底,這種說法誰都會說但卻根本做不到!

更何況重構的方法有好幾種,決定該怎麼重構的時間還沒到,如何徹底重構?或許有人會想依據未來的預測來徹底重構,但這樣想的人大概忘了敏捷方法是不進行預測的,只有因應現實而改變。

蘇格拉底說:「事情的好壞不重要,重要的是你的想法和看法。」本來針對事情來討論是一件好事,目的是為了思考彼此之間的差異點而獲得成長。無奈 Steven 一開始為了捍衛他的觀點,就針對和他觀點不同觀點的人,拼命強調同人說 TDD 不必然需要 Refactoring 的觀念是錯的,叫我要多看書暗示我不夠用功,其實這樣的行為模式突顯他面對意見紛歧沒有反省思辨的能力。

至於同人提出邏輯思辨的想法就叫做精神層面的說法,同人只能說這其實表現出 Steven 自以為是的「自我感覺良好」,運用修辭技巧閃來閃去還是難逃嚴謹的邏輯批判。他要表演這些同人其實沒什麼意見,他可以繼續活在他的象牙塔之中。但對於同人而言,我的收穫只是多看到一個負面教材,這對我 1/9 敏捷開發分享會的聽眾和閱讀我文章的讀者來說可是一大貢獻呀。Steven 說他不小心傷害我的感情;呵呵,少來了,不禮貌與不尊重的行為代表他不夠理性,只是顯出他的無理而已。



     

One Response to “測試驅動開發的步驟”

  1. [...] 延伸閱讀:(其它與測試驅動開發與重構的文章) 測試驅動開發的精神 測試驅動開發要徹底重構? 測試驅動開發的步驟 [...]

Leave a Reply

You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="">