有一則故事說的是一個人肚子疼,去看醫(yī)生,醫(yī)生給他開了眼藥,理由是眼神不好,吃錯了東西,所以才會肚子疼。軟件開發(fā)中出現(xiàn)的問題往往不是單純的問題,頭疼醫(yī)頭,腳疼醫(yī)腳的做法未必適合于軟件開發(fā)。
應(yīng)用迭代并不是一件簡單的事情,懂得了迭代和增量的概念,并不等于你能夠用好它們。為什么這么說呢?很多的軟件組織嘗試著運用迭代開發(fā),但是結(jié)果卻不盡人意,于是將問題怪罪在迭代的方法不切實際上。軟件工程中有句著名的話-"沒有銀彈"。迭代和增量也不是什么銀彈。要想做好迭代,缺乏優(yōu)秀的軟件設(shè)計思想和高明的軟件設(shè)計師的支持是不行的。在XP中,非常強調(diào)各項實踐的互為補充。在我看來,迭代能夠順利實行的思路需要重構(gòu)、測試優(yōu)先、持續(xù)集成等的直接支持。而這些實踐,體現(xiàn)了軟件設(shè)計和軟件過程中的關(guān)系。
迭代實踐出現(xiàn)問題往往是在項目的中期。這個時候,軟件的主體已經(jīng)形成,代碼的增長速度也處于一個快速增長的情況。這種狀態(tài)下的軟件開發(fā)對變化的需求是最沒有抵抗力的,尤其是那些設(shè)計本身存在問題的軟件。軟件開發(fā)到這個階段,代碼往往比較混亂,缺乏一條主線或是基礎(chǔ)的架構(gòu)。這時候,需求的變化,或是新增的需求導(dǎo)致的成本直線上升,項目進(jìn)度立刻變得難以預(yù)期,開發(fā)人員的士氣受到影響。
在這個時候,軟件組織要做的,并不是在迭代這個問題上深究下去,而是應(yīng)當(dāng)從軟件設(shè)計入手,找到一種能夠適應(yīng)變化的軟件設(shè)計思路或方法。例如,你是否應(yīng)該考慮在面向?qū)ο箢I(lǐng)域做一些研究呢?面向?qū)ο蟮乃悸泛茏⒅貙⒆兓膬?nèi)容和不變的內(nèi)容相區(qū)分,以便支持未來的變化和應(yīng)對不確定性。然后你再來考慮相應(yīng)的成本。
做好迭代有幾個值得注意的地方:
軟件開發(fā)的能力并不體現(xiàn)為代碼量的多少,而是體現(xiàn)為代碼實現(xiàn)的功能,代碼的可擴展性、可理解性上。所以對代碼進(jìn)行不斷的改進(jìn),對設(shè)計進(jìn)行不斷的改進(jìn)(具體的次數(shù)根據(jù)需要而定),使軟件的結(jié)構(gòu)比較穩(wěn)定,并能夠支持變化。這是迭代的一個前提。否則,每一次的迭代都花費大量的精力來對原先的設(shè)計進(jìn)行修改,對代碼進(jìn)行優(yōu)化,這樣的迭代效率是不高的,也可以視為一種浪費。堅持不斷改進(jìn)軟件質(zhì)量的做法其實是將軟件的集中維護(hù)、改進(jìn)的成本分?jǐn)偟秸麄€過程中,這種思路,和全面質(zhì)量管理的思路是非常類似的。XP中的重構(gòu)實踐有一個修飾詞,稱為無情。這充分表現(xiàn)了XP的異類,但是應(yīng)該承認(rèn),只有設(shè)計和代碼的質(zhì)量上去了,才能夠為后續(xù)的迭代過程打下一個基礎(chǔ),更何況,XP所處的往往是一個不確定的、變化多端的環(huán)境。正是因為這種環(huán)境對軟件開發(fā)有著很大的影響,因此代碼質(zhì)量也被高度的重視。不同的行業(yè),不同的項目,需要根據(jù)自己的特征進(jìn)行調(diào)整,但是,只有保證代碼的優(yōu)美性,才能夠順利地達(dá)成迭代的目標(biāo)。
代碼設(shè)計優(yōu)化同時必須保持簡單的原則,不在一開始進(jìn)行大量的設(shè)計投入。我曾堅信,軟件編碼之前,嚴(yán)格的軟件設(shè)計是不可或缺的。但是慢慢的,我發(fā)現(xiàn)這種思路未必是正確的。在總結(jié)了一些開發(fā)經(jīng)驗之后,我發(fā)現(xiàn),很多的時間其實是浪費在了設(shè)計上。
在一個軟件的設(shè)計中,對界面結(jié)構(gòu)有著很強的要求,而Eclipse的設(shè)計思路正當(dāng)其時。因此,我興奮的將Eclipse的設(shè)計思路注入到界面設(shè)計上來,在花費了大量的時間進(jìn)行設(shè)計和實現(xiàn)之后,發(fā)現(xiàn)并不能很好的滿足需要。更為糟糕的是,由于設(shè)計的復(fù)雜性,導(dǎo)致調(diào)試和變更的難度都加大,而團(tuán)隊的其它成員,也表示難以理解這種思路。最后的這個設(shè)計廢棄了,但是損失已經(jīng)是造成了,復(fù)雜的設(shè)計和實現(xiàn),足足花費了一個星期的開發(fā)時間。
除了第一次的迭代,后續(xù)的迭代過程都是建立在前一次迭代的基礎(chǔ)上。因此,每一次迭代中積累下來的問題最終都會反應(yīng)在后續(xù)的迭代過程中。要想保證迭代順利的進(jìn)行,對代碼進(jìn)行重構(gòu)和審查是少不了的工作。其中最重要的工作莫過于消除重復(fù)代碼,重復(fù)代碼是造成代碼雜亂的罪魁禍?zhǔn)?。消除重?fù)代碼的工作可不僅僅只是找出公函這么簡單,其間涉及到重構(gòu)、面向?qū)ο笤O(shè)計、設(shè)計模式、框架等眾多的知識。這些知識的介紹并不是本文的重點,但是我們必須知道,只有嚴(yán)格的控制好代碼的質(zhì)量,軟件的質(zhì)量和軟件過程的質(zhì)量才有保證。
精益編程告訴我們,盡可能推遲決策。在一個變化的環(huán)境中,早期的決策往往缺乏足夠的事實支持和實踐證明。即便是再高明的軟件設(shè)計師,難免會犯錯誤,這是非常正常的,那么,既然目前的決定是有著很大風(fēng)險的,那為什么我們還要急于做出決定呢?在看待設(shè)計這個問題上,一種比較好的做法是,盡量避免高難度、高浪費的設(shè)計,以滿足現(xiàn)有的需要作為實現(xiàn)的目標(biāo)。未來的需求等到確定的時候再進(jìn)行調(diào)整。
推遲決策其實是軟件設(shè)計的一大能力,為什么我們會推薦使用面向?qū)ο蠹夹g(shù)呢?因為面向?qū)ο蠹夹g(shù)具有很強的推遲決策的能力,先將目前確定的問題納入面向?qū)ο笤O(shè)計,并為未來的不確定性留下擴展。推遲決策并不是一個簡單的問題,它需要很強的面向?qū)ο蟮脑O(shè)計思維能力。
設(shè)計模式中有很多這方面的例子,其中的裝飾模式具有很強的代表性。
在設(shè)計剛開始的時候,沒有人知道ConcreteComponent最后的發(fā)展會是什么樣。很明顯,這是一個處于不確定環(huán)境中的設(shè)計,我們唯一能夠確定的,只有Component這個類體系一定會擁有Operate這個方法,所以,我們設(shè)計了一個接口Component來約束類體系,要求所有的子類都擁有Operate方法。另一個目的是為客戶端調(diào)用提供了統(tǒng)一的接口,這樣,客戶端對服務(wù)端信息的了解到了最小的程度,只需要知道Operate這個方法,并選擇適當(dāng)?shù)念惥涂梢粤?。還可以對這個模型做進(jìn)一步的改進(jìn),令耦合程度進(jìn)一步降低。
在統(tǒng)一了接口之后,我們就可以根據(jù)需要來實現(xiàn)現(xiàn)有的功能,我們實現(xiàn)了一個ConcreteComponent類,它實現(xiàn)了Component接口,并實現(xiàn)了核心的功能。如果在未來,需求的變化,要求我們增加額外的行為,我們就使用ConcreteDecorator類來為ConcreteComponent添加新的功能:
public class ConcreteDecorator implement Component { private Component component; public void Operate() { //額外的行為 component.Operate; } } |
先找出共通點,然后實現(xiàn)共通點,并把不確定的信息設(shè)計為擴展,這就是推遲決策的設(shè)計思路。但是,應(yīng)該指出的是,上面這個例子的設(shè)計,仍然有很多的限制,例如,增加的需求(也就是某個ConcreteDecorator)中可能擁有新的接口,例如需要一個AnotherOperate方法,這時候,原先的擴展性設(shè)計就又變得難以滿足需要了。在軟件設(shè)計中,針對接口設(shè)計的靈活性和擴展性雖然比以往的設(shè)計增強的許多,但它并不是萬能的,而且取決于設(shè)計師對需求的理解能力和設(shè)計水平。此外,推遲設(shè)計決策要求我們學(xué)習(xí)抽象的思維,識別和區(qū)分軟件中變化和不變的部分。
Martin Fowler把軟件設(shè)計分為三個層面:概念(conceptual)層面、規(guī)約(Specification)層面、實現(xiàn)(Implementation)層面。軟件的設(shè)計應(yīng)該盡可能地站在概念、規(guī)約層面上進(jìn)行,而不是過分關(guān)注實現(xiàn)層面。之所以有時候我們發(fā)現(xiàn)在迭代的過程中,軟件難以承受這種變化,那么,很大的可能是規(guī)約層面和實現(xiàn)層面出了問題。我們在前面一節(jié)討論重構(gòu)和審查的時候說,消除重復(fù)代碼是一項復(fù)雜的工作,針對規(guī)約設(shè)計就是其中最有效,但也是最難的一種方法。
我們可以把規(guī)約層面想象為軟件的接口或是抽象類,或是具體類的公有方法,而把實現(xiàn)層面想象為實現(xiàn)類、實現(xiàn)細(xì)節(jié)。那么,我們的原則應(yīng)該盡可能設(shè)計穩(wěn)定的規(guī)約層面,并為客戶(可能是真正的客戶,大部分情況下是使用你的代碼的客戶程序員)提供一個優(yōu)秀的、簡單的界面(接口)。社會發(fā)展到現(xiàn)在的水平,任何一個人都不會花費過多的時間來研究你的代碼,如果你的代碼不能夠為他人提供便利性,那么最后被淘汰的一定就是你的代碼。Java語言的成功,很大程度上就在于他在保證其強大功能的同時,還提供了一個簡單、易用、清晰的規(guī)約界面。
在軟件設(shè)計中,重視規(guī)約層面的設(shè)計是很普遍的。為什么我們提倡三層架構(gòu)的軟件設(shè)計?最重要的是因為他為軟件結(jié)構(gòu)合理性貢獻(xiàn)巨大,遠(yuǎn)遠(yuǎn)超過了他的其它價值。在現(xiàn)代的軟件設(shè)計中,數(shù)據(jù)庫、界面、業(yè)務(wù)建模其實是三種差異較大的技術(shù),這就導(dǎo)致了三者的變化度是不同的。根據(jù)區(qū)分不同變化度的原則,我們知道,必須對三種技術(shù)進(jìn)行區(qū)分。而這正是三層架構(gòu)的主要思路。從這個思路擴展出去,我們還可以根據(jù)變化度的需要,將三層架構(gòu)演變?yōu)樗膶蛹軜?gòu)、甚至多層架構(gòu)。而多個層次之間,正是通過優(yōu)秀的規(guī)約界面來達(dá)到最松散的耦合的。
在精益編程中,為了避免浪費,要求每位程序員提高代碼的規(guī)約層面的穩(wěn)定性是非常有必要的。一個系統(tǒng)中,設(shè)計優(yōu)良的規(guī)約界面能夠擁有比較好的抗變化能力,能夠較好的適應(yīng)迭代過程。
版本2的軟件出現(xiàn)了版本1中不存在的行為,稱為回歸?;貧w是軟件開發(fā)中的主要問題。在對現(xiàn)有功能修改的同時影響原有的行為,這是造成bug的主要原因。在迭代的過程中,必須避免回歸行為的出現(xiàn)。而避免回歸問題的主要解決方法是構(gòu)建自動化的測試,實現(xiàn)回歸測試。
成功構(gòu)建回歸測試的關(guān)鍵仍然在于是否能夠設(shè)計出優(yōu)秀的規(guī)約界面,并針對規(guī)約界面進(jìn)行測試。這樣,不但設(shè)計具有抗變化性,測試同樣具有抗變化性。而唯一可能改變的就只有實現(xiàn)了。在回歸測試的幫助下,代碼的變化是不足為懼的。我們把有關(guān)測試的詳細(xì)討論放在測試一節(jié)中。
在后續(xù)的章節(jié)中,我們會詳細(xì)的討論XP中的一項非常有特點的組織規(guī)則-結(jié)對編程。這里我們需要知道,不同的團(tuán)隊有著不同的組織,其迭代過程也需要應(yīng)用不同的組織規(guī)則。例如,組織的規(guī)模,小規(guī)模的組織可以應(yīng)用更快的迭代周期,如一周,在一個迭代周期中,團(tuán)隊可以集中力量來開發(fā)一個需求,強調(diào)重構(gòu)和測試,避免過多的前期設(shè)計。對于大的組織來說,可以考慮迭代周期更長一些,更注重前期設(shè)計,并將開發(fā)人員和測試人員的迭代周期交錯開來。團(tuán)隊的組織構(gòu)成也是影響迭代過程的主要原因。團(tuán)隊是否都是由相同水平的人構(gòu)成,每個人的專長是否能夠互補,團(tuán)隊是否存在溝通問題。
林星,辰訊軟件工作室項目管理組資深項目經(jīng)理,有多年項目實施經(jīng)驗。辰訊軟件工作室致力于先進(jìn)軟件思想、軟件技術(shù)的應(yīng)用,主要的研究方向在于軟件過程思想、Linux集群技術(shù)、OO技術(shù)和軟件工廠模式。您可以通過電子郵件 iamlinx@21cn.com和他聯(lián)系。