重構模式6

refactoring Patterns:第六部分  
內容:
簡化設計
設計模式
關於作者
相關內容:
該系列的其他部分
Refactoring和軟件設計

石一楹 ([email protected])
浙江大學靈峯科技開發公司技術總監
2001 年 12 月

Refactoring是一種重要的設計輔助工具。特別地,他能夠使得傳統的up-front設計更簡單,也可以改良現有軟件的設計。本文闡述了在應用refactoring環境下設計應當具有的特點以及refactoring和OO社團最受人矚目之一的設計模式之間的關係。

簡化設計

爲什麼設計應當是簡單的?
傳統的軟件方法偏向於進行一次性的Upfront設計,我們知道這很難。

軟件方法學的設計者通常喜歡用建築打比方。他們說,如果你要建造一座大廈,那麼在你畫完所有的施工圖和建築規範之前,你從來不應該開始真正施工。

我喜歡這樣的比喻,因爲建築學的發展經歷了幾千年甚至可能是上萬年,而軟件行業,最多也只有5,60年的時間,我們需要從建築中學習很多東西。就如模式語言的提出者,著名的建築學家,Christopher Alexander ,給了設計模式很大的啓發。

但是,如果我們只是從建築學到東西,恐怕還是遠遠不夠。我還不知道哪一棟大廈的外形能夠發生變化,或者哪一個大商場在發現自動扶梯位置不太合適時,能夠自由移動到一個更加合適的地方(我附近一家大超市的自動扶梯真的讓我想到了這一點)。也許要部分達到這樣的目的還是有可能的,但肯定要花費極高的代價。但是如果我們去構造一個企業的應用系統,那麼我們必須準備好企業發展的同時需要對我們的軟件做出改變。

軟件的核心就是可變。這種變化不僅僅在於需求的變化,還在於人們理解的變化,而軟件這種人活動的產物,必須要隨之發生改變。

增量迭代的開發方法學是對軟件變化的一種響應。但是正如我們前面所講,增量迭代模型有可能對系統的設計提出比傳統的Waterfall更復雜的要求。你需要預期所有可能的情形,然後才能保證你是可以增量的,也是能夠迭代的。

這種兩難的境地來源於一個基本的假設:
    削減軟件成本的最主要方法是減少返工的成本以及它的可能性。

傳統的軟件發法學認爲,如果我能夠儘量早地凍結需求,那麼我修改設計的可能性和成本就越小,如果我的設計能夠儘早地凍結,那麼我修改實現的可能性和成本就越小。要想早早凍結設計,它必須是非常詳盡而複雜的。

我把這種思路稱爲"改變恐懼症",不僅僅因爲變化是最難研究的東西(制訂出條條框框則簡單的多),也因爲我們沒有足夠的經驗來適應變化,更重要的是,從來沒有哪個時代比今天變化更快。

現在我們從另一個角度來想問題:
    我們是否可能通過增加返工的方法來削減軟件的成本?

這個方法初看起來自相矛盾。但是Kent Beck說:
    The key is that risk is money just as much as time is money。

既然系統在變,那麼你不能肯定今天設計的東西一定能夠被日後用到。而你需要等待設計完成的成本會很高,因爲在你的設計完成之前,沒有人可以開始工作。

設計不是獨立的。別人要使用你的設計,他必須能夠理解你的設計。如果你今天的設計十分複雜,那麼你就增加了從今天開始的所有開銷,更多的東西需要檢查和測試,更多的東西需要理解,更多的東西需要解釋。更重要的是,你不能估算出明日的成本。你必須估計日後將要發生什麼事情,通常你不可能準確無誤地做到這一點。

所以,讓設計簡單,我們可以通過不斷地增量修改設計使得系統更加接近需求,就象我們開車,每次都做一些小小的調整,最後達到目的地。

什麼纔是簡單的設計?
要讓設計簡單,是否意味着我們可以隨意fix and build,寫一段,然後任意修改它。但是簡單並不意味着隨意,當然更不意味着愚蠢。

就象我在Duplicate Code裏指出的一樣,Once and Only Once通常是代碼最簡單的形式,因爲它沒有重複,每一個類都有自己簡單的責任,每一個方法都有自己簡單的意圖。

