來源: http://codurance.com/2015/05/12/does-tdd-lead-to-good-design/
做者:SANDRO MANCUSOhtml
最近我發了一則推特,「TDD 沒法帶來好設計,若是你不知道好設計長什麼樣子」,而且說咱們可能應該先教授設計再教TDD,(或者至少同時教授)。由於這條推特我和J.B. Rainsberger, Ron Jeffries,以及其餘一些人進行了一場討論。最終J.B和我在Hangout on Air進行了一場現場辯論。
若是你回顧個人講演,blog,包括個人書,你都會發現我屢次提到TDD是一種設計工具。那麼爲何我如今再也不這麼主張了呢?程序員
在仔細觀察了我是如何工做的,以及不少其餘開發人員如何工做後,我意識到沒幾我的經過TDD驅動得到了好的設計。儘管我深愛「紅燈——綠燈——重構」的節奏,但單單一個「重構」的步驟並不足以使TDD被稱爲一個設計工具。
TDD並無開出藥方來告訴你應該怎麼設計。它只是不停地纏着你,追問你:「你肯定寫成這樣麼?是否已經足夠好了?你能讓它更加完善麼?」 這種糾纏或者說是讓你保持專一設計和代碼改進的提醒,很是的棒,可是還不夠。
在我看來,TDD是一種軟件開發的流程,它帶來了不少的益處,其中包括持續不斷的提醒咱們改進代碼。然而,改進代碼到底要改爲什麼樣,TDD卻並未涉及。算法
嗯,是,也不是。我沒有忘記。可是簡單設計四規則不是TDD的一部分,而如今我在討論TDD自己。不少經驗豐富的TDD踐行者一般都把簡單設計四規則做爲重構階段設計的指導思想,包括我本身也使用它,以及其它技巧。
簡單設計四規則是衆多可選的設計思想之一。SOLID是另外一選項,領域驅動設計是又一個,另有許多其餘的設計原則和模式能夠做爲很好的設計指導。這些思想正是咱們須要裝在腦子裏來進行重構階段的。從另外一個角度來看,對現有設計思想的透徹理解也會帶給咱們更好的設計。
TDD是一種流程(而非設計工具),在這個流程中的重構階段,你能夠發揮你已經掌握的的軟件設計知識和技巧去幫助本身改進設計。數據庫
TDD有兩種主要的風格,它們在什麼時候進行設計有着至關顯著的區別。編程
古典派是由Kent Beck開創的本來風格,也被稱爲底特律式TDD小程序
設計在重構階段發生。工具
通常來講測試是基於狀態的測試。單元測試
在重構階段,由測試驅動的單元可能會分化爲多個類。學習
極少使用Mock技術,除非是要與現存系統隔離。測試
在編碼前不進行設計方面的思索。設計徹底是從代碼中浮現出來的。
是避免過分設計的極佳方法。
基於狀態的測試以及無前置設計,使得這種風格更容易理解和採用。
經常結合簡單設計四規則一塊兒使用。
當咱們知道輸入和指望的輸出,可是不清楚實現多是什麼樣的時候,很適合使用這種方式進行探索。
在咱們沒法藉助行業專家和行業語言(好比數據轉換,算法等)的狀況下頗有幫助。
單純爲了測試暴露狀態。
相比由外而內風格,重構階段一般更大。後面會詳細介紹由外而內風格。
當新的類在重構階段浮現出類時,被測的單元就會超出一個類。若是咱們單單看那個測試的話,這沒什麼問題,然而,當新的類浮現出來後,它就有了本身的生命。它會被程序的其餘部分複用。當這個類演化時,可能會破壞其它不相關的測試,由於那些測試使用了它們實際的實現而不是mock對象。
經驗不足的練習者每每會跳太重構,也就是設計改進的階段,致使開發進程變爲 紅燈—綠燈—紅燈—綠燈—...—紅燈—綠燈—大重構。
因爲探索式的特色,測試驅動下的類產生於「我想咱們須要一個有這樣接口的類」。可能沒法和系統的其它部分很好地對接。
有可能會緩慢費事。咱們經常一開始已經知道被測的類不該該有這麼多的職責,古典派的建議是等待重構階段,只在確實有必要時才抽出其它類。對於初學者來講這多是個好建議,對更有經驗的程序員純粹是浪費時間。
由外而內TDD,也被稱做倫敦派或者mock式,是由一些XP實踐先驅接受和發展出的風格。隨後它啓發產生了BDD。
不一樣於經典派,由外及內TDD爲咱們如何着手測試驅動代碼作出了規定:從外(接到外部輸入的第一個類)到內(各個將會實現系統須要的一個單一特性的類)。
通常從一個驗收測試開始,驗收測試用來檢驗是否整個功能工做。驗收測試也爲實現進行了嚮導。
驗收測試的失敗會告知咱們功能還有某些部分沒有實現的信息(數據沒有返回,消息沒有送入隊列,數據沒有存人數據庫等等),從中咱們能夠開始進行單元測試。第一個被測的類負責接收外部的請求(好比一個控制器,隊列監聽器,事件接收器,組件入口等)。
鑑於咱們知道咱們不會在一個類裏實現整個程序,咱們會假定被測試類會須要一些合做類。而後咱們在測試裏驗證被測類與其合做類之間的協做。
經過被測類須要調用合做類公開方法來完成的各類事,能夠識別出合做類。合做類的名字和方法名應該來自於行業語言中的名詞和動詞。
當一個類被徹底測試後,咱們選取一個合做類(此時應該尚未進行實現),並採用與上一個類相同的方式,經過測試驅動它的行爲。這就是咱們叫它由外而內的緣由:咱們從靠近系統輸入的地方(外部)經過不斷識別合做類,逐步向程序內部推動。
設計起始於紅燈階段,也就是開始寫測試時。
測試測的是行爲和協做,而非狀態。
在重構階段對設計進行完善。
每一個合做類以及它的公開方法都是爲了給已有的客戶類提供服務的,這使得代碼可讀性很高。
相比經典式方法,由外而內式的重構階段要小得多。
提升了封裝性,由於不須要僅僅爲了測試而暴露狀態。
更符合「tell, don't ask」設計方法。
更符合面向對象編程的本來理念:測試是關於對象間發送的消息,而非檢測對象的狀態。
適用於商業應用,從user story和驗收條件中能夠得到名詞和動詞。(做爲類名和方法名)。
對於初學者來講很難接受,由於須要更高層次的設計能力。
開發者從代碼中沒法獲得反饋來建立合做類。他們須要經過寫測試來使合做類顯現出來。
不成熟的建立合做類有可能致使過分設計。
不適用於探索式的工做,以及不特定於user story的行爲(數據轉換,算法等)。
設計能力糟糕的話可能會致使mock對象爆炸式的激增。
基於行爲的測試要比基於狀態的測試難寫。
在寫測試的時候須要具有DDD(領域驅動設計)以及其餘設計技能,包括簡單設計四規則。
兩種都應該用,不一樣的風格只是工具而已,應該根據實際須要來採用。經驗豐富的TDD實踐者會從一種風格換到另外一種,根本不操心本身在用那種風格。
有兩種設計:宏設計和微設計。當咱們用測試驅動代碼時咱們在進行微設計,主要用於經典式TDD。宏設計超出了咱們正在實現的功能。它是關於咱們如何在更高層面對領域進行建模,如何劃分應用,層次,服務等等。宏設計給咱們應用的總體結構,並讓不一樣的團隊和開發者能夠齊頭並進而不會相互阻礙。宏設計關係到商業層面如何看待應用,經常適用領域驅動設計之類的技術。宏設計也幫助保持應用的總體一致性。TDD並不能幫助咱們在這個層面進行設計。
當採用由外而內方式TDD時,咱們經常會考慮到宏設計,可是由外而內方式自己並不足以定義整個應用的宏設計。
多年以來,我看到了不少由測試驅動開發的應用最終仍然很難維護。固然,我得說比起我之前維護的那些根本沒有測試的遺留程序,它們要好得多了。
寫或不寫測試,程序員均可以搞出一團糟來。用經典式或由外而內式,程序員同樣均可以驅動出一坨x。
TDD不是設計工具。它是一種軟件開發的工做流程,在流程的整個週期裏貫徹着對代碼改進的提示。當面臨這些提示時(寫測試和重構),程序員須要瞭解一些設計方面的指導方針才能改善代碼,如:簡單設計四規則麼,領域驅動設計,SOLID,模式,Law of Demeter, Tell Don't Ask, POLA/S, Design by Contract, Feature Envy, 內聚), 耦合), Balanced Abstraction Principle,等等。僅僅說「重構」並不足以把TDD稱爲一個設計工具。
不少程序員抱怨TDD和mock讓它們寫代碼更慢了。最終他們都放棄了TDD由於太可貴到他們想要的結果。在我看來,沒有哪一個程序員會對理解紅燈——綠燈——重構的循環有什麼困難。他們苦苦掙扎的事情,是如何設計良好的軟件。
TDD最棒的一點就是它會持續不斷的問咱們,「嘿,你能把代碼改得更好一點麼?你看到這個類有多難測試了麼?好了,如今這段代碼工做了,綠燈。如今改進吧。」可是除了這些,你仍是得靠本身來寫出更好的代碼。
若是你理解了好的設計是長什麼樣子的,TDD作起來會容易不少。練習並理解現有的豐富設計原則會讓TDD容易也有用不少。而且會讓TDD的學習曲線平緩下來,也更容易被人接受。
偏激是很差的。咱們一下從太重的前期設計(BDUF)變成徹底沒有設計。拋棄咱們已有的設計知識是個錯誤。固然咱們不該該回到過分設計每件事的黑暗時代,然而那種認爲咱們只須要關注微設計的想法也是錯的。若是你是一我的搞搞,作些kata練習,或者開發一個小程序,那麼隨便你喜歡怎麼樣開發均可以。可是若是你是團隊的一部分,開發一個比練習題大不少的軟件,那麼仍是對你的團隊行行好,多關心關心宏設計以及代碼結構吧。
Sandro Mancuso
軟件工匠,做者,倫敦軟件匠藝社區(LSCC)的發起人。儘管Sandro從1996年纔開始職業生涯,他很早就開始編寫代碼了。他曾經在創業公司,軟件工做室,產品公司,跨國諮詢公司以及投行工做過。
在他的職業生涯中Sandro有機會接觸各類不一樣的項目,語言,技術以及行業。他對於在各類規模的組織中推廣軟件工匠思想以及極限編程有着豐富的經驗。Sandro因爲發展和推廣軟件匠藝精神而頗負盛名,並常常應邀在世界各地進行講演。他的職業抱負是經過幫助程序員自我改善並更關注本身的手藝,來讓整個軟件行業更上一層樓。