今年春節時,我寫了一篇《TDD并不是看上去的那么美》,在這篇文章中我列舉了一些關于使用TDD的一些難點和對TDD的質疑,后來出現了一些爭論(可參見那篇文章的評論),以及Todd同學的《TDD到底美不美》,還有infoQ中文上的那個幾乎沒有營養離線討論。今天,有網友給我推來一個英文版infoQ的視頻——“Coplien and Martin Debate TDD, CDD and Professionalism”,這是2008年2月18日的視頻,視頻的主角兩個人爭論TDD好還是不好,一個是敏捷社區的教主級的人物——Robert Martin(大家稱之為“Bob大叔”),另一個是C++,OO,多范式編程的大師Jim Coplien(大家都叫他Cope)。這兩個人對TDD的見解有分歧。Coplien的很多觀點和我之前的不謀而合,而他自己稱他是堅決強烈地站在TDD的對立面上。下面是Jim的原話:
I have adopted a very strong position against what particularly the XP community is calling test driven development.
InfoQ的視頻很多時候相當的不給力,就像有前列腺的患者撒尿一樣,半天都擠不出一滴。不過,好在那里有這兩個人對話的摘錄。在這里,我給大家摘要一下:
——————————————————正文分割線————————————————————
Coplien首先讓Uncle Bob定義了一下TDD,Uncle Bob說明了他的三個法則:(敏捷的同學一定不陌生)
-
一個測試驅動的程序員,其不會在寫出一個測試失敗的Unit Test前,去寫一句可用在生產線上的代碼。(沒有測試之前不要寫任何功能代碼)
-
在編寫用于生產線上代碼之前,不寫過多的測試失敗的Unit Test。(只編寫剛好能體現一個失敗情況的測試代碼)
-
在現有代碼通過Unit Test前,不寫更多的用于生產線上的代碼。(只編寫恰好能通過測試的功能代碼)
Coplien說他有意見的不是這三個法則,而是因為這個三個法則是孤立說出來的。Coplien說他和一些咨詢師或是Scrum Master參與過很多的項目,他們發現這些項目都有兩個問題:
-
他們使用TDD的時候,軟件沒有一個架構或是framework。當然,Kent Beck說——TDD可以驅使你去做架構。但是,TDD和Unit Test 是一回事嗎?Unit Test是一個偉大的事,尤其是當你去寫API和類庫的時候。今天XP所說的TDD和UT很不一樣。如果你使用TDD來驅動你的軟件系統架構,那么,基本上來說,三個迭代以后,你開發的軟件就會crash掉,而且無法再往前開發。 因為什么?因為連軟件團隊自己都受不了這三個迭代出來的架構,而且你還會發現,你根本沒去去重構。
-
第二個問題是,TDD這種方法破壞了GUI(圖形界面),就算是Kent也說:“你永遠不可以在一個漂亮的界面后面隱藏一個糟糕的架構”,Coplien強烈地相信軟件的架構是通過界面來發出其光芒。他覺得如果沒有一個好的軟件架構,這個會影響用戶的操作。
Coplien接著說,如果我們使用Uncle Bob的三條法則,我們也許沒有什么問題,但Coplien想告訴大家另一個非常重要的事,那就是軟件架構。并說:“我根本不接受TDD是軟件專業化實踐的論點”。
Bob大叔說,讓我們回到99年,那時的敏捷社區覺得軟件架構是無關的,不需要軟件架構,只需要做一堆tests,做一堆stories,以及足夠快的迭代,這樣就可以讓那些代碼魔幻式地拼裝起來,這就是horse shit。對于大多數的敏捷擁護者來說,這的確是愚蠢的。今天你再和Knet說這個事,他也會說那不過是一種說法。
Coplien回應到,實際上,Knet在解釋XP的時候,在他的書131頁的位置說過,“是的,你得做些前期的架構,但也別把自己搞亂了”。
Bob大叔把話題轉回來,繼續聊關于架構方面的事,他說軟件的架構很重要,他也寫很一些關于架構的書,他說他也是一個架構方面的怪才,但是他認為架構自己并不會形成軟件的所有的外表。他覺得好的軟件架構和設計能力應該出現在若干次迭代之后。他覺得你在架構軟件的時候,你會創造一些東西,也會破壞一些東西,并且會在幾次迭代中做一些試驗性的工作,來嘗試一下不同的架構。在2到3次迭代以后,你可以知道那一種架構是對的,這樣,你可以在后面的迭代中進行調整 。因此,他認為架構是需要進化和發展的,而不會因為被可執行的代碼所形成,也不會因為你所寫的測試而形成。
Coplien贊同架構進化的觀點,而且他相信軟件的架構的演變和進化不是因為你寫的代碼,也不是因為Use Case,也不是告訴你你的軟件需求的范圍和其中的關系,但是如果你做的方法是以增量式的,以用戶驅動式的,而你卻在和用戶溝通時沒有一些前期的業務知識,那么這一定是相當有風險的,并且你一定會把事搞砸的。
Coplien接著說,他在Knet早期提到TDD的時候和Knet時,提到YAGNI(陳皓注:You Aren’t Gonna Need It,XP的一個法則,也就是只做最簡單的事)時,Kent說到:“讓我們來做一個銀行帳戶,一個儲蓄帳戶”,儲蓄帳戶其實就是對余額進行一些加加減減的事,就像一個計算器一樣。Copilen繼續解釋到,但是如果你要做一個真正的銀行系統,你的軟件架構根本不可能從一個儲蓄帳戶的對象(計算器)重構出來。因為儲蓄帳戶根本就不是一個對象,其是一個流程,后面有一個數據庫的查帳索引事務,還有存款保證多和利息,還有一些轉帳功能。就算是這樣,這也只是用戶的功能,你還需要支持稅務人員和精算會計師等這些人,這會讓銀行系統成為一個錯綜復雜的軟件架構,這絕對不是你可以用迭代干出來的事。當然,Bob大叔是可以的,因為他有40年的銀行系統的經驗。但是Bob大叔你的這40年可真不敏捷啊。
Coplien接著說, 因為Bob大叔可以在軟件前期做很多很重要的決定,這讓得后面的事變得相對比較簡單。Coplien根本不相信只要你把代碼往那一放,在上面披上一層皮,再設置好一些角色,設置好接口,在文檔里寫上整個業務結構,而你只有在有人花錢的時候你才會在其中填充進真正的代碼,反之就違反了你的YAGNI原則。所以,你只是在你需要的時候做你要做的事,但你卻還是要提前得到你的軟件架構,否則你一定會把你自己逼進死角的。
Bob大叔辯解到,我說的可能和你說的這個有點不同。我們應該不會像你所說的往接口中寫一些抽象成員函數,而是創建一些有抽象接口的對象。當然,我不會把一下子為這個對象裝載上一堆方法。那些是我需要使用測試驅動或是需求驅動來做的事,我還會隨時隨地在看是否哪里軟件架構可以讓我拆分接口。
Coplien說,問題 是你得知道你要干什么?他說他非常同意Knet的書”XP Explained”里說的——“你不能去猜”,然后他舉了一個例子,一個他曾經在一個電信項目中重新架構軟件的例子,這是一個長途交換機的項目,項目組特別喜歡用面向對象,有一個人需要去做一個“Recovery Object”(應該是系統恢復對象),Coplien說這是很扯的一件事,因為系統恢復根本就不是一個對象,因為他對業務不熟,所以想這么做。而當你在細節上分析的時候,你會發現這根本就不是一個有成員方法的對象。我個人認為,Coplien想用這個例子來說Bob大叔的先定義對象的抽象接口并不是一個好的需求分析的方法。Coplien還說,這個事情今天被資本化成了SOA,真是在玩火啊。
Bob大叔說,這個他很同意。你的確需要知道這個對象的意義是什么。而且他和Coplien都同意應該根據可運行的代碼來決定未來,而不是基于投機心理搞一個巨大無比的架構。
此時,Bob大叔把話題又帶回原地,他問Coplien:“你需要多少的時間才能寫出可運行的代碼?是不是一個系統需要寫200萬行代碼才能算?”,Coplien說,在他的經歷中,200萬行代碼算是小項目了,他的項目都是幾億行代碼的。而在讓代碼可以跑起來,他至少需要讓所有的對象都聯系起來。
Bob追問到,“那么你是怎么測試這些對象的連接性的?”,Coplien說,我當然要測試,我會測試系統啟動和停止,看看有沒有內存問題,半小時就好了。Bob大叔似乎找到了突破點,于是說到:“Excellent!那么我們間的分歧是什么呢?也許你只是不同意TDD的概念和其專業化,當然,這是另外一個話題了”。
然后,Coplien說了一段我非常非常認同的話——“我看到很多人正在做正確的事,來避免我們之前討論的那些問題,當然那不是TDD的擴展,而是Dan North所說的BDD??梢姡浖_發中很多人在開發軟件中都是在用正確的很好的方法,而我對此有意見的是,有人把這個事說成TDD,然后人們就去買相關的書來了解TDD,并且看到“architecture only comes from tests”,我在過去6個月中聽到過4次這樣的說法,這就像你所說的,完全就是horse shit。而關于你所說的專業化的事,如果你沒有見過一個專業化你怎么知道?”。(不是嗎?大多數人都知道怎么開發軟件,而不是TDD才是專業化的軟件開發。)
然后,Bob想多談談專業化的事,Bob說,在今天,一個不負責任的程序會提交一段他沒有跑過單元測試的代碼,所以,要確定你沒有把一條沒有測試過的代碼提交到代碼庫里的最佳做法就是TDD。
Coplien完全不同意這個說法。他覺得底層的東西是更重要的。他用了一個示例來攻擊Bob大叔的這個觀點,他先是說代碼走查和結對編程都有好的有價值的地方,當然和這個話題不相關。然后他又說了Unit Test,想想我們的單元測試,可能我們的測試案例并不可能測試我們程序中參數的各種狀態,這些狀態有可能只是半打,有可能是一百個,有可能是2的32次方個,所以,我們可以命中一些狀態,也會沒有測試到一些狀態,我們的測試真的只是試驗性的,所以,如果你在測試中發現bug,你真的很幸運。
隨后,Coplien推崇了一個叫“Design By Contract” – 契約式設計的方法(我在軟件設計中那些方法中提到過,),這個方法認為軟件有前驗條件,后驗條件,還有不變的。這個方法是Eiffel項目使用的一個方法,使用這個方法你可以靜態的去做一些檢查,相當于你做了一個基礎架構來干這些事。Coplien相信這個方法有TDD所有的優點——我需要努力思考我的代碼,我需要思考軟件的外部接口,而且,Coplien發現這么做會比做測試更有效。這會讓你對那些參數的范圍考慮地更為寬廣,而不是只在測試案例寫幾個隨機分散的值來測試。
今天,Bertrand Meyer(Eiffel語言的創造者,他也不贊同TDD)把這個方法推進了一步,叫CDD – Contract Driven Development,這個是一種關注于對象間關系,其在程序運行前提條件和運行后的后驗條中達成一種契約,可以通過對契約條件的動態或靜態的檢查,來對程序的功能進行驗證。這樣可以讓你更有效地測試程序。這種方法需要對業務的重點部位非常好的了解。這是TDD很難做到的(這就是我在《TDD并不是看上去的那么美》一文中說的TDD的測試范圍是個很大的問題)。
Bob大叔似乎在努力回憶CDD和Eiffel,然后他說,TDD不就是干這個的嗎?TDD就是把契約變成單元測試,不但測試輸入,也測試返回值,這不就是先驗條件和后驗條件,而且他說,Unit Test和代碼結合得更緊,而契約沒有和代碼結合得緊密,這是他覺得很不舒服的地方。
Coplien說Bob大叔創建了不應該創建的二元論。他說代碼在哪里,UT就跟到哪里,代碼有多臃腫,UT就有多臃腫,而UT也是代碼,也會有BUG,所以,其實這真是事半功倍。還有一個最有名的示例是ADA編譯器,其使用了TDD,反而增加了代碼中的BUG,因為你的代碼多,測試就多,代碼就更多,整個代碼就太過臃腫。如果你測試中使用了斷言,這意味著你就耦合上了代碼,你的測試案例和你的代碼耦合地越多,你的代碼就越難維護。這就是我在《TDD并不是看上去的那么美》一文中說的TDD的代碼臃腫和維護問題)
Bob大叔為Coplien對代碼臃腫的說法感到驚訝。Coplien說,這就是他的經歷,他看到的。Bob大叔承認有很多混亂的測試和混亂的代碼,他覺得像XUnit這樣的工具被濫用了。Coplien打斷道,這不是要和你爭論的,我爭論的是這就是我看到大家在實踐的東西。
Bob大叔反回到,你有沒有看到CDD也被濫用的情況?Coplien說,他只覺得目前,軟件業對CDD用的還不夠。
最后,時間不夠了,Bob大叔問了一個不相干的問題,他說,我們這里有BDD,CDD, TDD, 關于DD,他不知道誰是最先第一個使用帶DD這個詞的,他說他好像記得一個RDD – Responsibility Driven Development。
Coplien對這個問題可能很無語,他只能說——“DD,這是Unix的一個命令嘛,Disk Dump,但這可能算。謝謝你Bob,很高興又一次見到你 ”
——————————————————正文分割線————————————————————
看完后,我的感覺如下:
-
這是2008年就在討論的事,而在2011年我發布了《TDD并不是看上去的那么美》后中國這邊才開始討論。(InfoQ和 Thoughtworks怎么不去找Coplien?)
-
英語很重要,不懂英語,只看國內的東西,你就容易被洗腦,你就需要更多的時間和精力去思考那些早被人思考過的問題。
-
開發和測試,都是需要充分地了解業務,充分的思考,充分權衡后才能做得好的事。并不是你用了哪個方法后就專業了,就NB了。
-
相當BS——上不談業務,下不談技術,只談方法論的人和公司,這是絕對的扭曲。
轉自:http://coolshell.cn/articles/4891.html
原創文章,作者:s19930811,如若轉載,請注明出處:http://www.www58058.com/2139