簡單的設計最重要的特性就是容易適應變化.爲了達到這樣的目的,簡單設計應當:

  1. 能夠簡單地被理解.這依賴於你代碼的可理解性.只有可理解,下面的簡單性才能達到.
  2. 能夠簡單地被修改和擴展.OO系統往往通過增量的方法改變或增加系統行爲,所以也就是要被簡單地重用,這要求我們沒有重複代碼.
  3. 有最少數目的類.要易於理解系統,那麼系統中每一個類應當和需要解決的問題的每一個重要概念相對應,如果人工加入許多毫無意義的類或者太多與問題概念無法對應的類,系統將無法理解
  4. 有最少數目的方法.每一個方法應該有他獨立的意義, 如果沒有可以言述意圖的名字,那麼系統將難以理解.

如果你看過Kent Beck和其他Agile聯盟作者的文章,或者你看過我的Duplicate Code和Long Method,你可能會覺得2和3,4存在着矛盾.因爲要達到2的目的,你必須有很多小類和精幹的方法,而3,4則要求你具有最少的類和方法.要理解它們之間其實一致的關鍵在於,你如何決定需要一個新類或者需要一個新的方法.在進行Refactoring時,如果你覺得系統的某一部分具有獨立的可對應問題領域的概念時,那麼你應當毫不猶豫地使用Extract Class形成一個新類.反之,某一個類無法具有其獨立的應用意義,或者該類數據太多而沒有行爲,這個時候你應當考慮這個類是不是應該被刪除.當然,這裏面有些例外(如method object)同樣道理,如果一個代碼片斷能夠有獨立意圖的行爲,那麼不管它的大小,可能是一個簡單的表達式,都應該有獨立的方法,但如果沒有這樣清晰的意圖,再多的代碼都可以在一個方法裏面.

顯然,簡單的設計並非我們想象的那麼簡單.除了我們可以不太考慮以後解決的問題外,簡單,作爲一種重要審美標準.不是輕易能夠達到的.我們一開始的設計往往不能夠達到這樣的要求,可能是類的劃分不合理,可能存在着重複的代碼,可能行爲的分配需要調整,要達到簡單,你就必須Refactoring你的代碼,使設計更加合理.

Refactoring如何支持簡單的upfront設計?
開始,你可以作簡單的設計,也許是一個類,2、3個方法,你針對這個類寫出test case,實現代碼,讓test case通過。在實現這個類的過程中,你發現2、3個方法太長,它們之間會有很多重複代碼,於是你進行refactoring,你使用extract method讓方法更能揭示意圖。然後,你解決另外一個問題,你寫出另外一個Test case,可能和上面一個類沒有多大的關係,也可能你發現這個類在以前的類上附加了一些功能。這時,你會發現直接對前面一個類進行修改比較困難,你對前面一個類進行refactoring,讓它更加容易加入。如果隨着新功能的加入,發現前面一個類其實包含了兩個應用概念,你可能需要對這個類進行重構,把它拆分爲兩個類。你進行refactoring,讓所有的Test cast都能通過測試。你接着又選取一個需求,增加新的test case,然後實現它。這時候你發現這個類和原來的某個類具有很多類似的地方,你可能需要把這兩個類進行進一步的抽象,通過refactoring形成一個新的超類。。。

隨着時間的推移,越來越多的需求被實現,系統變得越來越大,某一天,你發覺系統有些變樣,你覺得有必要修改系統的某一部分結構。這時候,你和你的同僚可能需要暫時摘下增加功能的帽子。你們可能需要更多的討論,交流,使用任何有意義的方法爲你們的討論增強交流的效果,白板、CRC,然後進行Refactoring。

Kent Beck指出,某些大的Refactoring可能並不是一天就能完成的。它可能要花費幾天甚至一個月的時間。但是,你還需要繼續完成用戶的需求。這時採取的方法就是小步的增量改變。在實現新需求,寫下新的test case時,你可能看到一個機會能夠讓你的代碼朝着大目標前進一步。使用這樣的方法,每一次你的工作可能是移動一個變量或一個方法。但是隨着新功能進一步的加入,這樣的機會不斷出現。最終,一個大的目標會變成很小的工作。這時候,你已經水到渠成,花上幾分鐘就完成了。

Refactoring的這種工作方式可以大大減少UpFront的設計量,它同時使你的設計變爲一種必要和需求的產物,這種爲了更好地加入新需求所做的設計,更準確地反映了問題的本身。同時,它使得設計隨着你對問題的進一步深入而逐漸變得更合理,隨着你對新技術的掌握而變得聰明,這是一種進化的設計方法。

