敏捷思維-架構設計中的方法學 (轉)

amyz發表於2007-08-15
敏捷思維-架構設計中的方法學 (轉)[@more@]方法論對開發而言意味著什麼?我們如何看待軟體開發中的方法論?方法論能夠成為軟體開發的救命稻草嗎?在讀過此文後,這些疑惑就會得到解答。在第一篇文章中,我們來了解標題中的一些詞的含義。方法學是什麼? 是什麼? 為什麼討論架構?方法論方法論的英文為Methodology,詞典中的解釋為"A series of related methods or techniques"我們可以把它定義為軟體開發(針對軟體開發)的一整套方法、過程、規則、實踐、技術。關於方法論的出現的問題,我很贊同Alistair Cockburn的一句話,"方法論源於恐懼。"出於對專案的超期、成本失控等等因素的恐懼,專案經理們從以前的出發,制定出了一些控制、監測專案的方法、技巧。這就是方法論產生的原因。在Agile Software Development一書中,作者提到了方法論的十三個要素,基本能夠函蓋方法論的各個方面:角色(Roles) 個性(Personality) 技能(Skills) 團隊(Teams) 技術(Techniques) 活動(Activities) 過程(Process) 工件(Work products) 里程碑(Milestones) 標準(Standards) 質量(Quality) 工具(Tools) 團隊價值(Team Values)它們之間的關係可以用一幅圖來表示:圖 1. 方法論的十三個要素很多的方法論,都涉及了上面列舉的十三要素中的部分要素,因此,我們可以把方法論看作是一個抽象的、無窮的超集,而現實中的方法論都是指超集的一個有限的子集而已。它們之間的關係就好像有理數和1到100之間的整數的關係一樣。不論是XP,還是UI設計經驗之類,都屬於方法論的一個子集,只是這兩個子集之間有大小的差別而已。我們還應該看到,討論一個完備的方法論是沒有意義的,因此這種方法論鐵定不存在,就好像你檢視窮舉出所有的有理數一樣荒唐。因此,我們關於一個通用的方法論的說法也是無意義的。好的方法論,比如說XP、水晶系列,它們都有一個適合的範圍,因為它們瞭解一點,自己並不是一個無所不能的方法論。在現實中,我們其實不斷的在接觸方法論。比如說,為了控制專案的進度,專案經理要求所有的開發人員每週遞交一份詳細的進度報告,這就是一種方法、一種技巧。如果把開發過程中的這些技巧的組織起來,就能夠成為一種方法論。你可能會說,那一種方法論的產生也太容易了吧。不,這樣產生的方法論並沒有太大的實用價值,沒有實用價值的方法論根本就沒有存在的必要。因此,一個成功的方法論是要能夠為多個的專案所接受,並且能夠成功實現軟體的交付的方法論。我和我的同事在實踐中做了一些試驗,希望能夠把一些好的方法論應用於開發團隊。試驗的結果很無奈,方法論實施的效果並不理想,一開始我們認為是方法本身的原因,到後來,我們發現事情並不是這麼簡單。在試驗的過程中,開發人員一致認同方法論的優勢所在,但是在實施過程中,鮮有堅持的下來的。在Agile Software Development中,我發現作者遇到了和我們一樣的問題。 Alistair Cockburn在和大量的專案團隊的訪談之後,寫成了Agile Software Development一書。在訪談之前,他篤定自己將會發現高度精確的過程控制是成功的關鍵所在,結果他發現事實並非如此,他把他的發現歸結為7條定律。而我在實際中的發現也包含在這七條定律中,總結起來就只有兩點:溝通和反饋。只要能夠保證良好的溝通和即時的反饋,那麼開發團隊即使並沒有採用先進的方法論,一樣可以成功。相反,那些"高質量"的團隊卻往往由於缺乏這兩個因素而導致失敗(我們這裡指的失敗是拒絕使用最終的軟體)。最有效,而成本也最低的溝通方法就是面對面(face to face)的溝通,而隨著專案團隊的變大,或是另外一些影響因素的加入(比如地理位置的隔絕),面對面的溝通越來越難實現,這導致溝通的的成本逐漸加大,質量也慢慢下降。但這並不是說非面對面的溝通不可,重要的是我們需要知道不同的溝通方式的成本和質量並不相同。XP方法尤為強調面對面的溝通,透過現場客戶、站立會議、結對等方式來保證溝通的有效。在我的經驗中,一個開發團隊其實是需要多種溝通方式的結合的。完全的面對面的溝通對某些團隊來說是很難實現的,那麼問題的關鍵就在於你如何應用溝通的方式來達到你希望的效果。在前不久結束的歐萊雅創業計劃大賽上,有一支團隊特別引人注目,他們彼此間素未謀面,僅僅憑藉Inte和電話完成了高效的合作。他們雖然沒有使用面對面的溝通方式,但是仍然達成了既定的目標。軟體開發也是一樣的,面對面的溝通是非常有必要的,但其它的溝通方式也是需要的。再看反饋,不論是控制進度,還是保證客戶的滿意度,這些活動都需要管理成本。軟體開發中的管理成本的一個通性就是伴隨有中間產出物(intermediate delivery)。比如說我們的需求規約、分析文件、設計文件、測試計劃,這些都屬於中間產出物。中間產出物的增加將會帶來下降的問題,因為開發人員的時間都花在了完成中間產出物的工作上,花在給軟體新功能上的時間就減少了。而中間產出物的主要目的是兩個,一個是為了保證軟體如客戶所願,例如需求規約;另一個是為了作為團隊中的其他成員工作的輸入,例如開發計劃、測試計劃等。因此,我們也可以針對這兩點來商討對策,一種是採用迭代的思想,提高軟體釋出的頻率,以保證客戶的需求被確實的滿足,另一種就是縮小團隊的溝通範圍,保證成員能夠從其他人那裡得到新的思路,而不是撰寫規範的內部文件(內部文件指那些僅為內部開發人員之間的溝通所需要的文件)。因此,一個軟體專案的成功和你採用的開發方法論並沒有直接的關係。重量我們根據把擁有大量artifact(RUP官方翻譯為工件,意思是軟體開發過程中的中間產物,如需求規約、設計模型等)和複雜控制的軟體開發方法稱為重型(Heavy Weight)方法,相對的,我們稱artifact較少的方法為輕型(Light Weight)方法。在傳統的觀念中,我們認為重型方法要比輕型許多。因為我們之所以想出重型方法,就是由於在中大型的專案中,專案經理往往遠離程式碼,他無法有效的瞭解目前的工程的進度、質量、成本等因素。為了克服未知的恐懼感,專案經理制定了大量的中間管理方法,希望能夠控制整個專案,最典型的莫過於要求開發人員頻繁的遞交各種表示專案目前狀態的報告。在Planning XP一書中有一段討論輕重型方法論的精闢論述,它把重型方法論歸結為一種性的姿態(defensive posture),而把輕型方法論歸結為一種渴望成功(Plan to win)的心態。如果你是採用了防禦性姿態,那麼你的工作就集中在防止和跟蹤錯誤上,大量的工作流程的制定,是為了保證專案不犯錯誤,而不是專案成功。而這種方法也不可謂不好,但前提是如果整個團隊能夠滿足前面所提到的兩個條件的話,專案也肯定會成功,但是重型方法論的一個弊端就在於,大家都在防止錯誤,都在懼怕錯誤,因此人和人之間的關係是很微妙的,要達到充分的溝通也是很難的。最終,連對人的評價也變成是以避免錯誤的多寡作為考評的依據,而不是成就。我們在做試驗的時候,一位專案經理開玩笑說,"方法論源自專案經理的恐懼,這沒錯。但最糟糕的是整個團隊只有專案經理一個人恐懼,如果能夠做到人人的恐懼,那大家也就沒有什麼好恐懼的了。"這句話提醒了我們,如果一個團隊的精神就是力求成功,那麼這支團隊的心態就和其它的團隊不同了,尤其是對待錯誤的心態上。根本就沒有必要花費大量的精力來預防錯誤,錯誤犯了就犯了,即時改正就可以了。這其實就是渴望成功的心態。方法論的藝術管理,被稱為科學和藝術的融合體,而管理的藝術性部分很大程度的體現為人的管理上。我說,方法學,一樣是科學和藝術的融合體。這是有依據的,其實方法論和管理學是近親關係,管理學中有一門分支是,而在軟體組織中,專案管理是非常重要的,方法學就是一種針對軟體開發的一種特定的專案管理(或是專案管理的一個子集)。重型方法最大的一個問題就在於他不清楚或忽略了藝術這個層次,忽視了人的因素,把人做為一個計量單位,一種資源,一種線性元素。而人的要素在軟體開發中是非常重要的,軟體開發實際上是一種知識、智力的轉移過程,最終形成的產品是一種知識產品,它的成本取決於開發者的知識價值,因此,人是最重要的因素。而人這個要素是很難衡量的,每個人都有不同的個性、想法、經驗、經歷,這麼多複雜的因素加在一起,就導致了人的不可預見性。因此,我們強調管人的藝術。最簡單的例子是,在重型方法中,我們的基本假設是對人的不信任。專案經理要控制專案。但不信任就會產生很多的問題,比如士氣不高,計劃趕不上變化,創新能力低下,跳槽率升高等等。人都是希望被尊重的,技術人員更看重這一點,而很多公司也口口聲聲說自己多麼多麼以人為本,可是採用的卻是以不信任人為前提的開發方法,言行不一。我們說敏捷方法的出發點是相互信任,做到這一點是很難的,但是一旦做到了,那這個團隊就是非常具有競爭力的。因此,這就產生了一個問題,在沒有做到完全的相互信任之前,我們到底相不相信他人呢,這就是我提到的藝術性的問題,什麼時候你要相信人?什麼時候你不相信人,這些都是需要權衡的問題,也都是表現你藝術性的問題。敏捷敏捷代表著有效和靈活。我們稱那些輕型的、有效的方法為敏捷方法。在重型方法中,我們在一些不必要、重複的中間環節上浪費了太多的精力,而敏捷則避免了這種浪費。我們的文章將會重點的討論敏捷(Agile)方法論的思想,敏捷這個名字的前身就是輕型。目前已經有了一個敏捷聯盟,他們制定了敏捷宣言: Individuals and interactions over processes and tools. Working software over comprehensive documentation. Customer collaboration over contract negotiation. Responding to change over following a plan. 而我對敏捷的理解包括了幾個方面: · 較低的管理成本和高質量的產出。軟體開發存在兩個極端:一個是沒有任何的管理成本,所有的工作都是為了軟體的產出,但是這種方式卻往往導致軟體開發過程的混沌,產品的低質量,團隊士氣的低落。另一個是大量管理活動的加入,評審、變更管理,缺陷跟蹤,雖然管理活動的加入能夠在一定程度上提高開發過程的有序性,但是成本卻因此提高,更糟糕的是,很容易導致團隊的低效率,降低創新能力。因此,敏捷方法檢視尋找一個平衡點,用低成本的管理活動帶來最大的產出,即軟體的高質量。 · 尊重人性。敏捷方法尊重人性,強調效率。軟體開發可以說是一種腦力的投入,如果不能保證開發人員的自願投入,產品就肯定要打折扣。事實多次的證明,一個願意投入的開發人員和一個不願意投入的開發人員效率相差在三倍以上,對組織的貢獻更是在十倍以上。 · 溝通和反饋是一切的基礎。我們已經討論過溝通的重要程度,而即時的反饋是擁抱變化的前提條件。 · 客戶是上帝。沒有客戶就沒有一切,客戶的重要性可以用一句話來形容,就是以合理的成本建造合適的軟體(build the right system at the right cost)。敏捷其實也有輕重之分,關鍵在於是否能夠做到有效和靈活。因此,敏捷方法論提倡的一個思想是"剛好夠(barely sufficient)"。不過這個"剛好夠"可不是那麼容易判斷的。一支8個人的團隊採用XP方法,隨著方法的熟練使用,團隊的能力在不斷的增強,能夠處理的問題越越來越複雜,也許他們能夠處理採用重型方法的20個人團隊能夠處理的問題。可是如果團隊的人數突然增加到12人,這支團隊肯定就會出問題,他的表現可能還不如那支20個人的團隊了。人數增加了的時候,原先的方法肯定還做適當的調整,比如說,在原先的敏捷方法上增加一些重型方法的技巧。我們不能夠要求一支6個人的團隊和一支20個人的團隊用同樣的方法,前者可能採用輕一些的敏捷方法,後者可能採用重一些的敏捷方法,關鍵的問題在於,兩支團隊都把重點放在溝通、反饋、頻繁交付軟體這些關鍵的因素上,也就是做到有效和靈活。架構設計架構(Architecture)(也有被稱為體系結構的)是軟體設計中非常重要的一個環節。軟體開發的過程中只要需求和架構確定之後,這個軟體就基本上可以定型了。這就好比骨骼確定了,這個人的體形就不會有很大的變化。因此我選擇了架構設計來討論敏捷軟體開發(需求我已經寫過了)。我們在前面討論過超集和子集的概念,因此我們接下去要討論的架構設計也是一個很小的子集。方法論如果沒有經歷過多個專案的檢驗是不能稱為成功的方法論的,我也並不認為我的架構設計就是一個好的方法論,但引玉還需拋磚,他的主要目的是為了傳播一種思想。因此,我採用了語言(PLOP)做為寫作架構設計的形式,主要的原因就是模式是一種很好的組織思想的方法。因此,在我們接下去的歷程中,我們集中討論的東西就圍繞著架構、方法學、敏捷這三個要素展開。這篇文章並不是討論如何編碼實現軟體架構的,也不要單純的把它看作架構設計的指南,其實文中的很多思想來自於方法論,因此提到的很多架構設計的思想也適用於其它工作,如果能夠了解這一點,看這篇文章的收穫可能會更多一些。 透過上一章的介紹,我們對敏捷和方法有了一個大致的瞭解,從這一章起,我們開始對軟體開發過程中架構設計的研究。記住一點,我們並不是為了架構設計而研究架構設計,我們的目的在於敏捷方法學的應用。架構設計是一種權衡(trade-off)。一個問題總是有多種的解決方案。而我們要確定唯一的架構設計的解決方案,就意味著我們要在不同的矛盾體之間做出一個權衡。我們在設計的過程總是可以看到很多的矛盾體:開放和整合,一致性和特殊化,穩定性和延展性等等。任何一對矛盾體都源於我們對軟體的不同期望。可是,要滿足我們希望軟體穩定執行的要求,就必然會影響我們對軟體易於擴充套件的期望。我們希望軟體簡單明瞭,卻增加了我們設計的複雜度。沒有一個軟體能夠滿足所有的要求,因為這些要求之間帶有天生的互斥性。而我們評價架構設計的好壞的依據,就只能是根據不同要求的輕重緩急,在其間做出權衡的合理性。目標我們希望一個好的架構能夠: · 重用:為了避免重複勞動,為了降低成本,我們希望能夠重用之前的程式碼、之前的設計。重用是我們不斷追求的目標之一,但事實上,做到這一點可沒有那麼容易。在現實中,人們已經在架構重用上做了很多的工作,工作的成果稱為(),比如說的視窗機制、平臺等。但是在企業商業建模方面,有效的框架還非常的少。 · 透明:有些時候,我們為了提高效率,把實現的細節隱藏起來,僅把客戶需求的介面呈現給客戶。這樣,具體的實現對客戶來說就是透明的。一個具體的例子是我們使用的tag技術來代替JSP的嵌入程式碼,因為我們的HTML介面人員更熟悉tag的方式。 · 延展:我們對延展的渴求源於需求的易變。因此我們需要架構具有一定的延展性,以適應未來可能的變化。可是,如上所說,延展性和穩定性,延展性和簡單性都是矛盾的。因此我們需要權衡我們的投入/產出比。以設計出具有適當和延展性的架構。 · 簡明:一個複雜的架構不論是測試還是維護都是困難的。我們希望架構能夠在滿足目的的情況下儘可能的簡單明瞭。但是簡單明瞭的含義究竟是什麼好像並沒有一個明確的定義。使用模式能夠使設計變得簡單,但這是建立在我熟悉設計模式的基礎上。對於一個並不懂設計模式的人,他會認為這個架構很複雜。對於這種情況,我只能對他說,去看看設計模式。 · 高效:不論是什麼系統,我們都希望架構是高效的。這一點對於一些特定的系統來說尤其重要。例如實時系統、高訪問量的網站。這些值的是技術上的高效,有時候我們指的高效是效益上的高效。例如,一個只有幾十到一百訪問量的資訊系統,是不是有必要使用技術,這就需要我們綜合的評估效益了。 · 安全:安全並不是我們文章討論的重點,卻是架構的一個很重要的方面。規則為了達到上述的目的,我們通常需要對架構設計制定一些簡單的規則:功能分解顧名思義,就是把功能分解開來。為什麼呢?我們之所以很難達到重用目標就是因為我們編寫的經常處於一種好像是重複的功能,但又有輕微差別的狀態中。我們很多時候就會經不住誘惑,用複製貼上再做少量修改的方式完成一個功能。這種行為在XP中是堅決不被允許的。XP提倡"Once and only once",目的就是為了杜絕這種複製修改的現象。為了做到這一點,我們通常要把功能分解到細粒度。很多的設計思想都提倡小類,為的就是這個目的。所以,我們的程式中的類和方法的數目就會大大增長,而每個類和方法的平均程式碼卻會大大的下降。可是,我們怎麼知道這個度應該要如何把握呢,關於這個疑問,並沒有明確的答案,要看個人的功力和具體的要求,但是一般來說,我們可以用一個簡單的動詞短語來命名類或方法的,那就會是比較好的分類方法。我們使用功能分解的規則,有助於提高重用性,因為我們每個類和方法的精度都提高了。這是符合大自然的原則的,我們研究自然的主要的一個方向就是將物質分解。我們的思路同樣可以應用在軟體開發上。除了重用性,功能分解還能實現透明的目標,因為我們使用了功能分解的規則之後,每個類都有自己的單獨功能,這樣,我們對一個類的研究就可以集中在這個類本身,而不用牽涉到過多的類。根據實際情況決定不同類間的耦合度雖然我們總是希望類間的耦合度比較低,但是我們必須客觀的評價耦合度。系統之間不可能總是松耦合的,那樣肯定什麼也做不了。而我們決定耦合的程度的依據何在呢?簡單的說,就是根據需求的穩定性,來決定耦合的程度。對於穩定性高的需求,不容易發生變化的需求,我們完全可以把各類設計成緊耦合的(我們雖然討論類之間的耦合度,但其實功能塊、模組、包之間的耦合度也是一樣的),因為這樣可以提高效率,而且我們還可以使用一些更好的技術來提高效率或簡化程式碼,例如中的內部類技術。可是,如果需求極有可能變化,我們就需要充分的考慮類之間的耦合問題,我們可以想出各種各樣的辦法來降低耦合程度,但是歸納起來,不外乎增加抽象的層次來隔離不同的類,這個抽象層次可以是具體的類,也可以是介面,或是一組的類(例如Beans)。我們可以借用Java中的一句話來概括降低耦合度的思想:"針對介面程式設計,而不是針對實現程式設計。" 設計不同的耦合度有利於實現透明和延展。對於類的客戶(者)來說,他不需要知道過多的細節(實現),他只關心他感興趣的(介面)。這樣,目標類對客戶來說就是一個黑盒子。如果介面是穩定的,那麼,實現再怎麼擴充套件,對客戶來說也不會有很大的影響。以前那種牽一髮而動全身的問題完全可以緩解甚至避免。其實,我們仔細的觀察GOF的23種設計模式,沒有一種模式的思路不是從增加抽象層次入手來解決問題的。同樣,我們去觀察Java原始碼的時候,我們也可以發現,Java原始碼中存在著大量的抽象層次,初看之下,它們什麼都不幹,但是它們對系統的設計起著重大的作用。夠用就好我們在上一章中就談過敏捷方法很看重剛好夠用的問題,現在我們結合架構設計來看:在同樣都能夠滿足需要的情況下,一項複雜的設計和一項簡單的設計,哪一個更好。從敏捷的觀點來看,一定是後者。因為目前的需求只有10項,而你的設計能夠滿足100項的需求,只能說這是種浪費。你在設計時完全沒有考慮成本問題,不考慮成本問題,你就是對開發組織的不負責,對客戶的不負責。應用模式這篇文章的寫作思路很多來源於對模式的研究。因此,文章中到處都可以看到模式思想的影子。模式是一種整理、傳播思想的非常優秀的途徑,我們可以透過模式的方式學習他人的經驗。一個好的模式代表了某個問題研究的成果,因此我們把模式應用在架構設計上,能夠大大增強架構的穩定性。抽象架構的本質在於其抽象性。它包括兩個方面的抽象:業務抽象和技術抽象。架構是現實世界的一個模型,所以我們首先需要對現實世界有一個很深的瞭解,然後我們還要能夠熟練的應用技術來實現現實世界到模型的對映。因此,我們在對業務或技術理解不夠深入的情況下,就很難設計出好的架構。當然,這時候我們發現一個問題:怎樣才能算是理解足夠深入呢。我認為這沒有一個絕對的準則。一次,一位朋友問我:他現在做的系統有很大的變化,原先設計的工作流架構不能滿足現在的要求。他很希望能夠設計出足夠好的工作流架構,以適應不同的變化。但是他發現這樣做無異於重新開發一個lotus notes。我聽了他的疑問之後覺得有兩點問題:首先,他的開發團隊中並沒有工作流領域的專家。他的客戶雖然瞭解自己的工作流程,但是缺乏足夠的理論知識把工作流提到抽象的地步。顯然,他本身雖然有技術方面的才能,但就工作流業務本身,他也沒有足夠的經驗。所以,設計出象notes那樣的系統的前提條件並不存在。其次,開發一個工作流系統的目的是什麼。原先的工作流系統運作的不好,其原因是有變化發生。因此才有改進工作流系統的動機出現。可是,畢竟notes是為了滿足世界上所有的工作流系統而開發的,他目前的應用肯定達不到這個層次。因此,雖然做不到最優的業務抽象,但是我們完全可以在特定目的下,特定範圍內做到最優的業務抽象。比如說,我們工作流可能的變化是工組流路徑的變化。我們就完全可以把工作流的路徑做一個抽象,設計一個可以動態改變路徑的工作流架構。有些時候,我們雖然在技術上和業務上都有所欠缺,沒有辦法設計出好的架構。但是我們完全可以借鑑他人的經驗,看看類似的問題別人是如何解決的。這就是我們前面提到的模式。我們不要把模式看成是一個硬性的解決方法,它只是一種解決問題的思路。Martin Fowler曾說:"模式和業務的區別就在於模式會引發你的思考。" 在《分析模式》一書中,Martin Fowler提到了分析和設計的區別。分析並不僅僅只是用用例列出所有的需求,分析還應該深入到表面需求的的背後,以得到關於問題本質的Mental Model。然後,他引出了概念模型的概念。概念模型就類似於我們在討論的抽象。Martin Fowler提到了一個有趣的例子,如果要開發一套軟體來模擬桌球遊戲,那麼,用用例來描述各種的需求,可能會導致大量的運動軌跡的出現。如果你沒有了解表面現象之後隱藏的運動定律的本質,你可能永遠無法開發出這樣一個系統。關於架構和抽象的問題,在後面的文章中有一個測量模式的案例可以很形象的說明這個問題。架構的一些誤解我們花了一些篇幅來介紹架構的一些知識。現在回到我們的另一個主題上來。對於一個敏捷開發過程,架構意味著什麼,我們該如何面對架構。這裡我們首先要澄清一些誤解: · 誤解1:架構設計需要很強的技術能力。從某種程度來說,這句話並沒有很大的錯誤。畢竟,你的能力越強,設計出優秀架構的機率也會上升。但是能力和架構設計之間並沒有一個很強的聯絡。即使是普通的程式設計人員,他一樣有能力設計出能實現目標的架構。 · 誤解2:架構由專門的設計師來設計,設計出的藍圖交由程式設計師來實現。我們之所以會認為架構是設計師的工作,是因為我們喜歡把軟體開發和建築工程做類比。但是,這兩者實際上是有著很大的區別的。關鍵之處在於,建築設計已經有很長的歷史,已經發展出完善的理論,可以透過某些理論(如力學原理)來驗證設計藍圖。可是,對軟體開發而言,驗證架構設計的正確性,只能夠透過寫程式碼來驗證。因此,很多看似完美的架構,往往在實現時會出現問題。 · 誤解3:在一開始就要設計出完善的架構。這種方式是最傳統的前期設計方式。這也是為XP所摒棄的一種設計方式。主要的原因是,在一開始設計出完美的架構根本就是在自欺欺人。因為這樣做的基本假設就是需求的不變性。但需求是沒有不變的(關於需求的細節討論,請參看拙作『需求的實踐』)。這樣做的壞處是,我們一開始就限制了整個的軟體的形狀。而到實現時,我們雖然發現原來的設計有失誤之處,但卻不願意面對現實。這使得軟體畸形的生長。原本一些簡單的問題,卻因為彆扭的架構,變得非常的複雜。這種例子我們經常可以看到,例如為相容前個版本而導致的軟體複雜性。而2000年問題,的安全性問題也從一個側面反映了這個問題的嚴重性。 · 誤解4:架構藍圖交給程式設計師之後,架構設計師的任務就完成了。和誤解2一樣,我們借鑑了建築工程的經驗。我們看到建築設計師把設計好的藍圖交給施工人員,施工人員就會按照圖紙建造出和圖紙一模一樣的大廈。於是,我們也企圖在軟體開發中使用這種模式。這是非常要命的。軟體開發中缺乏一種通用的語言,能夠充分的消除設計師和程式設計師的溝通隔閡。有人說,UML不可以嗎?UML的設計理念是好的,可以減輕溝通障礙問題。可是要想完全解決這個問題,UML還做不到。首先,程式設計師都具有個性化的思維,他會以自己的思維方式去理解設計,因為從設計到實現並不是一項機械的勞動,還是屬於一項知識性的勞動(這和施工人員的工作是不同的)。此外,對於程式設計師來說,他還極有可能按照自己的想法對設計圖進行一定的修改,這是非常正常的一項舉動。更糟的是,程式設計師往往都比較自負,他們會潛意識的排斥那些未經過自己認同的設計。架構設計的過程模式通常我們認為模式都是用在軟體開發、架構設計上的。其實,這只是模式的一個方面。模式的定義告訴我們,模式描述了一個特定環境的解決方法,這個特定環境往往重複出現,制定出一個較好的解決方法有利於我們在未來能有效的解決類似的問題。其實,在管理學上,也存在這種類似的這種思維。稱為結構性問題的程式化解決方法。所以呢,我們完全可以把模式的思想用在其它的方面,而目前最佳的運用就是過程模式和組織模式。在我們的文章中,我們僅限於討論過程模式。我們討論的過程僅限於面向的軟體開發過程。我們稱之為OOSP(-oriented software process )。因為我們的過程需要物件導向特性的支援。當然,我們的很多做法一樣可以用在非OO的開發過程中,但是為了達到最佳的效果,我建議您使用OO技術。那麼,我們應該如何避開這些誤區呢,或者,換句話說,敏捷軟體開發是如何做架構設計的。這裡有幾種過程模式:圖 2. 敏捷架構過程模式概覽(High-Level)在接下去的篇幅中,我們會逐一對各種過程模式進行介紹。然後再站在全域性的角度分析各個模式之間的關係,並將之歸納為架構設計的模式。敏捷型架構設計我們說我們這裡列出的過程模式是敏捷型的,關於這一點我們會在接下去的各個章節中驗證這一點。我們列出的各個過程模式並不是完全照搬敏捷型方法,因為在各種敏捷型方法中,某些技巧適合架構設計,某些方法則不適合架構設計。因此,我們在採用一種方法和技術前,我們會問自己幾個簡單的問題:該方法/技巧有什麼價值? 該方法/技巧需要多大的投入?從建立、維護、培訓等多方面估計。 比較該方法/技巧的投入和價值,它還值得我們採用嗎? 是否還有其它價值/投入比更高的方法/技巧呢?在我們的文章中,每一種方法/技巧的討論都回答了前三個問題,至於第四個問題,希望有同行能夠告訴我。 我們說,和重型方法偏重於計劃、過程和中間產物不同,敏捷方法更加看重人和溝通。人和溝通永遠是第一位的,而計劃、過程和中間產物,那只是保證溝通、實現目標的手段。這並不是說計劃、過程、中間產物不重要,只是不能夠本末倒置注:我們把中間產物定義為為了實現跨邊界的溝通而制定的文件、模型、程式碼。例如設計文件、資料模型等。參考RUP的Artifact。評判軟體成功的標準有很多,對於敏捷方法論來說,成功的標準首先在於交付可用的軟體。為了保證軟體的可用性,最重要的就是做好需求。做好需求的方法有很多(參見拙作需求的實踐),但這並不是我們討論的主題。對於我們要開始的架構設計的工作來說,從需求出發來設計架構,這就是保證軟體可用性的一個基本的保證。 Context 我們如何開始我們的架構設計工作? Problem 我們在進行架構設計的時候,往往主要考慮的都是平臺、語言、開發環境、等一些基本問題,可是對於和客戶的具體情況密切相關的一些問題卻很少系統的考慮。甚至還存在一種誤區,認為架構設計無非就是寫一些空話,套話。這樣子做出來架構設計,如何用於指導軟體的實現呢? IT界的技術層出不窮,面對著如此之多的技術、平臺、框架、庫,我們如何選擇一組適合軟體的技術?每一個客戶的軟體都有自身的特點,如何才能夠設計出符合客戶利益的架構?軟體中往往都充斥著眾多的問題,在一開始就把所有的問題都想清楚往往很難做到,但是如果不解決問題,風險又居高不下。 Solution 針對需求設計架構。架構設計就是鋪設軟體的主管道(例1)。我們根據什麼來制定主管道的粗細、路徑等因素呢?很明顯,是根據城市的人口、地理位置、水源等因素來決定的。對應到軟體設計也是一樣的。城市的各因素就是軟體中的各種需求:功能需求、非功能需求、變化案例等等。一般來說,功能需求決定業務架構、非功能需求決定技術架構,變化案例決定架構的範圍。需求方面的知識告訴我們,功能需求定義了軟體能夠做些什麼。我們需要根據業務上的需求來設計業務架構,以使得未來的軟體能夠滿足客戶的需要。非功能需求定義了一些、效率上的一些、規則。而我們的技術架構要能夠滿足這些約束和規則。變化案例是對未來可能發生的變化的一個估計,結合功能需求和非功能需求,我們就可以確定一個需求的範圍,進而確定一個架構的範圍。從例2中,我們看到自已字處理軟體的幾種需求的範例。真正的字處理軟體要複雜的多。而我們最主要的就是必須認識到,架構是來自於需求的。有什麼樣的需求就有什麼樣的架構。試想一下,如果我們沒有對速度的要求,我們還需要考慮這方面的設計嗎?我們上面提到了幾種型別的需求對架構的影響,其實還有一個很重要的需求,就是環境的需求。這並不是一個很重要的需求,但是對於部署(deployment)架構設計來說就特別重要。畢竟,我們開發出的軟體是要上"戰場"的,充分的考慮部署問題是非常有必要的。 例1:城市中自來水管的架設是一項非常的複雜的工程。為了需要滿足每家每戶的需要,自來水管組成了一個龐大的網路。在這樣一個複雜的網路中,如何完成鋪設的任務呢。一般的做法是,先找出問題的根源,也就是水的源頭。從水源鋪設一條管道通至城市,然後根據城市的區域劃分,設計出主管道,剩下的就是使用的問題了,每家每戶的管道最終都是連到主管道上的。因此,雖然自來水網路龐大複雜。但是真正的主管道的非常簡單的。例2:我們打算開發一個字處理軟體,功能需求可以簡單概括為格式化使用者輸入的文字,非功能需求可能是格式化大小為1000K的一段文字的處理速度不能低於10S,變化案例可能是推出多種語言版本。那麼我們在設計業務架構的時候,我們會集中於如何表示文字、圖象、等要素,我們該需要有另外的技術架構來處理速度問題,比如緩衝技術,對於變化案例,我們也要考慮相應的架構,比如把字型獨立於程式包的設計。 從需求到架構。在需求階段,我們可以得到一些代表需求調研成果的中間產物。比如說,CRC卡片、基本用例模型、使用者素材、介面原型、介面原型流程圖、非功能需求、變化案例等。我們在架構設計階段的主要工作就是要把這些需求階段的中間產物轉換為架構設計階段的中間產物。圖 3. 需求階段的中間產物 其實,架構設計就是要完成兩項工作,一是分析,二是設計。分析是分析需求,設計則是設計軟體的大致結構。很多的方法論把分析和設計兩種活動分開來,但其實這兩者是很難區分的,做分析的時候會想到如何設計,而思考如何設計反過來又會影響分析的效果。可以說,他們兩者之間是相互聯絡和不斷迭代的。這種形態我們將會在後面的迭代設計模式中詳細的討論。在敏捷方法論中,需求最好是迭代進行的,也就是說一點一點的作需求。這種做法在那些需求變化快的專案中尤其適用。由於我們採用的流程是一種迭代式的流程,這裡我們將會面臨著如何對待上一次迭代的中間產物的問題。如果我們每一次迭代都需要修改已存在的中間產物,那麼這種維護的成本未免過大。因此,敏捷方法論的基本做法是,扔掉那些已經沒有用處的中間產物。還記得在第一章的時候,我們強調說軟體要比文件重要。我們生成中間產物的目的都是為了生成最終的程式,對於這些已經完成作用的模型,沒有必要付出額外的維護成本。不要斷章取義的採用拋棄模型的做法。因為,拋棄模型的做法需要一個適合環境的支援。後面會針對這個話題開展大範圍的討論。這裡我們簡單的做一個瞭解: · 簡單化:簡單的模型和簡單的程式。模型和程式越複雜,就需要更多的精力來處理它們。因此,我們儘可能的簡化它們,為的是更容易的處理它們。 · 高效的溝通渠道:透過增強溝通的效果來減少對中間產物的需要。試想一下,如果我隨時能夠從客戶那裡得到需求的細節資料,那前期的需求調研就沒有必要做的太細緻。 · 角色的交叉輪換:開發人員之間建立起角色的機制,這樣,能夠儘量的避免各子系統諸侯割據的局面。 · 清晰的流程:或者我們可以稱之為明確的過程。過程在方法論中向來都是一個重點,敏捷方法論也不例外。開發人員能夠清楚的知道,今天做什麼,明天做什麼。過程不是給別人看的,而是給自己用的。 · 工具:好用的工具能夠節省大量的時間,這裡的工具並不僅僅指CASE工具,還包括了版本控制工具、自動化測試工具、畫圖工具、文件製作和管理工具。使用工具要注意成本和效益的問題。 · 標準和風格:語言不通是溝通的一個很大的障礙。語言從某個角度來看屬於一種標準、一種風格。因此,一個團隊如果採用同樣的編碼標準、文件標準、註釋風格、製圖風格,那麼這個團隊的溝通效率一定非常的高。如果上述的環境你都不具備,或是欠缺好幾項,那你的文件的模型還是留著的好。僅針對需求設計架構僅針對需求設計架構的含義就是說不要做未來才有用的事情。有時候,我們會把架構考慮的非常複雜,主要的原因就是我們把很多未來的因素放入到現在來考慮。或者,我們在開發第一個產品的時候就檢視把它做成一個完美的框架。以上的這兩種思路有沒有錯呢?沒有錯,這只是如何看待投入的問題,有人希望開始的時候多投入一些,這樣後續的投入就會節省下來。但在現實中,由於需求的不確定性,希望透過增加開始階段的投入來將降低未來的投入往往是難以做到的,框架的設計也絕對不是能夠一蹴而就的,此這種做法並不是一個好的做法。所以我們在後頭會著重論述架構設計的簡單性和迭代過程,也就是因為這個理由。模式模式將可以幫助我們抓住重點。設計模式在書的一開始(第二章)就討論了一個設計一個文件編輯器的問題。為了解決設計文件編輯器引出的七個問題,一共使用了8種不同的模式。這8種模式的組合其實就是架構,因為它們解決的,都是系統中最高層的問題。在實踐中,人們發現架構也是存在模式的。比如,對於系統結構設計,我們使用層模式;對於分散式系統,我們使用模式;對於互動系統,我們使用MVC(模型-檢視-控制器)模式。模式本來就是針對特定問題的解,因此,針對需求的特點,我們也可以採用相應的模式來設計架構。在sun網站上提供的寵物商店的範例中,就把MVC模式的思想擴充套件成為架構的思想,用於提供不同的介面檢視: MVC架構圖,這裡提供原圖的概覽,檢視其出處請點選這裡。我們可以瞭解到在圖的背後隱藏著的需求:系統需要支援多種使用者介面,包括為普通使用者提供的HTML介面,為使用者提供的WML介面,為管理員提供的介面,以及為B2B業務設計的service介面。這是系統最重要的需求,因此,系統的設計者就需要確定一個穩定的架構,以解決多介面的問題。相對於多介面的問題,後端的業務處理邏輯都是一致的。比如HTML介面和WML介面的功能並沒有太大的差別。把處理邏輯和介面分離開來還有額外的好處,可以在新增功能的同時,不涉及介面的改動,反之亦然。這就是我們在第二篇中提到的耦合度的問題。 MVC模式正可以適用於解決該問題。系統使用控制器來為業務邏輯選擇不同的介面,這就完成了MVC架構的設計思路。在架構設計的工作中,我們手頭上有模式這樣一張好牌,有什麼理由不去使用它呢?抓住重點在架構設計一開始,我們就說架構是一種抽象,那就是說,架構設計摒棄了具體的細節,僅僅抓住軟體最高層的概念,也就是最上層、優先順序最高、風險最大的那部分需求。我們考慮、分析、解決一個問題,一定有一個漸進的過程。架構設計就是解決問題其中比較早期的一個階段,我們不會在架構設計這個階段投入過多的時間(具體的原因在下文會有討論),因此關鍵點在於我們要能夠在架構設計中把握住需求的重點。比如,我們在模式一節中提到了分散式系統和互動系統,分佈和互動就是這兩個系統的重點。那麼,如果說我們面對的是一個分散式的互動系統,那麼,我們就需要把這兩種特性做為重點來考慮,並以此為基礎,設計架構。而我們提到的寵物商店的範例也是類似的,除了MVC的架構,還有很多的設計問題需要解決,例如用於資料庫訪問的資料物件,用於檢視管理的前端控制器,等等(具體使用到的架構模式可以訪問sun的網站)。但是這些相對於MVC模式來說,屬於區域性的,優先順序較低的部分,可以在架構確定後再來設計。架構設計和領域專家一個架構要設計的好,和對需求的理解是分不開的。因此在現實中,我們發現業務領域專家憑藉著他對業務領域的瞭解,能夠幫助開發人員設計出優秀的架構來。架構是需要抽象的,它是現實社會活動的一個基本模型,而業務領域的模型僅僅憑開發人員是很難設計出來的。在ERP的發展史上,我們看到MRP發展為MRPII,在發展到閉環MRP,直到發展成為現在的ERP,主要的因素是管理思想的演化,也就是說,對業務領域的理解進步了,架構才有可能進步。因此,敏捷型架構設計的過程中,我們也非常強調領域專家的作用。例3:信貸系統在一個銀行的信貸帳務處理系統中,我們應該如何把握最初的架構思路呢?從需求上來看,這個信貸帳務處理系統有幾個特點:它不是一個單獨的系統,它需要和外部的其它系統互動,例如信貸業務系統、網上銀行、資料倉儲系統等。在所有的需求中,最複雜的就是它的利息計算的需求,它要求能夠支援多種的利息演算法。因此,我們的架構設計首先是從系統的全域性環境開始考慮。其它系統和該系統的關係如何,應該如何設計系統間的介面。透過需求的調研,系統的介面分為4類:和企業外部系統的介面。信貸系統需要連線到人民銀行的系統。和企業內部系統的介面。信貸系統需要能夠為資料倉儲系統和網上銀行系統提供資料。和平級系統的介面。信貸系統需要從平級的帳戶、資金、財務系統中取資料,並向帳戶、押匯系統傳送資料。具體的實現策略並不在我們的討論範圍之內,但是可以看到,架構策略的制定是以需求為基礎的。我們可以把這部分的需求歸納為技術架構或平臺架構。然後是利息演算法的問題,我們經過統計,目前的利息計算方式有四種,可預見到的還有兩種。在一開始的階段,我們並不需要考慮具體演算法的實現,但是我們需要考慮演算法的實現框架,因此我們很自然的想到Strategy模式可以勝任這一工作,把不同的利息演算法封裝起來。而我們的工作重點也就轉到定義利息演算法的介面問題。透過分析、比較多種利息演算法,我們定義了一個最初始的演算法介面,然後由不同的利息演算法來實現演算法介面。雖然,這個介面目前還不是很完整,但是它會在接下去的開發過程中慢慢的完善起來。這部分的需求屬於業務架構的一部分。考慮到系統的結構非常的複雜,因此在系統結構的處理上,我們採用了層模式做為系統的基本結構。此外,在每個層,我們還定義了幾個子模組來處理特定的問題。這樣,我們就可以將複雜的功能有序的組織起來。經過上述的分析,我們對系統的架構有了一個簡單的認識,但是還沒有結束,一個架構應該包括系統的各個基本部分,因此,我們還要考慮票據處理、報表、帳務處理等環節,但是一開始就考慮周詳,這要花費大量的時間,因此我們只是簡單的定義了一個原始的架構,然後在後續的開發過程中把這個架構完善起來。 團隊設計是敏捷方法論中很重要的一項實踐。我們這裡說的團隊,指的並不是複數的人。一群人就是一群人,並沒有辦法構成團隊。要想成為團隊,有很多的工作要做。 我們之所以考慮以團隊為單位來考慮架構設計,是因為軟體開發本身就不是一件個人的事情,架構設計更是如此。單個人的思維不免有考慮欠妥之處,單個人的學識也不可能覆蓋所有的學科。而組織有效的團隊卻能夠彌補這些缺憾。 Context 誰來負責架構的設計? Problem 在我們的印象中,總認為架構設計是那些所謂架構設計師的專屬工作,他們往往擁有豐富的設計經驗和相關的技能,他們不用編寫程式碼,就能夠設計出理論上盡善盡美的架構,配有精美的圖例。問題1:理論上設計近乎完美的架構缺乏程式的證明,在實際應用中往往會出這樣那樣的問題。問題2:設計師設計架構帶有很大的主觀性,往往會忽視客戶的需求,導致架構無法滿足需求。問題3:實現的程式設計師對這種架構有牴觸的情緒,或是因為不理解架構而導致架構實現的失敗。問題4:架構師設計架構主要是依據自己的大量經驗,設計出的架構不能真實的反映目前的軟體需要。 Solution 團隊設計的理論依據是群體決策。和個人決策相比,群體決策的最大好處就是其結論要更加的完整。而群體決策雖然有其優點,但其缺點也是很明顯的:需要額外付出溝通成本、決策效率低、責任不明確、等等。但是群體決策如果能夠組織得當的話,是能夠在架構設計中發揮很大的優勢的。避免象牙塔式的架構設計對軟體來說,架構設計是一項至關重要的工作。這樣的工作交給某個人是非常危險的。即便這個人再怎麼聰明,他也可能會遺漏部分的細節。組織有效的團隊的力量是大大超過個人的力量的,因此團隊的成果較之個人的成果,在穩定性和思考的周密程度上,都要更勝一籌。 tt W. Ambler在其著作中給出了象牙塔式架構(ivory tower architecture)的概念: An ivory tower architecture is one that is often developed by an architect or architectural team in relative isolation to the day-to-day development activities of your project team(s). 中國現在的軟體開發行業中也逐漸出現了象牙塔式的架構設計師。這些架構師並不參與實際的程式編寫,他的工作就是為專案製作出精美的架構模型,這種架構模型在理論上是相當完美的。例1:在XP中,我們基本上看不到架構設計的影子。並不是說採用XP技術的團隊就不需要架構設計。XP不存在專門的設計時期,它提倡使用一些簡單的圖例、比喻的方式來表達軟體的架構,而這種的架構設計是無時無刻不在進行的。其實,XP中的設計採用的就是團隊設計的方式,結隊程式設計(Pair Programming)和程式碼的集體所有制(Collective Ownership)是團隊設計的基礎,也就是基於口述的溝通方式。透過採用這樣的方式,XP幾乎不需要文件來表達架構的設計。 優秀的架構師能夠充分的利用現有框架,減少軟體的投入,增強軟體的穩定性。這些都沒有錯,但是問題在於“過猶不及”。象牙塔式架構師往往會出現文章開始指出的那些問題。架構設計其實並不是非常複雜的工作,但它要求開發人員具備相關的技能、經驗以及對問題域有一定的瞭解。開發人員往往都具有相關的技術技能(程式設計、資料庫設計、建模),而對問題域的理解可以從使用者和行業專家那裡獲得幫助。因此,在理論上,我們要實現架構設計的團隊化是完全可能的。在上面的象牙塔式架構定義中,我們看到架構師和日常的開發工作是隔絕的。這樣的設計出的架構有很大的侷限性。在現實中,我們還會發現另外一種角色,他來自於開發團隊外部,為開發人員提供相關的技術或業務的培訓。這種角色稱為教練,在軟體開發中是非常重要的角色,不能夠和象牙塔式架構設計師之間畫等號。選擇你的設計團隊。 軟體的架構在軟體的生命週期的全過程中都很重要,也就是說,軟體開發團隊中的所有人員都需要和架構打交道。因此,最好的團隊組織方式是所有開發人員都參與架構的設計,我們稱這種方式為全員參與。全員參與的方式保證了所有開發人員都能夠對架構設計提出自己的見解,綜合多方面的意見,在全體開發人員中達成一致。這種方式尤其適合於一些小的團隊。還是會有很多的團隊由於種種的原因不適合採用全員參與的方式。那麼,組織優秀的開發人員組成設計組也是比較好的方式。一般,我們選擇那些在專案中比較重要的,有較多開發經驗,或是理論紮實的那些人來組成設計組。當然,如果你考慮到為組織培養後續力量,你也可以讓一些新手加入設計組,或是你覺得自己的開發力量不足,邀請外部的諮詢力量介入,這完全取決於具體的情況。 設計組不同於我們之前提到的象牙塔式架構設計師。設計組設計出來的架構只能稱為原始架構,它是需要不斷的反饋和改進的。因此,在架構實現中,設計組的成員將會分佈到開發團隊的各個領域,把架構的思想帶給所有開發人員,編寫程式碼來檢驗架構,並獲得具體的反饋,然後所有的成員再集中到設計組中討論架構的演進。團隊設計中存在的問題在團隊設計的過程,我們會遇到各種各樣的問題,首當其衝的就是溝通成本的問題。架構設計時,需求尚未被充分理解,軟體的設計思路還處於萌發的狀態。這樣的情況下,團隊的每位成員對軟體都有獨特的見解,這些可能有些是相同的,有些是互斥的。就好比盲人摸象一樣,他們的觀點都代表了軟體的一部分或是一方面,但是沒有辦法代表軟體的全部。在敏捷方法論中,我們的每一個流程都是迅速進行、不斷改進的。架構設計也是一樣,我們不可能在一次架構設計上花費更多的時間。而團隊決策總是傾向於較長的討論和權衡。例2中的問題在架構設計中時有發生,純技術的討論很容易上升稱為爭吵。這種情況幾乎沒有辦法完全避免。團隊型的決策必然會發生觀念的衝突。控制一定程度內的觀念的衝突對團隊的決策是有益,但是如果超出了這個程度就意味著失控了,需要團隊領導者的調節。而更重要的,我們需要注意溝通的技巧:團隊溝通團隊進行架構設計的時候溝通是一個非常需要注意的問題,上述的情境在軟體組織中是經常發生的,因為技術人員很自然認為自己的技術比別人的好,如果自己的技術受到質疑,那怕對方是抱著討論的態度,也無異於自身的權威受到了挑戰,面子是無論如何都需要捍衛的。而溝通如果帶上了這樣一層主觀色彩,那麼溝通訊息的受眾就會潛意識的拒絕接受資訊。相反,他會找出對方話語中的,準備進行反擊。因此,我們要注意培養一種良好的溝通氛圍。在實際的觀察中,我發現團隊溝通中存在兩種角色,一種是建議者,他們經常能夠提出建議。一種是質疑者,他們對建議提出否定性的看法。這兩種角色是可能互換的,現在的建議者可能就是剛才的質疑者。質疑者的發言是很能打擊建議者的積極性的,而在一個腦力激盪的會議中,最好是大家都能夠扮演建議者的角色,這就要求溝通會議的主持者能夠掌握好這一點,對建議給予肯定的評價,並鼓勵大家提出新的建議。例2:敏捷方法非常注重的就是團隊的溝通。溝通是一個很有意思的話題,講起來會花費大量的時間,我們這裡只是針對架構設計中可能存在的溝通問題做一個簡單的討論。我們這裡假設一個討論情境,這個情境來源於真實的生活:專案主管徐輝、設計師李浩、設計師羅亦明正在討論一個新的軟體架構。 "李浩你認為這個軟體資料庫連線部分應該如何考慮?"徐輝問。李浩想了想,"我覺得方案A不錯…" "方案A肯定有問題!這個軟體和上一次的又不同。"羅亦明打斷了李浩的發言。 "你懂什麼!你到公司才多久,方案A是經過很長時間的證明的!"發言被打斷,李浩有點惱火,羅亦明進入公司沒有多久,但在一些事情上老是和他唱反調。 "我進公司多久和方案A的錯誤有什麼關係!" 在這樣一種氛圍中,會議的結果可想而知。 良好的溝通有助於架構設計工作的開展。一個成員的能力平平的團隊,可以藉由良好的溝通,設計出優秀的架構,而一個擁有一個優秀成員的團隊,如果缺乏溝通,最後可能連設計都出不來。這種例子現實中可以找到很多。標準和風格 我們總是在不知不覺之中使用各種各樣的標準和風格。在團隊設計中,我們為了提高決策的效率,可以考慮使用統一的標準和風格。統一的標準和風格並不是一朝一夕形成的。因為每個人都有自己不同的習慣和經歷,強制性的要求開發人員使用統一的標準(風格)容易引起開發人員的不滿。因此在操作上需要注意技巧。對架構設計而言,比較重要的標準(風格)包括以下的這些類別:介面設計 流程設計 建模規範 編碼規範 持久層設計 測試資料 在我的經驗中,有一些組織平時並不注意標準(風格)的積累,認為這種積累屬於雕蟲小技,但正是這些小技,能夠非常有效的提高溝通的效率和降低開發人員的學習曲線。試想一下,如果一個團隊中所有人寫出的程式碼都是不同標準和風格的,那麼理解起來肯定會困難許多。當然,我們沒有必要自己開發一套標準(風格)出來,現實中有很多可以直接借用的資料。最好的標準是UML語言,我們可以從UML的官方網站到最新的規範,常用的編碼標準更是隨處可見。不過雖然有了統一的標準,如果風格不統一,同樣會造成溝通的障礙。例如下圖顯示的類圖,雖然它們表示的是同一個類,但是由於版型、可視性、詳細程度的差別,看起來又很大的差別。而在其它的標準中,這種差別也是普遍存在的。因此,我們在使用了統一的標準之後,還應該使用同樣的風格。Scott W. Ambler專門成立了一個網站討論UML的建模風格的相關問題,有興趣的讀者可以做額外的閱讀。圖 4. 兩種風格的類圖 在統一的風格的基礎上更進一步的是使用術語。使用溝通雙方都瞭解專門的術語,可以代表大量的資訊。最好的術語的範例就是設計模式的模式名。如果溝通的雙方都瞭解設計模式,那麼一方只需要說這部分的設計可以使用工廠模式,另一方就能夠理解,而不用再詳細的解釋設計的思路。這種的溝通方式是最高效的,但它所需要的學習曲線也會比較陡。團隊設計的四明確為了最大程度的提高團隊設計的高效性,可以從4個方面來考慮: 1、明確目標 泛泛的召開架構討論會議是沒有什麼意義的,一個沒有鮮明主題的會議也不會有什麼結果。在源自需求的模式中,我們談到說可以有非功能需求的架構,可以有功能需求的架構。因此,在進行團隊設計之前,我們首先也需要確定,此次要解決什麼問題,是討論業務邏輯的架構,還是技術架構;是全域性性的架構,還是各模組的架構。 2、明確分工 我們之所以重視團隊,很重要的額一個原因就是不同的成員有不同的擅長的區域。有些成員可能擅長於業務邏輯的建模,有的擅長於原型設計,有的擅長於資料庫設計,有的則擅長於Web程式設計。你能夠想象一個軟體沒有介面嗎?(有些軟體可能是這種情況)你能夠想象一個軟體只有資料庫,而沒有處理邏輯嗎?因此,架構設計就需要綜合的考慮各個方面,充分利用成員的優勢。這就要求團隊的各個成員都能夠明確自己的分工。 3、明確責權除了明確自己的分工,每位成員都需要清楚自己的責任。沒有責任,分工就不會有任何的效力。每位成員都需要明確自己要做些什麼。當然,和責任相對的,沒有成員還需要知道自己的權力是什麼。這些清楚了,進行高效的溝通的前提就具備了。每次架構的討論下來,每個人都清楚,自己要做些什麼,自己需要要求其他人做些什麼,自己該對誰負責。如果這些問題回答不了,那這次的討論就白費了。 4、明確溝通方式這裡使用溝通方式可能有一點點不恰當,為了明確的表達意思,大家可以考慮資訊流這個詞。一個完整架構包括幾個方面,分別都由那些人負責,如何產生,產生的整個過程應該是什麼樣的?這樣的一個資訊流程,囊括了上面提到的三個明確。如果團隊的每一個人都能夠為架構的產生而努力,並順利的設計出架構,那麼這樣的流程是完美的。如果你發現其中的一些人不知道做些什麼,那麼,這就是流程出問題的現象了。完美的流程還會有一個額外的副產品,架構產生之後,團隊對於軟體的設計已經是非常的清晰了。因為我們提倡的是儘可能多的開發人員參與架構的設計。不僅僅是架構討論到這裡,其實有很多的內容已經脫離了架構設計了。也就是說,很多的原則和技巧都是可以用於軟體開發的其它活動的。至於哪一些活動能夠利用這些方法呢?大家可以結合自己的實際情況,來思考這個問題。提示一點,關鍵的入手處在於目前效率較低之處。 XP非常強調簡單的設計原則:能夠用陣列實現的功能決不用連結串列。在其它Agile方法中,簡單的原則也被反覆的強調。在這一章,我們就對簡單性做一個全面的瞭解。 Context 架構應該設計到什麼程度? Problem 軟體的架構都是非常的複雜的,帶有大量的文件和圖表。開發人員花在理解架構本身上的時間甚至超出了實現架構的時間。在前面的文章中,我們提到了一些反物件牙塔式架構的一個原因,而其中的一個原因就是象牙塔式架構的設計者往往在設計時參雜進過多的自身經驗,而不是嚴格的按照需求來進行設計。在軟體開發領域,最為常見的設計就是"Code and Fix"方式的設計,設計隨著軟體開發過程而增長。或者,我們可以認為這種方式根本就不能算是設計,它抱著一種船到橋頭自然直的態度,可是在設計不斷改動之後,程式碼變得臃腫且難以理解,到處充滿著重複的程式碼。這樣的情形下,架構的設計也就無從談起,軟體就像是在風雨中的破屋,瀕臨倒塌。針對於這種情形,新的設計方式又出現了,Martin Fowler稱這種方式為"Planned Design"。和建築的設計類似,它強調在編碼之前進行嚴格的設計。這也就是我們在團隊設計中談到的架構設計師的典型做法。設計師們通常不會去程式設計,理由是在土木工程中,你不可能看到一位設計師還要砌磚頭。 "Planned Design"較之"Code and Fix"進步了許多,但是還是會存在很多問題。除了在團隊設計中我們談的問題之外,需求變更將會導致更大的麻煩。因此,我們理所當然的想到進行"彈性設計":彈性的設計能夠滿足需求的變更。而彈性的設計所付出的代價就是複雜的設計。題外話:這裡我們談論"Planned Design"引出的一些問題,並沒有任何排斥這種方式的意思。"Planned Design"還是有很多可取之處的,但也有很多需要改進的地方。事實上,本文中我們討論的架構設計方式,本質上也是屬於"Planned Design"方式。和"Planned Design"相對應的方式是XP所主張的"Evolutionary Design"方式,但是這種方式還有待於實踐的檢驗,並不能簡單的說他就一定要比"Planned Design"先進或落後。但可以肯定的一點是:"Evolutionary Design"方式中有很多的思想和技巧是值得"Planned Design"借鑑的。 Solution XP中有兩個非常響亮的口號:"Do The Simplest Thing that Could Possibly Work"和"You Aren't Going to Need It"(通常稱之為YAGNI)。他們的核心思想就是不要為了考慮將來,把目前並不需要的功能加到軟體中來。粗看之下,會有很多開發人員認為這是不切實際的口號。我能理解這種想法,其實,在我熱衷於模式、可重用元件技術的時候,我對XP提倡的簡單的口號嗤之以鼻。但在實際中,我的一些軟體因為複雜設計導致開發成本上升的時候,我重新思考這個問題,發現簡單的設計是有道理的。降低開發的成本不論是模式,可重用元件,或是框架技術,目的都是為了降低開發的成本。但是他們的方式是先進行大量的投入,然後再節省後續的開發成本。因此,架構設計方面的很多思路都是圍繞著這種想法展開的,這可能也是導致開發人員普遍認為架構設計高不可攀的原因。 XP的方式恰恰相反,在處理第一個問題的時候,不必要也不可能就設計出具有彈性、近乎完美的架構來。這項工作應該是隨著開發的演進,慢慢成熟起來的。我不敢說這種方式肯定正確,但是如果我們把生物的結構視同為架構,這種方式不是很類似於自然界中生物的進化方式嗎?在一開始就製作出完美的架構的設想並沒有錯,關鍵是很難做到這一點。總是會有很多的問題是你在做設計時沒有考慮到的。這樣,當一開始花費大量精力設計出的"完美無缺"的架構必然會遇到意想不到的問題,這時候,複雜的架構反而會影響到設計的改進,導致開發成本的上升。這就好比如果方向錯了,交通工具再快,反而導致錯誤的擴大。Martin Fowler在他的論文中說,"Working on the wrong solution early is even more wasteful than working on the right solution early"(提前做一件錯事要比提前做一件對的事更浪費時間),相信也是這個道理。更有意思的是,通常我們更有可能做錯。在我們進行架構設計的時候,我們不可能完全取得詳細的需求。事實上,就算你已經取得了完整的需求,也有可能發生變化。這種情況下做出的架構設計是不可能不出錯的。這樣,浪費大量的時間在初始階段設計不可能達到的"完美架構",倒不如把時間花在後續的改進上。提升溝通的效率我們在團隊設計中已經談過了團隊設計的目標之一就是為了降低溝通的成本,以期讓所有人都能夠理解架構。但是如果架構如果過於複雜,將會重新導致溝通成本的上升,而且,這個成本並不會隨著專案進行而降低,反而會因為上面我們提到的遇到新的問題導致溝通成本的持續上升。簡單的架構設計可以加快開發團隊理解架構的速度。我們可以透過兩種方式來理解簡單的含義。首先,簡單意味著問題的解不會非常的複雜,架構是解決需求的關鍵,無論需求再怎麼複雜多變,總是可以找出簡單穩定的部分,我們可以把這個簡單穩定的部分做為基礎,再根據需要進行改進擴充套件,以解決複雜的問題。在示例中,我們提到了measurement pattern,它就是按照這種想法來進行設計的。其次,簡單性還體現在表示的簡單上。一份5頁的文件就能夠表達清楚的架構設計為什麼要花費50頁呢?同樣的道理,能夠用一副簡單的圖形就能夠表示的架構設計也沒有必要使用文件。畢竟,面對面的溝通才是最有效率的溝通,文件不論如何的複雜,都不能被完全理解,而且,複雜的文件,維護起來也需要花費大量的時間。只有在兩種情況下,我們提倡使用複雜的文件:一是開發團隊沒有辦法做到面對面溝通;二是開發成果要作為團隊的知識積累起來,為下一次開發所用。考慮未來我們之所以考慮未來,主要的原因就是需求的不穩定。因此,我們如果考慮未來可能發生的需求變化,就會不知覺的在架構設計中增加複雜的成分。這違背的簡單的精神。但是,如果你不考慮可能出現的情況,那些和目前設計格格不入的改變,將會導致大量的返工。還記得YAGNI嗎?原則上,我們仍然堅持不要在現有的系統中為將來可能的情況進行設計。但是,我們必須思考,必須要為將來可能出現的情況做一些準備。其實,軟體中了不起的介面的思想,不就是源於此嗎?因此,思考未來,但等到需要時再實現。變更案例有助於我們思考未來,變更案例就是你在將來可能要(或可能不要)滿足的,但現在不需要滿足的需求。當我們在做架構設計的時候,變更案例也將會成為設計的考慮因素之一,但它不可能成為進行決策的唯一考慮因素。很多的時候,我們沉迷於設計通用系統給我們帶來的挑戰之中,其實,我們所做的工作對使用者而言是毫無意義的。架構的穩定架構簡單化和架構的穩定性有什麼關係嗎?我們說,架構越簡單,其穩定性就越好。理由很簡單,1個擁有4個方法和3個屬性的類,和1個擁有20個方法和30屬性的類相比,哪一個更穩定?當然是前者。而架構最終都是要對映到程式碼級別上的,因此架構的簡單將會帶來架構的穩定。儘可能的讓你的類小一些,儘可能的讓你的方法短一些,儘可能的讓類之間的關係少一些。這並不是我的忠告,很多的設計類的文章都是這麼說的。在這個話題上,我們可以進一步的閱讀同類的文章(關於 refactoring 的思考)。辨正的簡單因此,對我們來說,簡單的意義就是不要把未來的、或不需要實現的功能加入到目前的軟體中,相應的架構設計也不需要考慮這些額外的需求,只要剛好能夠滿足當前的需求就好了。這就是簡單的定義。可是在現實之中,總是有這樣或者那樣的原因,使得設計趨向複雜。一般來說,如果一個設計對團隊而言是有價值的,那麼,付出一定的成本來研究、驗證、發展、文件化這個設計是有意義的。反之,如果一個設計沒有很大的價值或是發展它的成本超過了其能夠提供的價值,那就不需要去考慮這個設計。價值對不同的團隊來說具有不同的含義。有時候可能是時間,有時候可能是使用者價值,有時候可能是為了團隊的設計積累和程式碼重用,有時候是為了獲得經驗,有時候是為了研究出可重用的框架(FrameWork)。這些也可以稱為目的,因此,你在設計架構時,請注意先確定好你的目的,對實現目的有幫助的事情才考慮。 Scott W.Ambler在他的文章中提到一個他親身經歷的故事,在軟體開發的架構設計過程中,花了很多的時間來設計資料庫到業務邏輯的對映架構,雖然這是一件任何開發人員都樂意專研的事情(因為它很酷)。但他不得不承認,對使用者來說,這種設計先進的架構是沒有太大的意義的,因為使用者並不關心具體的技術。當看到這個故事的時候,我的觸動很大。一個開發人員總是熱衷於新奇的技術,但是如果這個新奇技術的成本由使用者來承擔,是不是合理呢?雖然新技術的採用能夠為使用者帶來效益,但是沒有人計算過效益背後的成本。就我開發過的專案而言,這個成本往往是大於效益的。這個問題可能並沒有確定的答案,只能是見仁見智了。 簡單並不等於實現簡單說到這裡,如果大家有一個誤解,認為一個簡單的架構也一定是容易設計的,那就錯了。簡單的架構並不等於實現起來也簡單。簡單的架構需要設計者花費大量的心血,也要求設計者對技術有很深的造詣。在我們正在進行的一個專案中,一開始設計的基礎架構在實現中被修改了幾次,但每修改一次,程式碼量都減少一分,程式碼的可讀性也就增強一分。從心理的角度上來說,對自己的架構進行不斷的修改,確實是需要一定的勇氣的。因為不論是設計還是程式碼,都是開發人員的心血。但跨出這一步是值得的。右側的例子討論了Java的IO設計,Java類庫的設計應該來說是非常優秀的,但是仍然避免不了重新的修改。實際上,在軟體開發領域,由於原先的設計失誤而導致後來設計過於複雜的情況比比皆是(例如的OLE)。同樣的,我們在設計軟體的時候,也需要對設計進行不斷的修改。能夠實現複雜功能,同時自身又簡單的設計並不是一件容易的事情。簡單設計需要什麼樣的設計師簡單的架構需要全面的設計師。什麼才是全面的設計師,我的定義是既能夠設計,又能夠編碼。我們在團隊設計模式中就已經談過象牙塔式架構和象牙塔式架構設計師。他們最容易犯的一個毛病就是設計和程式碼的脫離。從我們自己的經驗來看,即使在設計階段考慮的非常完美的架構,在編碼階段也會出現這樣或那樣的問題。從而導致架構實現變得複雜。最明顯的特徵就是在編碼時出現了有大量方法的類,或是方法很長的類。這表明架構和程式碼脫鉤了。在我們的開發過程中,不只一次出現這種現象,或者說,出現了壞味道(Bad Smell)。Refactoring的技巧也同樣有助於識別壞味道。一次的架構設計完成後,開發人員可以按照設計,快速的程式設計。可在一段時間之後,新的特色不斷的加入,我們發現程式碼開始混亂,程式碼量增大,可讀性下降,變得困難,程式碼不可控制的徵兆開始出現。我們就知道,架構的設計需要調整了。這屬於我們在後面所提到的Refactoring模式。而我們在這裡要說的是,如果架構的設計師不參與編碼,它是無法感受到壞味道的,因此也就不會主動的對設計進行改進。要解決這個問題,最好的辦法是讓設計師參與程式碼的編寫,尤其是重要架構的現實部分需要設計師的參與。如果設計師沒有辦法參與編碼,那就需要一種機制,能夠把程式碼反饋給設計師,讓他在適當的時候,重新考慮改進架構。一個可能的辦法是Code Review。讓設計師稽核程式碼,以確保編碼者真正瞭解了架構設計的意圖。例1.Java的IO系統從Java的IO系統設計中,我們可以感受到簡單設計的困難。 IO系統設計的困難性向來是公認的。Java的IO設計的一個目的就是使IO的使用簡單化。在Java的1.0中,Java的IO系統主要是把IO系統分為輸入輸出兩個大部分,並分別定義了抽象類InputStream和OutputStream。從這兩個的抽象類出發,實現了一系列不同功能的輸入輸出類,同時,Java的IO系統還在輸入輸出中實現了FilterInputStream和FilterOutputStream的抽象類以及相關的一系列實現,從而把不同的功能的輸入輸出函式連線在一起,實現複雜的功能。這個實現其實是Decorator模式(由於沒有看過原始碼和相關的資料,這裡僅僅是根據功能和使用技巧推測,如果大家有不同的意見,歡迎來信討論)。因此,我們可以把多個物件疊加在一起,提供複雜的功能: DataInpuStream in = new DataInputStream( new BufferedInputStream( new FileInputStream("test.txt"); 上面的程式碼使用了兩個FilterInputStream:DataInpuStream和BufferedInputStream,以實現讀資料和緩衝的功能,同時使用了一個InputStream:FileInputStream,從中讀取流資料。雖然使用起來不是很方便,但是應該還是非常清晰的設計。令設計混亂的是既不屬於InputStream,也不屬於OutputStream的類,例如RanAccesile,這正表明,由於功能的複雜化,使得原先基於輸入輸出分類的設計變得混亂,根據我們的經驗,我們說設計需要Refactoring了。因此,在Java1.1中,IO系統被重新設計,採用了Reader和Writer位基礎的設計,並增加了新的特性。但是目前的設計似乎更加混亂了,因為我們需要同時使用1.0和1.1兩種不同的IO設計。 例2. measurement pattern 在分析模式一書中有一個measurement pattern(測量模式),原來它是為了要解決現實中各種各樣紛繁複雜的可測量的屬性。例如,一個醫療系統中,可能會有身高多高,體重多種,血壓多少等上千種可測量的屬性。如果分別表示它們,必然導致系統複雜性的上升。因此measurement pattern就從這些屬性的可測量的共性出發,研究新的解決方法,提出了measurement pattern的想法:如圖所示,把可測量的屬性(Measurement)做為Phenomenon Type的例項,此外,每一個的Person可以擁有多個的Measurement,同時,Measurement還對應處理的屬性,例如圖中的Quantity,就表示了Measurement的數量和單位。比如,一個人的體重是65公斤,那麼,Phenomenon Type就是體重,Quantity的amount是65,units是公斤。圖 5.牋 measurement pattern 的類圖這其實是一個很簡單的設計,但它清楚的表示了屬性之間的關係,簡化了數千種的屬性帶來的複雜性。此外,我們進一步思考,就會發現,這種架構只是針對目前出現屬性眾多的問題的基本解決方法,它還可以根據具體的需要進行擴充套件,例如,實現動態新增單位,或實現不同單位的轉化等問題。因此,我們這裡展示的其實是一種思考的方法,假想一下,當你在面對一個複雜的醫療系統時,大量的屬性和不同的處理方式,你是不是可以從這樣複雜的需求中找出簡單的部分來呢?在我們架構設計的第一篇中,我們談到架構設計的本質在於抽象,這裡例子就是最典型的一個例子,在我們傳統的想法中,我們都會把身高、體重等概念做為屬性或是類,但是為了滿足這裡的需求,我們對這些具體的概念做一個抽象,提出可測量類別的概念,並把它設計為類(Phenomenon Type),而把具體的概念做為例項。這種抽象的思想在軟體設計中無處不在,例如元類的概念。更深入的理解下一章中我們將會討論迭代設計,其中還會涉及到簡單設計的相關知識。建議可以將兩章的內容結合起來看。 迭代是一種軟體開發的生命週期模型,在設計中應用迭代設計,我們可以得到很多的好處。 Context 在軟體生命週期中,我們如何對待架構設計的發展? Problem 架構設計往往發生在細節需求尚未完成的時候進行的。因此,隨著專案的進行,需求還可能細化,可能變更。原先的架構肯定會有不足或錯誤的地方。那麼,我們應該如何對待原先的設計呢?我們在簡單設計模式中簡單提到了"Planned Design"和"Evolutionary Design"的區別。XP社團的人們推崇使用"Evolutionary Design"的方式,在外人看來,似乎擁護者們從來不需要架構的設計,他們採用的方式是一開始就進入程式碼的編寫,然後用Refactoring來改進程式碼的質量,解決未經設計導致的程式碼質量低下的功能。從一定程度上來說,這個觀點並沒有錯,它強調了程式碼對軟體的重要性,並透過一些技巧(如Refactoring)來解決缺乏設計的問題。但我並不認同"Evolutionary Design"的方式,在我看來,一定程度上的"Planned Design"是必須的,至少在中國的軟體行業中,"Planned Design"還沒有成為主要的設計方向。借用一句明言,"凡事預則立,不預則廢",在軟體設計初期,投入精力進行架構的設計是很有必要的,這個架構是你在後續的設計、編碼過程中依賴的基礎。但是,一開始我們提到的設計改進的問題依然存在,我們如何解決它呢?在簡單設計模式中,我們提到了設計改進的必要性,但是,如果沒有一種方法去控制設計的改進的話,那麼設計改進本身就是一場噩夢。因此,何時改進,怎麼改進, 如何控制,這都是我們需要面對的問題。 Solution 為了實現不斷的改進,我們將在開發流程中引入迭代的概念。迭代的概念在我的另一篇文章--《需求的實踐》中已經提到,這裡我們假設讀者已經有了基本的迭代的概念。軟體編碼之前的工作大致可以分為這樣一個工作流程:上圖中的流程隱含著一個資訊的損失的過程。來自於使用者的需求經過整理之後,開發人員就會從中去掉一些資訊,同樣的事情發生在後面的過程中,資訊丟失或變形的情況不斷的發生。這裡發生了什麼問題?應該說,需求資訊的失真是非常普遍的,我們缺少的是一種有效的辦法來抑止失真,換句話說,就是缺少反饋。如果把眼睛蒙上,那我們肯定沒有辦法走出一條很長的直線。我們走路的時候都是針對目標不斷的調整自己的方向的。同樣的,漫長的軟體開發過程如果沒有一種反饋機制來調整方向,那最後的軟體真是難以想象。所以我們引入了迭代週期。初始設計和迭代設計在團隊設計中,我們一直在強調,設計組最開始得到的設計一定只是一個原始架構,然後把這個原始架構傳播到每一位開發者的手中,從而在開發團隊中形成共同的願景。(願景(Vision):源自於管理學,表示未來的願望和景象。這裡借用來表示軟體在開發人員心中的樣子。在後面的文章中我們會有一個章節專門的討論架構願景。)迭代(Iterate)設計,或者我們稱之為增量(Incremental)設計的思想和XP提倡的Evolutionary Design有異曲同工之妙。我們可以從XP、Crystal、RUP、ClearRoom等方法學中對比、體會迭代設計的精妙之處:每一次的迭代都是在上一次迭代的基礎上進行的,迭代將致力於重用、修改、增強目前的架構,以使架構越來越強壯。在軟體生命週期的最後,我們除了得到軟體,還得到了一個非常穩定的架構。對於一個軟體組織來說,這個架構很有可能就是下一個軟體的投入或參考。我們可以把早期的原始架構當作第一次迭代前的早期投入,也可以把它做為第一次迭代的重點,這些都是無所謂的。關鍵在於,原始架構對於後續的架構設計而言是非常重要的,我們討論過架構是來源於需求的,但是原始架構應該來源於那些比較穩定的需求。 TIP:現實中迭代設計退化為"Code and Fix"的設計的情況屢見不鮮("Code and Fix"參見簡單設計)。從表面上看,兩者的做法並沒有太大的差別,都是針對原有的設計進行改進。但是,二者效果的差別是明顯的:"Code and Fix"是混沌的,毫無方向感可言,每一次的改進只是給原先就已搖搖欲墜的積木上再加一塊積木而已。而迭代設計的每一次改進都朝著一個穩定的目標在前進,他給開發人員帶來信心,而不是打擊。在過程上,我們說迭代設計是在控制之下的。從實踐的經驗中,我們發現,把原該在目前就該解決的問題退後是造成這一問題的主要原因之一。因此,請嚴格的對待每一次的迭代,確保計劃已經完成、確保軟體的質量、確保使用者的需求得到滿足,這樣才是正統的迭代之路。 單次的迭代我們說,每一次的迭代其實是一個完整的小過程。也就是說,它同樣要經歷文章中討論的這些過程模式。只不過,這些模式的工作量都不大,你甚至可以在很短的時間內做完所有的事情。因此,我們好像又回到了文章的開頭,重新討論架構設計的過程。單次迭代最令我們興奮的就是我們總是可以得到一個在當前迭代中相當穩定的結果,而不像普通的架構設計那樣,我們深怕架構會出現問題,但又不得不依賴這個架構。從我們的心理上來分析,我們是在持續的建設架構中,我們不需要回避需求的變更,因為我們相信,在需求相對應的迭代中,我們會繼續對架構進行改進。大家不要認為這種心理的改變是無關緊要的,我起初並沒有意識到這個問題,但是我很快發現新的架構設計過程仍然籠罩在原先的懼怕改變的陰影之下的時候,迭代設計很容易就退化為"Code and Fix"的情形。開發人員難以接受新方法的主要原因還是在心理上。因此,我不得不花了很多的時間來和開發人員進行溝通,這就是我現實的經驗。迭代的交錯基於我們對運籌學的一點經驗,迭代設計之間肯定不是線性的關係。這樣說的一個原因架構設計和後續的工作間還是時間差的。因此,我們不會傻到把時間浪費在等待其它工作上。一般而言,當下一次迭代的需求開始之後,詳細需求開始之前,我們就已經可以開始下一次迭代的架構設計了。各次迭代之間的時間距離要視專案的具體情況而定。比如,人員比較緊張的專案中,主要的架構設計人員可能也要擔任編碼人員的角色,下一次迭代的架構設計就可能要等到編碼工作的高峰期過了之後。可是,多次的交錯迭代就可能產生版本的問題。比如,本次的迭代的編碼中發現了架構的一個問題,反饋給架構設計組,但是架構設計組已經根據偽修改的本次迭代的架構開始了下一次迭代的架構設計,這時候就會出現不同的設計之間的衝突問題。這種情況當然可以透過加強對設計模型的管理和引入版本控制機制來解決,但肯定會隨之帶來管理成本上升的問題,而這是不符合敏捷的思想的。這時候,團隊設計就體現了他的威力了,這也是我們在團隊設計中沒有提到的一個原因。團隊設計透過完全的溝通,可以解決架構設計中存在衝突的問題。 迭代頻率 XP提倡迭代週期越短越好(XP建議為一到兩週),這是個不錯的提議。在這麼短的一個迭代週期內,我們花在架構設計上的時間可能就只有一兩個小時到半天的時間。這時候,會有一個很有意思的現象,你很難去區分架構設計和設計的概念了。因為在這麼短的一個週期之內,完成的需求數量是很少的,可能就只有一兩個用例或使用者素材。因此,這幾項需求的設計是不是屬於架構設計呢?如果是的話,由於開發過程是由多次的迭代組成的,那麼開發過程中的設計不都屬於架構設計了嗎?我們說,架構是一個相對的概念,是針對範圍而言的,在傳統的瀑布模型中,我們可以很容易的區分出架構設計和普通設計,如果我們把一次迭代看作是一個單獨的生命週期,那麼,普通的設計在這樣一個範圍之內也就是架構設計,他們並沒有什麼兩樣。但是,迭代週期中的架構設計是要遵循一定的原則的,這我們在下面還會提到。我們希望迭代頻率越快越好,但是這還要根據現實的情況而定。比如資料倉儲專案,在專案的初期階段,我們不得不花費大量的時間來進行資料建模的工作,這其實也是一項專門針對資料的架構設計,建立後設資料,制定維,整理資料,這樣子的過程很難分為多次的迭代週期來實現。如何確定軟體的迭代週期可以說,如果一支開發團隊沒有相關迭代的概念,那麼這支團隊要立刻實現時隔兩週迭代週期是非常困難的,,同時也是毫無意義的。就像我們在上面討論的,影響迭代週期的因素很多,以至於我們那無法對迭代週期進行量化的定義。因此我們只能從定性的角度分析迭代週期的發展。另一個瞭解迭代的方法是閱讀XP的相關資料,我認為XP中關於迭代週期的使用是很不錯的一種方法,只是他強調的如此短的迭代週期對於很多的軟體團隊而言都是難以實現的。迭代週期的引入一定是一個從粗糙到精確的過程。迭代的本質其實是短週期的計劃,因此這也是迭代週期越短對我們越有好處的一大原因,因為時間縮短了,計劃的可預測性就增強了。我們知道,計劃的制定是依賴於已往的經驗,如果原先我們沒有制定計劃或細節計劃的經驗,那麼我們的計劃就一定是非常粗糙,最後的誤差也一定很大。但是這沒有關係,每一次的計劃都會對下一次的計劃產生正面的影響,等到經驗足夠的時候,計劃將會非常的精確,最後的誤差也會很小。迭代週期的確定需要依賴於單位工作量。單位工作量指的是一定時間內你可以量化的最小的績效。最簡單的單位工作量是每位程式設計師一天的編碼行數。可惜顯示往往比較殘酷,團隊中不但有程式設計師的角色,還有設計師、測試人員、文件製作人員等角色的存在,單純的編碼行數是不能夠作為唯一的統計依據的。同樣,只強調編碼行數,也會導致其它的問題,例如程式碼質量。為了保證統計的合理性,比較好的做法是一個團隊實現某個功能所花費的天數作為單位工作量。這裡討論的內容實際是軟體測量技術,如果有機會的話,再和大家探討這個問題。迭代週期和軟體架構的改進我們應用迭代方法的最大的目的就是為了穩步的改進軟體架構。因此,我們需要了解架構是如何在軟體開發的過程中不斷演進的。在後面的文章中,我們會談到用Refactoring的方法來改進軟體架構,但是Refactoring的定義中強調,Refactoring必須在不修改程式碼的外部功能的情況下進行。對於架構來說,我們可以近乎等價的認為就是在外部介面不變的情況下對架構進行改進。而在實際的開發中,除非非常有經驗,否則在軟體開發全過程中保持所有的軟體介面不變是一件非常困難的事情。因此,我們這裡談的架構的改進雖然和Refactoring有類似之處,但還是有區別的。軟體架構的改進在軟體開發過程會經歷一個振盪期,這個振盪期可能橫跨了數個迭代週期,其間架構的設計將會經歷劇烈的變化,但最後一定會取向於平穩。(如果專案後期沒有出現設計平穩化的情況,那麼很不幸,你的專案註定要失敗了,要麼是時間的問題,要麼就是需求的問題)。關鍵的問題在於,我們有沒有勇氣,在架構需要改變的時候就毅然做出變化,而不是眼睜睜的看著問題變得越來越嚴重。最後的例子中,我們討論三個迭代週期,假設我們在第二個週期的時候拒絕對架構進行改變,那麼第三個週期一定是有如噩夢一般。變化,才有可能成功。我們知道變化的重要性,但沒有辦法知道變化的確切時間。不過我們可以從開發過程中嗅到架構需要變化的氣味:當程式中重複的程式碼逐漸變多的時候,當某些類變得格外的臃腫的時候,當編碼人員的編碼速度開始下降的時候,當需求出現大量的變動的時候。例子:從這一週開始,我和我的小組將要負責對軟體專案中的表示層的設計。在這個迭代週期中,我們的任務是要為客戶端提供6到10個的檢視。由於檢視並不很多,表示層的架構設計非常的簡單:準確的說,這裡談不上設計,只是簡單讓客戶端訪問不同的檢視而已。當然,在設計的示意圖中,我們並沒有必要畫出所有的檢視來,只要能夠表達客戶端和檢視的關聯性就可以了。(架構設計需要和具體的實現繫結,但是在這個例子中,為了著重體現設計的演進,因此把不必要的資訊都刪掉。在實際的設計中,檢視可能是JSP頁面,也可能是一個視窗。)第一個迭代周的任務很快的完成了,小組負責的表示層模組也很順利的和其它小組完成了對接,一個簡陋但能夠運轉的小系統順利的釋出。客戶觀看了這個系統的演示,對系統提出了修改和補充。第二個迭代週中,模組要處理的檢視增加到了30個,檢視之間存在相同的部分,並且,負責資料層的小組對我們說,由於客戶需求的改進,同一個檢視中將會出現不同的資料來源。由於我們的檢視中直接使用了資料層小組提供給我們的資料來源的函式,這意味著我們的設計需要進行較大的調整。考慮到系統的檢視的量大大的增加,我們有必要對檢視進行集中的管理。前端控制器(Front Control)模式將會是一個不錯的技巧。對於檢視之間的普遍的重複部分,可以將檢視劃分為不同的子檢視,再把子檢視組合為各種各樣的檢視。這樣我們就可以使用組合(Composite)模式:客戶的請求集中提交給控制器,控制器接受到客戶的請求之後,根據一定的規則,來提供不同的檢視來反饋給客戶。控制器是一個具有擴充套件能力的設計,目前的檢視數量並不多,因此仍然可以使用控制器來直接分配檢視。如果檢視的處理規則比較複雜,我們還可以使用建立工廠(Create Factory)模式來專門處理生成檢視的問題。對於檢視來說,使用組合模式,把多個不同資料來源的檢視組合為複雜的檢視。例如,一個JSP的頁面中,可能需要分為頭頁面和尾頁面。專案進入第三個迭代週期之後,表示層的需求進一步複雜化。我們需要處理資訊、需要處理資料的合法性判斷、還需要面對更多的檢視帶來的複雜程度上升的問題。表示層的許可權處理比較簡單,我們可以從前端控制器中增加許可權控制的模組。同時,為了解決合法性判斷問題,我們又增加了一個資料過濾鏈模組,來完成資料的合法性判斷和轉換的工作。為了

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-958471/,如需轉載,請註明出處,否則將追究法律責任。

相關文章