敏捷思維: 架構(gòu)設(shè)計(jì)中的方法學(xué)(12)Refactoring ![]() |
![]() | 級別: 初級 林星, 項(xiàng)目經(jīng)理, 辰訊軟件工作室 2002 年 11 月 01 日 當(dāng)架構(gòu)模型進(jìn)行迭代的過程中,必然伴隨著對模型進(jìn)行修改和改進(jìn)。我們?nèi)绾畏乐箤δP偷男薷?,又如何保證對模型進(jìn)行正確的改進(jìn)? 架構(gòu)模型通過精化、合并等活動之后,將會直接用于指導(dǎo)代碼。而這個時(shí)候,往往就會暴露出一些問題出來,通常在實(shí)際編碼中,發(fā)現(xiàn)架構(gòu)存在或大或小的問題和錯誤,導(dǎo)致編碼活動無法繼續(xù)。這時(shí)候我們就需要對架構(gòu)模型進(jìn)行修改了。而架構(gòu)設(shè)計(jì)的過程本身是一個迭代的過程,這就意味著在每一次的迭代周期中,都需要對架構(gòu)進(jìn)行改進(jìn)。
我們?nèi)绾伪苊鈱軜?gòu)模型進(jìn)行修改?又如何保證架構(gòu)進(jìn)行正確的改進(jìn)?
我們從XP中借用了一個詞來形容架構(gòu)模型的修改過程――Refactoring,中文可以譯作重構(gòu)。這個詞原本是形容對代碼進(jìn)行修改的。它指的是在不改變代碼外部行為(可觀察行為)的情況下對代碼進(jìn)行修改。我們把這個詞用在架構(gòu)模型上,因?yàn)榻?jīng)過精化和合并之后的架構(gòu)模型往往由很多個粗粒度組件構(gòu)成。這些組件之間存在一定的耦合度(雖然我們可以令耦合度盡可能的低,但是耦合度一定是存在的),任何一個組件的重構(gòu)行為都會使變化擴(kuò)散到系統(tǒng)中的其它組件。這取決于被重構(gòu)的組件和其它組件之間的相對關(guān)系。如果被重構(gòu)的組件屬于層次較低的工具層上,那么這次的修改就可以引起模型很大的變動。 在精化和合并模式中,我們提到了改變和改進(jìn)的區(qū)別,因此,我們的對策主要分為兩種:如何防止改變的發(fā)生,以及,使用重構(gòu)來改進(jìn)軟件架構(gòu)。 在任何時(shí)候,需求的變更總是對架構(gòu)及軟件有著最大的傷害。而需求變更中最大問題是需求蔓延。很多人都有這樣的感覺,項(xiàng)目完成之后,發(fā)現(xiàn)初期的計(jì)劃顯得那么陌生。在項(xiàng)目早期對需求進(jìn)行控制是重要的,但并不是該模式談?wù)摰闹攸c(diǎn)。我們更關(guān)注在項(xiàng)目中期的需求蔓延問題和晚期的需求控制問題。關(guān)于這方面的詳細(xì)討論,請參見穩(wěn)定化模式。在項(xiàng)目中期,尤其是編碼工作已經(jīng)開始之后,要盡可能避免出現(xiàn)需求蔓延的情況。需求蔓延是經(jīng)常發(fā)生的,可能是因?yàn)橛脩粝M尤腩~外的功能,或是隨著用戶對軟件了解的加深,發(fā)現(xiàn)原有的需求存在一定的不足。完全防止需求蔓延是無法做到的,但是需要對其進(jìn)行必要的控制。例如,有效的估計(jì)變更對開發(fā)、測試、文檔、管理、組織等各個方面帶來的影響。 避免發(fā)生改變的另一個有效的辦法是從軟件過程著手。迭代法或漸進(jìn)交付法都是可用的方法。一個軟件的架構(gòu)設(shè)計(jì)往往是相對復(fù)雜的,其中涉及到整體結(jié)構(gòu)、具體技術(shù)等問題。一次性考慮全部的要素,就很容易發(fā)生考慮不周詳?shù)那闆r。人的腦容量并沒有我們想象的那么大。將架構(gòu)設(shè)計(jì)分為多個迭代周期來進(jìn)展,可以減少單次迭代周期中需要建模的架構(gòu)數(shù)量,因此可以減少錯誤的發(fā)生。另一方面,迭代次數(shù)的增多的直接結(jié)果是時(shí)間的延長,此外還有一個潛在的問題,如果由于設(shè)計(jì)師的失誤,在后期的迭代中出現(xiàn)問題,必然會導(dǎo)致大量的返工。因?yàn)橹暗哪P鸵呀?jīng)實(shí)現(xiàn)了。在得與失之間,我們?nèi)绾握业竭m當(dāng)?shù)钠胶恻c(diǎn)呢? 迭代次數(shù)應(yīng)該根據(jù)不同軟件組織的特點(diǎn)來制定,對于初期的迭代周期而言,它的主要任務(wù)應(yīng)該是制定總原則(使用架構(gòu)愿景模式)、定義層結(jié)構(gòu)和各層的職責(zé)(使用分層模式)、解決主要的技術(shù)問題上。在這個過程中,可以列出設(shè)計(jì)中可能會遇到的風(fēng)險(xiǎn),并根據(jù)風(fēng)險(xiǎn)發(fā)生的可能性和危害性來排定優(yōu)先級,指定專人按次序解決這些問題。除此之外,在初期參考前一個項(xiàng)目的經(jīng)驗(yàn),讓團(tuán)隊(duì)進(jìn)行設(shè)計(jì)(參見團(tuán)隊(duì)設(shè)計(jì)模式),這些組織保證也是很重要。初期的迭代過程是防止改變的最重要的活動。 請注意需求中的非功能需求。如果說功能需求定義了架構(gòu)設(shè)計(jì)的目標(biāo)的話,非功能需求就對如何到達(dá)這個目標(biāo)做出了限制。例如,對于實(shí)現(xiàn)一個報(bào)表有著多種的操作方法,但是如果用戶希望新系統(tǒng)和舊系統(tǒng)進(jìn)行有效的融合,那么實(shí)現(xiàn)的方式就需要好好的規(guī)劃了。請從初期的迭代過程就開始注意非功能需求,因?yàn)槿绻雎运鼈?,在后期需要花費(fèi)很大的精力來調(diào)整架構(gòu)模型。試想一下,如果在項(xiàng)目晚期的壓力測試中,發(fā)現(xiàn)現(xiàn)有的數(shù)據(jù)庫訪問方法無法滿足用戶基本的速度要求,那對項(xiàng)目進(jìn)行將會造成多么大的影響。 注意架構(gòu)的穩(wěn)定性。在精化和合并模式中,我們提到了一些模式,能夠降低不同組件之間的耦合度。并向調(diào)用者隱藏具體的實(shí)現(xiàn)。接口和實(shí)現(xiàn)分離是設(shè)計(jì)模式最大的特點(diǎn),請善用這一點(diǎn)。 盡可能的推延正式文檔的編寫。在設(shè)計(jì)的初期,修飾模型和編寫文檔通常都沒有太大的意義。因?yàn)榇藭r(shí)的模型還不穩(wěn)定,需要不斷的修改。如果這時(shí)候開始投入精力開發(fā)文檔,這就意味著后續(xù)的迭代周期中將會增加一項(xiàng)維護(hù)文檔一致性的工作了。而這時(shí)候的文檔卻無法發(fā)揮出它真正的作用。但是,延遲文檔的編寫并不等于什么都不做,無論什么時(shí)候進(jìn)行設(shè)計(jì),都需要隨手記錄設(shè)計(jì)的思路。這樣在需要的時(shí)候,我們就能夠有充分的資料對設(shè)計(jì)進(jìn)行文檔化的工作。 Martin Fowler的Refactoring一書為我們列舉了一系列的對代碼進(jìn)行重構(gòu)方法。架構(gòu)也是類似的。 重構(gòu)到模式 Joshua Kerievsky在《Refactoring to Patterns》一書中這樣描述重構(gòu)和模式的關(guān)系: Patterns are a cornerstone of object-oriented design, while test-first programming and merciless refactoring are cornerstones of evolutionary design (模式是面向?qū)ο笤O(shè)計(jì)的基石,而測試優(yōu)先編程和無情的重構(gòu)則是設(shè)計(jì)演進(jìn)的基石)。作者在文中著重強(qiáng)調(diào)了保持適度設(shè)計(jì)的重要性。 在作者看來,模式常常扮演著過度設(shè)計(jì)的角色。而在解決這個問題的同時(shí)又利用模式的優(yōu)點(diǎn)的解決方法是避免在一開始使用模式,而是在設(shè)計(jì)演進(jìn)中重構(gòu)到模式。這種做法非常的有效,因?yàn)樵诔跏荚O(shè)計(jì)中使用模式的話,你的注意力將會集中到如何使用模式上,而不是集中在如何滿足需求上。這樣就會導(dǎo)致不恰當(dāng)?shù)脑O(shè)計(jì)(過度設(shè)計(jì)或是設(shè)計(jì)不充分)。因此,在初始設(shè)計(jì)中,除非非常有把握(之前有類似的經(jīng)驗(yàn)),否則我們應(yīng)當(dāng)把精力放在如何滿足需求上。在初始模型完成后(參見精化和合并模式中的例子),我們會對架構(gòu)進(jìn)行重構(gòu),而隨著迭代的演進(jìn),需求的演進(jìn),架構(gòu)也需要演進(jìn),這時(shí)候也需要重構(gòu)行為。在這些過程中,如果發(fā)現(xiàn)某些部分的設(shè)計(jì)需要額外的靈活性來滿足需求,那么這時(shí)候就需要引入模式了。 在軟件開發(fā)過程中,我們更常的是遇見設(shè)計(jì)不充分的情況,例如組件之間耦合度過高,業(yè)務(wù)層向客戶端暴露了過多的方法等等。很多的時(shí)候,產(chǎn)生這種現(xiàn)象是由于不切實(shí)際的計(jì)劃而導(dǎo)致的。開發(fā)人員不得不為了最終期限而趕工,所有的時(shí)間都花費(fèi)在新功能上,而完成的軟件則被仍在一邊。這樣產(chǎn)出的軟件是無法保證其質(zhì)量的。對于這種情況,我們也需要對設(shè)計(jì)進(jìn)行重構(gòu),當(dāng)然,合理的計(jì)劃是大前提所在。團(tuán)隊(duì)的領(lǐng)導(dǎo)者必須向高層的管理者說明,現(xiàn)在的這種做法只會導(dǎo)致未來的返工,目前的高速開發(fā)是以犧牲未來的速度為代價(jià)的。因?yàn)榈土拥脑O(shè)計(jì)需要的高成本的維護(hù),這將抵消前期節(jié)省的成本。如果軟件團(tuán)隊(duì)需要可持續(xù)的發(fā)展,那么請避免這種殺雞取卵的行為。 因此,使用模式來幫助重構(gòu)行為,以實(shí)現(xiàn)恰當(dāng)?shù)脑O(shè)計(jì)。 測試行為 重構(gòu)的前提是測試優(yōu)先,測試優(yōu)先是XP中很重要的一項(xiàng)實(shí)踐。對于編碼來說,測試優(yōu)先的過程是先寫測試用例,再編寫代碼來完成通過測試用例(過程細(xì)節(jié)不只如此,請參看XP的相關(guān)書籍)。但是對于架構(gòu)設(shè)計(jì)來說,測試行為是發(fā)生在設(shè)計(jì)之后的,即在設(shè)計(jì)模型完成后,產(chǎn)出相應(yīng)的測試用例,然后再編碼實(shí)現(xiàn)。這時(shí)候,測試用例就成為聯(lián)系架構(gòu)設(shè)計(jì)和編碼活動的紐帶。 另一方面,在設(shè)計(jì)進(jìn)行重構(gòu)時(shí),相應(yīng)的測試用例也由很大的可能性發(fā)生改變。此時(shí)往往會發(fā)生需要改變的測試代碼超出普通代碼的情況。避免這種情況一種做法是令你的設(shè)計(jì)模型的接口和實(shí)現(xiàn)相分離,并使測試用例針對接口,而不是實(shí)現(xiàn)。在精化和合并模式中,我們提到了一些模式,能夠有助于穩(wěn)定設(shè)計(jì)和測試用例。Martin Fowler在他的Application Facade一文中,提到使用Facade模式來分離不同的設(shè)計(jì)部分,而測試則應(yīng)當(dāng)針對facade來進(jìn)行,其思路也是如此。 考慮一個用戶轉(zhuǎn)帳的用例。銀行需要先對用戶進(jìn)行權(quán)限的審核,在審核通過之后才允許進(jìn)行轉(zhuǎn)帳(處于簡便起見,圖中忽略了對象的創(chuàng)建過程和調(diào)用參數(shù)): ![]() 需要分別針對三個類編寫測試用例,設(shè)計(jì)模型一旦發(fā)生變化,測試用例也將需要重新編寫。再考慮下面的一種情況: ![]() 現(xiàn)在的設(shè)計(jì)引入了TransferFacade對象,這樣我們的測試用例就可以針對TransferFacade來編寫了,而轉(zhuǎn)帳的業(yè)務(wù)邏輯是相對比較穩(wěn)定的。使用這種測試思路的時(shí)候,要注意兩點(diǎn):首先,這并不是說其它的類就不需要測試用例了,這種測試思路僅僅是把測試的重點(diǎn)放在外觀類上,因?yàn)槿魏螘r(shí)候充分的測試都是不可能的。但其它類的測試也是必要的,對于外觀類來說,任何一個業(yè)務(wù)方法的錯誤都會導(dǎo)致最終的測試失敗。其次,當(dāng)外觀類的測試無法達(dá)到穩(wěn)定測試用例的效果時(shí),就沒有必要使用外觀類了。 只針對有需要的設(shè)計(jì)進(jìn)行重構(gòu)。 任何時(shí)候,請確保重構(gòu)行為僅僅對那些有重構(gòu)需要的設(shè)計(jì)。重構(gòu)需要花費(fèi)時(shí)間和精力,而無用的重構(gòu)除了增大設(shè)計(jì)者的虛榮心之外,并不能夠?yàn)檐浖黾觾r(jià)值。重構(gòu)的需要來源于兩點(diǎn):一是需求的變更。目前的設(shè)計(jì)可能無法滿足新的需求,因此需要重構(gòu)。二是對設(shè)計(jì)進(jìn)行改進(jìn),以得到優(yōu)秀簡潔的設(shè)計(jì)。除了這兩種情況,我們不應(yīng)該對設(shè)計(jì)模型進(jìn)行重構(gòu)。 使用文檔記錄重構(gòu)的模式。 應(yīng)該承認(rèn),模式為設(shè)計(jì)提供了充分的靈活性。而這些設(shè)計(jì)部分往往都是模型的關(guān)鍵之處和難點(diǎn)所在,因此需要對模式進(jìn)行文檔化的工作,甚至在必要的時(shí)候,對這部分的設(shè)計(jì)進(jìn)行培訓(xùn)和指導(dǎo)。確保你的團(tuán)隊(duì)能夠正確的使用文檔來設(shè)計(jì)、理解、擴(kuò)展模式。我們在解決方案的前一個部分提到了盡可能延遲文檔的創(chuàng)建。而在設(shè)計(jì)重構(gòu)為模式的時(shí)候,我們就需要進(jìn)行文檔化的工作了。因?yàn)槟J骄哂徐`活性,能夠抵抗一定的變更風(fēng)險(xiǎn)。 重構(gòu)并保持模式的一致性 正如上一節(jié)所說的那樣,模式并不是一個很容易理解的東西,雖然它保持了設(shè)計(jì)的靈活性和穩(wěn)定性。對于面向?qū)ο蟮男率侄?,模式簡直就像是飛碟一樣,由于缺少面向?qū)ο蟮脑O(shè)計(jì)經(jīng)驗(yàn),他們無法理解模式的處理思路,在實(shí)踐中,我們不只一次的碰到這種情況。我們不得不重頭開始教授關(guān)于模式的課程。因此,最后我們在軟件設(shè)計(jì)采用一定數(shù)量的模式,并確保在處理相同問題的時(shí)候使用相同的模式。這樣,應(yīng)用的模式就成為解決某一類的問題的標(biāo)準(zhǔn)做法,從而在一定程度上降低了學(xué)習(xí)的曲線。 保持模式的一致性的另一個方面的含義是將模式作為溝通的橋梁。軟件開發(fā)是一種團(tuán)隊(duì)的行為。因此溝通在軟件開發(fā)中扮演著很重要的角色。試想一下,開發(fā)人員在討論軟件設(shè)計(jì)的時(shí)候,只需要說"使用工廠模式",大家就都能夠明白,而不是費(fèi)勁口舌的說明幾個類之間的關(guān)系。這將大大提高溝通的效率。此外,模式的使用和設(shè)計(jì)的重構(gòu)對于提高團(tuán)隊(duì)的編程水平,培養(yǎng)后備的設(shè)計(jì)人員等方面都是很有意義的。 |