設計模式

簡單設計的要求
Refactoring對簡單設計的支持好像使設計模式變得有些過時。如果我一開始只需要寫下一個簡單的test cast、實現、refactoring,增加test cast、實現、refactoring。這是否意味着設計模式就毫無意義了呢?

對設計模式和Refactoring,以及與之相關的Agile聯盟方法學的研究給我一個很深刻的印象。一個方面,就像Martin Fowler指出,Agile方法學的提倡者往往也是模式社團的領導者。另一方面,如果你仔細體會一下Agile 宣言,你會發覺它們非常重視人的能力。在我自己組織小組試用XP的過程中也發現,XP對它的參與者有很高的要求。

我們已經看到,簡單的設計並不是愚蠢的設計,相反,它們是極端聰明的設計。要實現簡單的設計,你必須有豐富的知識。這裏在於,簡單設計提倡你不要太多考慮以後的具體需求本身,但它確實需要你考慮代碼和設計的結構。在用Refactoring武裝你的思想中,我已經講到,如果你的設計結構完全不考慮以後的擴展,那麼Refactoring也無能爲力。

設計模式是面向對象社團對設計的一個重要總結。其實,它們也是對簡單設計的最好詮釋。因爲,好的設計模式,就是對於解決問題所能發現的最簡單、最易重用的設計結構。另一方面,使用廣爲人知的設計模式也使得你的代碼更容易爲人理解、接受,大大提高了交流的效率、寬度和深度。

從這種意義上將,Refactoring不但沒有排除設計模式,它更促進了設計模式的學習、發現和驗證,以及更廣泛的應用。Agile對程序員素質的要求,在這一點上也得到了充分的體現。一旦小組成員具備熟練運用設計模式的能力,你的設計可能就是一句話:用XXX模式。(在我領導的EOSP2P項目中,這個模式就是半State/半策略模式。當我說出這個模式時,我們發覺整個小組不再需要upfront設計,我們構建,然後Refactoring。當然,象Factory method模式幾乎使每個系統都應當使用的。還有後續的proxy模式,都讓設計變得如此輕鬆)。

但是,在應用設計模式的過程中,你必須時時記住簡單性這個原則。設計模式最好的應用方法就是從簡單開始,只要能夠解決問題,抓住這種模式的核心,那麼模式良好的結構保證你的Refactoring建立在一個堅實的基礎之上。

所以,爲了使Refactoring能夠更好地進行,你需要更多的學習模式,因爲設計模式不但是良好設計的開始,同時也是Refactoring的目標。

Refactoring的目標
Refactoring有時會失去目標,你可能會產生循環的Refactoring,或者你無法判斷Refactoring的結果到底有沒有讓程序的結構更好。

作爲OO社團最重要貢獻之一的設計模式能夠爲Refactoring提供一個明確的目標。在Ralph Johnson多篇關於Refactoring的論文中都提到了這個問題。Martin Fowler的《Refactoring》中明確指出的就有state、strategy、visitor等模式。更深入一步,設計模式不但是Refactoring的目標,同時也爲看起來有些零散的行爲提供了一個全局性的指引。

Christopher Alexander在《The Timeless way of Building》一書中對模式的定義如下:

Each pattern is a three-part rule, which expression a relation between a certain context, a problem, and a solution.

很多人在軟件領域對他的定義做了修改和擴展,但這三部分內容是不變的。把這個定義放到我們軟件上下文來看,設計模式有兩個重要特性:

  1. 設計模式高於代碼層,他不是描述一個好的編碼風格,或者某種編程習慣用語;
  2. 設計模式不是純粹理論上的體系結構或者分析方法,它是一種可實際操作的東西。模式界提出的Three Rules就說,你必須提出三個實際的應用例子才能夠得上設計模式的條件。

正因如此,很多人都把設計模式稱之爲micro-architecture。顯然,由於它的可操作性,它可以通過Refactoring的方法達到。同時由於它一定層次的理論和總結性,他可以用於引導Refactoring並作爲Refactoring的目標。

關於作者
石一楹,現任浙江大學靈峯科技開發公司技術總監。多年從事OO系統分析、設計。現主要研究方向爲Refactoring和分析模式。你可以通過[email protected]和我聯繫。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章