迭代是一種軟件開發的生命周期模型,在設計中應用迭代設計,我們可以得到很多的好處。
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的頁面中,可能需要分為頭頁面和尾頁面。
項目進入第三個迭代周期之后,表示層的需求進一步復雜化。我們需要處理權限信息、需要處理數據的合法性判斷、還需要面對更多的視圖帶來的復雜程度上升的問題。
表示層的權限處理比較簡單,我們可以從前端控制器中增加權限控制的模塊。同時,為了解決合法性判斷問題,我們又增加了一個數據過濾鏈模塊,來完成數據的合法性判斷和轉換的工作。為了不使得控制器部分的功能過于復雜,我們把原先屬于控制器的視圖分發功能轉移到新的分發器模塊,而控制器專門負責用戶請求、視圖的控制。
我們來回顧這個例子,從迭代周期1中的需求最為簡單,其實,現實中的項目剛開始的需求雖然未必會像例子中的那么簡單,但一定不會過于復雜,因此迭代周期1的設計也非常的簡單。到了迭代周期2的時候,需求開始變得復雜,按照原先的架構繼續設計的話,必然會導致很多的問題,因此對架構進行改進是必要的。我們看到,新的設計能夠滿足新的需求。同樣的,迭代周期3的需求更加的復雜,因此設計也隨之演進。這就是我們在文章的開始提到的"Evolutionary Design"的演進的思想。
(待續)
作者簡介:
林星,辰訊軟件工作室項目管理組資深項目經理,有多年項目實施經驗。辰訊軟件工作室致力于先進軟件思想、軟件技術的應用,主要的研究方向在于軟件過程思想、Linux集群技術、OO技術和軟件工廠模式。您可以通過電子郵件 iamlinx@21cn.com 和他聯系。
文章來源于領測軟件測試網 http://www.kjueaiud.com/