(這篇文章是我在 XP2000 研討會發表的演說,它會公布在研討會講義中。)
Planned and Evolutionary Design (經過規劃的設計與演進式的設計)
The Enabling Practices of XP (XP有效的實作技巧)
The Value of Simplicity (簡單的價值)
What on Earth is Simplicity Anyway (究竟什么是簡單)
Does Refactoring Violate YAGNI? (重構違反了YAGNI嗎?)
Patterns and XP (模式與XP)
Growing an Architecture (發展結構)
UML and XP (UML與XP)
On Metaphor (關于隱喻)
Do you wanna be an Architect when you grow up? (你將來想成為一個軟件結構師嗎?)
Things that are difficult to refactor in (很難重構的東西)
So is Design Dead? (所以,設計死了嗎?)
Acknowledgements (致謝)
Revision History (修訂的記錄)
Extreme Programming (XP) 挑戰很多軟件開發常見的假設。其中最受爭議的就是極力排斥 up-front design,而支持一種比較屬于演進的方式。批評者說這是退回到了 "code and fix" 的開發方式,頂多只能算是一般急就章的程序設計罷了。支持者也?吹 XP 對于設計方法 (如 UML)、principle(設計準則)、patterns 等的排斥。別擔心,仔細注意你的程序代碼,你就會看到好的 design 浮現出來。
我發現自己正陷于這個爭論當中。我的工作著重在圖形化設計語言 - UML 以及 patterns,事實上我也寫過 UML 和 patterns 的書。我如此的擁抱 XP 是否表示我放棄了這些理論,或是將這些反漸進式 (counter-revolutionary) 的概念從腦中清除了?
嗯... 我不想讓你的心懸蕩在這兩種情境中。簡單的說并不是;接下來的文章就讓我來詳細說明。
Planned and Evolutionary Design
我將在這篇文章中說明軟件開發的兩種設計方式是如何完成的;蛟S最常見的是演進式設計。它的本質是系統的設計隨著軟件開發的過程增長。設計 (design) 是撰寫程序代碼過程的一部份,隨著程序代碼的發展,設計也跟著調整。
在常見的使用中,演進式設計實在是徹底的失敗。設計的結果其實是一堆為了某些特殊條件而巧妙安排的決定所組成,每個條件都會讓程序代碼更難修改。從很多方面來看,你可能會批評這樣根本就沒有設計可言,無疑地這樣的方式常會導致很差勁的設計。根據Kent的陳述,所謂的設計 (design) 是要能夠讓你可以長期很簡單地修改軟件。當設計 (design) 不如預期時,你應該能夠做有效的更改。一段時間之后,設計變得越來越糟,你也體會到這個軟件混亂的程度。這樣的情形不僅使得軟件本身難以修改,也容易產生難以追蹤和徹底解決的 bug。隨著計畫的進行,bug 的數量呈指數地成長而必須花更多成本去解決,這就是 "code and fix" 的惡夢。
Planned Design 的做法正好相反,并且含有取自其它工程的概念。如果你打算做一間狗屋,你只需要找齊木料以及在心中有一個大略的形象。但是如果你想要建一棟摩天大樓,照同樣的做法,恐怕還不到一半的高度大樓就垮了。于是你先在一間像我太太在波士頓市區那樣的辦公室里完成工程圖。她在設計圖中確定所有的細節,一部份使用數學分析,但是大部分都是使用建筑規范。所謂的建筑規范就是根據成功的經驗 (有些是數學分析) 制定出如何設計結構體的法則。當設計圖完成,她們公司就可以將設計圖交給另一個施工的公司按圖施工。
Planned Design 將同樣的方式應用在軟件開發。Designer 先定出重要的部份,程序代碼不是由他們來撰寫,因為軟件并不是他們 "建造[譯注3]" 的,他們只負責設計。所以 designer 可以利用像 UML 這樣的技術,不需要太注重撰寫程序代碼的細節問題,而在一個比較屬于抽象的層次上工作。一旦設計的部份完成了,他們就可以將它交給另一個團隊 (或甚至是另一家公司) 去 "建造"。因為 designer 朝著大方向思考,所以他們能夠避免因為策略方面不斷的更改而導致軟件的失序。Programmer 就可以依循設計好的方向 (如果有遵循設計) 寫出好的系統。
Planned design 方法從七○年代出現,而且很多人都用過它了。在很多方面它比 code and fix 漸進式設計要來的好,但是它也有一些缺點存在。第一個缺點是當你在撰寫程序代碼時,你不可能同時把所有必須處理的問題都想清楚。所以將無可避免的遇到一些讓人對原先設計產生質疑的問題?墒侨绻 designer 在完成工作之后就轉移到其它項目,那怎么辦?Programmer 開始遷就設計來寫程序,于是軟件開始趨于混亂。就算找到 designer,花時間整理設計,變更設計圖,然后修改程序代碼。但是必須面臨更短的時程以及更大的壓力來修改問題,又是混亂的開端。
此外,通常還有軟件開發文化方面的問題。Designer 因為專精的技術和豐富的經驗而成為一位 designer。然而,他們忙于從事設計而沒有時間寫程序代碼。但是,開發軟件的工具發展迅速,當你不再撰寫程序代碼時,你不只是錯失了技術潮流所發生的改變,同時也失去了對于那些實際撰寫程序代碼的人的尊敬。
建造者 (builder[譯注3]) 和設計者之間這種微妙的關系在建筑界也看得到,只是在軟件界更加凸顯而已。之所以會如此強烈是因為一個關鍵性的差異。在建筑界,設計師和工程師的技術有清楚的分野;在軟件界就比較分不清楚了[譯注2]。任何在高度注重 design 的環境工作的 programmer 都必須具備良好的技術,他的能力足夠對 designer 的設計提出質疑,尤其是當 designer 對于新的發展工具或平臺越來越不熟悉的狀況下。
現在這些問題也許可以獲得解決。也許我們可以處理人與人之間的互動問題。也許我們可以加強 designer 的技術來處理絕大部份的問題,并且訂出一個依照準則去做就足夠改變設計圖的流程。但是仍然有另外一個問題:變更需求。變更需求是軟件項目中最讓我感到頭痛的問題了。
處理變更需求的方式之一是做有彈性的設計,于是當需求有所更改,你就可以輕易的變更設計。然而,這是需要先見之明去猜測將來你可能會做怎樣的變更。一項預留處理易變性質的設計可能對于將來的需求變更有所幫助,但是對于意外的變化卻沒有幫助 (甚至有害)。所以你必須對于需求有足夠的了解以隔離易變的部份。照我的觀察,這是非常困難的。
部份有關需求的問題應該歸咎于對需求的了解不夠清楚,所以有人專注于研究需求處理,希望得到適切的需求以避免后來對設計的修改。但是即使朝這個方向去做一樣無法對癥下藥。很多無法預料的變更起因于瞬息萬變的商場,你只有更加小心處理需求問題來應付無法避免的情況。
這么說來,planned design 聽起來像是不可能的任務。這種做法當然是一種很大的挑戰。但是,跟演進式設計 (evolutionary design) 普遍以 code and fix 方式實作比較起來,我不覺得 planned design 會比較差。事實上,我也比較喜歡 planned design。因為我了解 planned design 的缺點,而且正在尋找更好的方法。
The Enabling Practices of XP
XP 因為許多原因而備受爭議,其中之一就是它主張演進式設計 (evolutionary design) 而不是 planned design。我們也知道,演進式設計可能因為特定的設計或是軟件開發趨于混亂而行不通。
想了解這些爭論的核心,就是軟件研發異動曲線。曲線的變化說明,隨著項目的進行,變更所需要的成本呈現指數的增加。這樣的曲線常以一句話來表示:在分析階段花一塊錢所作的變更,發行之后要花數千元來補救。諷刺的是大部分的計畫仍然沒有分析過程而以非標準的方式進行,但是這種成本上的指數關系還是存在著。這種指數曲線意味著演進式設計可能行不通,它同時也說明著為什么 planned design 要小心翼翼地規劃,因為任何的錯誤還是會面對同樣的問題。
XP 的基本假設是它可以將這種指數曲線拉平,這樣演進式設計就行得通了。XP 使曲線更平緩并能運用這種優勢。這是因為 XP 實作技巧之間的耦合效果:換句話說,不使用那些能夠拉平軟件開發曲線的實作技巧來工作,這條曲線也不會趨向平緩。這也是爭論的來源,因為評論家不了解這其間的關系。通常這些批評是根據評論家自身的經驗,他們并沒有實行那些有效的實作技巧,當他們看到結果不如預期,對于 XP 的印象也就是這樣了。
這些有效的實作技巧有幾個部份,主要是 Testing 和 Continuous Integration。如果沒有 testing 提供保障,其它的 XP 實作技巧都不可行。Continuous Integration 可以保持團隊成員信息同步,所以當你有改變的部份,不必擔心與其它成員資料整合會有問題。同時運用這些實作技巧能夠大大影響開發曲線。這讓我再次想起在ThoughtWorks 導入 testing 和 continuous integration 之后,明顯的改善了研發成果。改善的程度好到令人懷疑是不是像 XP 所主張的,必須要用到所有的實作技巧才能大幅改善效率。[譯注4]
Refactoring 具有類似的成效。那些曾經采用 XP 建議的原則來對程序代碼進行refactoring 的人發現,這么做要比無章法或是特殊方式的 restructuring 明顯的更有效率。那也曾經是 Kent 指導我適當的 refactor 得到的難忘經驗,也因為這么一次巨大的轉變促使我以這個主題寫了一本書。
Jim Highsmith 寫了一篇很棒的文章 "summary of XP",他把 planned design 和 refactoring 放在天秤的兩端。大部份傳統的做法假設構想不變,所以 planned design 占優勢。而當你的成本越來越不允許變更,你就越傾向于采用 refactoring。Planned design 并不是完全消失,只是將這兩種做法互相搭配運用取得平衡。對我來說,在設計進行 refactoring 之前,總覺得這個設計不夠健全。
Continuous integration、testing 和 refactoring 這些有效的實作方法讓 evolutionary design 看似很有道理。但是我們尚未找出其間的平衡點。我相信,不論外界對 XP 存有什么印象,XP 不僅僅是 testing、coding 和 refactoring。在 coding 之前還有 design 的必要。部份的 design 在 coding 之前準備,大部份的 design 則發生在實作每一項詳列的功能之前?傊,在 up-front design 和 refactoring 之間可以找到新的平衡。
The Value of Simplicity
XP 大聲疾呼的兩個口號是 "Do The Simplest Thing that Could Possibly Work"(只做最簡單可以正常運作的設計) 和 "You Aren't Going to Need It"(就是 YAGNI - 你將不會需要它)。兩項都是XP實務中簡單設計的表現形式。
YAGNI 一詞時常被討論,它的意思是現在不要為了將來可能用到的功能加入任何程序代碼。表面上聽起來好象很簡單,問題則出在像 framework、重用組件、和彈性化設計,這些東西本來就很復雜。你事先付出額外的成本去打造它們,希望稍后將這些花費都賺回來。這個事先彈性設計的想法被認為是軟件設計有效率的關鍵部份。
但XP的建議是,在處理第一個問題時不要因為可能需要某項功能,就建造出彈性的組件組及框架出來。讓整體結構隨著需要成長。假如我今天想要一個可以處理加法但是不用乘法的 Money 類別,我就只在 Money 類別中建造加法的功能。就算我確定下一個階段也需要乘法的運算,而且我知道很簡單,也花不了多少時間,我還是會留到下一階段再去做它。
其中一個理由是效益。如果我要花時間在明天才需要的功能,那就表示我沒有將精神放在這個階段應該完成的事情上。發表計畫詳列目前要完成的事項,現在做以后才需要的事情違背開發人員和顧客之間的協議。這種做法有讓現階段的目標無法達成的可能。而且這個階段的 stroies[譯注5] 是否具有風險,或是需不需要做額外的工作,都是由顧客來決定的 - 還是可能不包括乘法功能。
這種經濟效益上的限制是因為我們有可能出錯。就算是我們已經確定這個功能應該如何運作,都有可能出錯 - 尤其是這時候我們還沒有取得詳細需求。提前做一件錯誤的事情比提前做一件對的事情更浪費時間。而且XP專家們通常相信我們比較有可能會做錯而不是做對(我心有戚戚)。
第二個支持 simple design 的理由是復雜的設計違反光線行進的原理。復雜的設計比簡單的設計還要令人難懂。所以隨著漸增的系統復雜度,更加難以對系統做任何修改。如此,若系統必須加入更復雜的設計時,成本勢必增加。
現在很多人發現這樣的建議是無意義的,其實他們那樣想是對的。因為你所想象一般的研發并沒有被 XP 有效的技巧所取代。然而,當規劃式設計和漸進式設計之間的平衡點有了變化 (也只有當這樣的變化發生時),YAGNI 就會變成好的技巧。
所以結論是,除非到了往后的階段有所需要,否則你不會浪費精神去增加新的功能。即使不會多花成本,你也不會這樣做,因為就算現在加入這些功能并不增加成本,但是卻會增加將來做修改時的成本?傊,你可以在套用 XP 時明智的遵守這樣的方法,或是采取一種能降低成本的類似的方法。
What on Earth is Simplicity Anyway
因此,我們希望程序代碼能夠越簡單越好,這聽起來沒什么好爭論的,畢竟有誰想要復雜呢?但問題來了,究竟 "什么才叫簡單呢?"
在 XPE 一書中,Kent 對簡單系統訂了四個評量標準,依序是 (最重要排最前面):
通過所有測試。
呈現所有的意圖。
避免重復。
最少數量的類別或方法。
通過所有測試是一項很普通的評量標準,避免重復也很明確,盡管有些研發人員需要別人的指點才能做到。比較麻煩的是 "呈現所有的意圖"這一項,這到底指的是什么呢?
這句話的本意就是簡單明了的程序代碼。XP 對程序代碼的易讀性有很高的標準。雖然在 XP 當中,"巧妙的程序代碼 (clever code)" 這個字眼經常被濫用,不過意圖清楚的程序代碼,對其他人來說真的是一種巧妙。Josh Kerievsky 在 XP 2000 論文中舉了一個很好的例子,檢視在 XP 領域可能是大家最熟知的 JUnit 的程序代碼。JUnit 使用 decorators 在 test cases 中加入非必要的功能,像是同步機制及批次設定等,將這些程序代碼抽出成為 decorator,的確讓一般的程序代碼看起來清楚許多。
但是你必須捫心自問,這樣做之后的程序代碼夠簡單嗎?我覺得是,因為我了解 Decorator 這個 patterns。但是對于不了解的人來說還是相當復雜的。類似的情況,JUnit 使用 pluggable method,一種大部分的人剛開始接觸時都不會覺得簡單的技巧。所以,也許我們可以說 JUnit 對有經驗的人來說是比較簡單的,新手反而會覺得它很復雜。
XP 的 "Once and Only Once" 以及 Pragmatic Programmer(書名) 的 DRY(Don't Repeat Yourself) 都專注在去除重復的程序代碼。這些良好的建議都有很顯著而且驚人的效果。只要依照這個方式,項目就可以一路順利的運作。但是它也不能解決所有問題,簡單化還是不容易達成。
最近我參與一個可能是過度設計的項目,系統經過 refactor 之后去除部份彈性的設計。但是就像其中一位開發者所說的 "重構過度設計的系統要比重構沒有設計的要來的容易多了" 做一個比你所需要簡單一點的設計是最好的,但是,稍微復雜一點點也不是什么嚴重的事情。
我聽過最好的建議來自 Bob 大叔 (Robert Martin)。他的建議是不要太在意什么是最簡單的設計。畢竟后來你可以,應該,也會再重構。愿意在最后重構,比知道如何做簡單的設計重要得多。
Does Refactoring Violate YAGNI?
這個主題最近出現在 XP 討論區上,當我們審視設計在 XP 扮演的角色時,我覺得很值得提出來討論。
基本上這個問題起因于重構需要耗費時間卻沒有增加新的功能。而 YAGNI 的觀點是假設你為了眼前的需要做設計而不是未來,這樣算是互相抵觸嗎?
YAGNI 的觀點是不要增加一些現階段不需要的復雜功能,這也是簡單設計這項技巧的部份精神。重構也是為了滿足盡可能保持系統的簡單性這個需要,所以當你覺得可以讓系統變得更簡單的時候,就進行重構。
簡單設計不但利用了 XP 的實務技巧,本身也是其中一項有用的實務技巧。唯有伴隨著測試,持續整合,及重構的運用,才能有效地做出簡單設計。同時,讓研發異動曲線保持平緩的基礎也就是保持設計的簡單。任何不必要的復雜都會讓系統變得難于調整,除非這個復雜性是你為了所預測的彈性而加入的。不過,人們的預測通常都不太準確,所以最好還是努力地保持簡單性。
不管怎樣,人們不太可能第一次就能夠獲得最簡單的東西,因此你需要重構來幫助你更接近這個目標。
Patterns and XP
JUnit 的例子讓我不得不想到 patterns。XP 和 patterns 之間的關系很微妙,也常常被問起。Joshua Kerievsky 認為 patterns 在 XP 被過分輕視,而且他所提出的理由也相當令人信服,我不想再重提。不過值得一提的是,很多人都認為 patterns 似乎與 XP 是有沖突的。
爭論的本質在于 patterns 常被過度濫用。世上有太多傳奇性的 programmer,第一次讀到四人幫以 32 行程序代碼闡述 16 種 patterns 這樣的事情還記憶猶新[譯注6]。我還記得有一晚與 Kent 喝著醇酒一起討論一篇文章 "Not Design patterns: 23 cheap tricks (不要用設計模式-23 個簡單的訣竅)"。我們認為那不過是以 if 條件式來取代 strategy 這個 pattern 罷了。這樣的笑話有個重點,patterns 被濫用了。但并不表示 patterns 是不足取的,問題在于你要怎么運用它。
其中一項論點是簡單設計的力量自然會將項目導向 patterns。很多重構的例子明確地這么做,或者甚至不用重構,你只要遵從簡單設計的規則就會發現 patterns,即使你還不知道 patterns 是什么。這樣的說法也許是真的,不過它真的是最好的方式嗎?當然如果你先對于 patterns 有個大略的了解,或者手邊有一本書可以參考,會比自己發明新的 patterns 要好些。當我覺得一個 pattern 快浮現的時候,我必定會去翻翻 GOF 的書。對我來說,有效的設計告訴我們 pattern 值得付出代價去學習-那就是它特有的技術。同樣地就像 Joshua 所建議的,我們需要更熟悉于如何逐步地運用 patterns。就這一點而言,XP 只是與一般使用 patterns 的方式不同而已,并沒有抹煞它的價值。
但是從討論區一些文章看來,我覺得很多人明顯地認為 XP 并不鼓勵使用 patterns,盡管 XP 大部分的提倡者也都是之前 patterns 運動的領導者。因為他們看到了不同于 patterns 的觀點嗎?或是他們已經將 patterns 融入思考而不必再去理解它?我不知道其它人的答案是什么,但是對我來說,patterns 仍然是非常重要的。XP 也許是開發的一種流程,但 patterns 可是設計知識的骨干,不管是哪種流程這些知識都是很有用的。不同的流程使用 patterns 的方式也就不同,XP 強調等到需要時才使用 patterns 以及透過簡單的實作逐步導入 patterns。所以 patterns 仍然是一種必須獲得的關鍵知識。
我對于采用 XP 的人使用 patterns 的建議:
花點時間學習 patterns。
留意使用 patterns 的時機 (但是別太早)。
留意如何先以最簡單的方式使用 patterns,然后再慢慢增加復雜度。
如果用了一種 pattern 卻覺得沒有多大幫助-不用怕,再次把它去掉。
我認為XP應該要更加強調學習 patterns。我不確定它要怎么和 XP 的實務技巧搭配,不過相信 Kent 會想出辦法來的。
文章來源于領測軟件測試網 http://www.kjueaiud.com/