本文謝絕無授權轉載。
在目前大部分的軟件開發組織中,敏捷開發已經成為毋庸置疑的標配。隨著數位技術大神和布道師的宣揚和數量龐大的敏捷教練的身體力行式推廣,商業環境和客戶需求變更速度的日益加快,采用端到端交付周期更短的敏捷開發過程基本已經成為項目成功的必要條件。
軟件設計的剛需被敏捷了嗎?
工作流程的變更以及開發節奏的加快并不能繞開一個很核心的問題:寫出容易維護方便擴展的代碼的復雜程度本質上沒有改變;軟件的維護周期越長,迭代的版本越多,這個基本問題就越突出。要想順利解決這一問題,只能依賴于系統具有相對良好的設計,使得添加新的功能不會輕易破壞原有的結構,出現問題的時候,不需要大范圍地對系統做出變更。
傳統的瀑布式方法希望通過借鑒成熟的建筑行業的做法,采用預先大規模的架構設計,對系統做好明確的分割;繼而進行不同層次的設計,直到所有可以預見到的需求都得以滿足,然后才開始進行的代碼的編寫和構建。這種方法生產出來的軟件交付工期很長,適應性很差,除了少數特殊行業之外基本已經被市場所淘汰。
敏捷宣言提出了一些基本的原則來指導我們怎樣用相對更"敏捷"的方式開發和交付我們的軟件;通過多個不同的迭代,增量式的構建和持續交付系統來降低風險。然而軟件本身的復雜性導致我們不能將客戶的需求一對一的翻譯成代碼,像搭積木一樣構建出來一個可以輕易維護的系統。因為新加入的需求很可能導致原有的代碼結構無法適應新的需求;某些為了盡快完成需求而做出的關鍵的假設可能必須被打破導致添加新的需求會破環大量已有的功能。如何做出恰如其分的軟件設計,既能滿足現有的短期需求,又能平衡潛在的變更。
各種不同的敏捷實踐方法論對如何管理用戶的需求,如何增強不同角色的溝通,如何實施日常的開發和測試活動,如何驗證需求保證已經交付的承諾不被變更所破壞,如何規劃和平衡資源和進度等復雜問題都給出了豐富的可選實踐供項目管理人員裁剪;對于如何做軟件設計以及做多少軟件設計并沒有很詳盡的描述。就連基本的是否需要軟件設計,以及需要多少軟件設計,怎樣算作過度設計都語焉不詳。
真的需要軟件設計嗎?
大部分情況下,對于這個基本的問題我想答案應該是肯定的,除非你是在做一個很小的個人項目。如果需要牽扯到多個人一塊合作并且最終的產品需要維護比較長時間,那么起碼某種程度的軟件設計應該是不可或缺的。畢竟軟件開發活動本身也是圍繞著人展開的,既然需要多個不同知識背景,不同技能,不同角色的人一起來協作交付功能復雜多變的軟件,那么必然需要一些設計保證參與其中的人有一致的理解。
敏捷運動的早期一個常見的誤解就是,敏捷軟件開發不需要軟件設計,不需要軟件架構,只需要采用極限編程,將人們聚集一個公共空間里,直接動手寫代碼就行了;復雜的設計文檔都是浪費,都是可以避免掉的,代碼就是最好的設計文檔。這樣的過程可能適合于幾個能力超強的程序員聚在一起做的臨時小項目,放到更廣泛的商業環境則難以持續下去。人員的流動,特殊需求的變更,性能問題的修補會使一個一開始看起來極其簡單的幾個源代碼文件組成的小項目演進成難以維護的“龐然大物”。
如果系統沒有明確的分工和邊界,沒有相對清晰的職責分工和交互限制,軟件的結構很容易陷入“大泥球”結構而不可維護,試想如果代碼里的每一個包或者類都有可能和另外其它的任意一個類有交互關系,即使是一個絕對代碼行數很小的項目也會變得無法繼續添加新的功能。
哪些東西應該包含在軟件設計中?
所謂的設計其實可以理解為關于如何組織軟件各個部分(特性和行為分割)的一些決策,以及做出相應決策的一些原因。
敏捷場景下,重構的重要性以及早已經深入人心,因而容易經由重構來去掉“壞味”的部分就不宜放在設計中。因為一般為了重用的目的都會將設計決策寫下來供后續使用;如此一來必然產生一些維護成本;而維護設計文檔的開銷一般比代碼要大很多。因此容易通過重構而優化的部分,放在專門的軟件設計中顯得有些得不償失了。畢竟敏捷軟件開發的基本思路就是消除浪費,使得投入產出比最大化。
某些跟具體實現技術相關而和核心業務需求關系比較遠的決策,大部分也不適宜包含在軟件設計中。譬如期望某部分關鍵數據需要做持久化以保證系統異常重啟的時候依然可以恢復。對于業務需求而言,這塊數據需要持久化是重要的,但是如何做持久化,又可能是易變的,譬如今天是考慮用文件來做持久化就可以了,將來可能發現不夠必須用關系數據庫,或者甚至關系數據庫可能也不是一個合適的選擇,得要用鍵值對數據庫。識別到可能變化的部分,并將不變的部分抽象出來,放入設計中可能就足夠了。這樣技能照顧到當前的需求,又能滿足將來擴展的需要。至于具體是怎樣實現的,看代碼就足夠了。
需求的概念抽象化,和軟件的靜態模型可以作為設計的中心之一,必須詳細考慮并歸檔維護。之所以要對需求進行抽象化處理,是因為用戶的期望可能是模糊不清的,甚至是“朝令夕改”的。敏捷方法強調持續交付就是為了使用戶早期得到反饋,及時修正他們的需求,更好的管理客戶的期望,避免開發出不符合客戶真正預期的產品,浪費開發資源不說也浪費了客戶的投資。軟件的靜態模型是關于大的軟件職責的拆分和交互邊界,這一部分不僅是當前進一步開發的基本依據,日后萬一需要重構也是很重要的參考,值得花力氣仔細討論達成一致,減少日后維護成本。
軟件的部署和核心模塊的交互在有這方面的變更的時候(新加入模塊或者服務等)也需要仔細考慮并作為軟件設計的關鍵活動。模塊的邊界是粗粒度的系統耦合的地方,一些關鍵的交互流程也適宜詳細討論并放在軟件設計文檔中。
系統核心的模塊/類以及之間的交互,如果有發生變更,也需要第一時間考慮清楚并放置在設計過程中產生合適的產出,便于溝通和交流。如果模塊的粒度足夠大(譬如估計有很多的代碼),那么哪些部分是對外交互的接口也應該提早考慮清楚,并提取出來以便后續寫代碼以及代碼評審的時候對照,確保設計被正確遵守。
什么時候應該停止繼續設計?
敏捷語義下,任何的浪費都是可恥的,代價巨大的設計工作自然也不例外。知道何時還需要仔細討論搞清楚,何時應該停止變得尤其困難,甚至需要一些接近于藝術化的方法,需要經過大量的實踐經驗累積和反思才能做到不偏不倚。
如果發現所討論的問題可能代碼實現很容易就能完成,如果考慮不完備,那么修改代碼的代價非常小,那么就可以立即停止了;因為設計的目的是為了更好的寫代碼,更好的維護既有的代碼。因此只有重構代碼的代價遠遠大于預先仔細設計付出的代價是,才應該花費力氣去做這些燒腦的工作。發現代碼實現已經很容易沒什么問題的時候,就放手去寫代碼或者重構代碼吧。這種情況往往發生在我們想去“設計”一些內部實現細節,而這些細節對模塊的邊界以及待修改模塊和核心部分耦合很小的情況。
如果是在構建一個新的模塊,而這個模塊和已有的系統有形形色色的復雜聯系(耦合),那么如果這個模塊和已有的系統的各個部分的交互已經比較清楚,而且其內部實現估計工作量也很小的時候,那么就可以放心將剩下的工作交給聰明的程序員去繼續了。將一些細微的工作也放入設計中,只會使設計文檔變得龐大而又難以維護。畢竟可工作的代碼比完美的文檔更重要,雖然后者也很有價值。
如果對于是否應該繼續設計有分歧,可以和其它準備實現的程序員坐在一起討論(其實任何時候都應該如此,如果團隊規模比較小而且時間允許),看將要寫代碼的程序員是否覺得足夠清楚,返工的風險是否足夠得小。如果對于一些核心的模塊或者類的職責還有不同的認識,或者程序員不知道某些改動是應該新創建一個子包,還是應該在已有的摸個包中修改來實現,那么很可能有些關鍵的部分沒有設計清楚。
決定何時應該適可而止也和你的程序員團隊的實際水平和能力密切相關。一群天才程序員可能需要極少的設計來達成基本的共識就可以產出高質量易維護的代碼,而水平平庸的程序員團隊則需要更多設計上的預先討論溝通以達成基本共識,減少返工。
工具
軟件工程的一個關鍵要素就是工具。軟件設計自然也離不開合適的工具,尤其是軟件設計又是對需求進行抽象的結晶;選擇合適的工具可以增進協作和溝通,使得設計輸出是有實際指導作用的而不僅僅是純粹的文檔工作。
輕量級的文檔工具往往使維護和修改變得更加容易,因為設計輸出本身也是一個迭代的過程;便于多人評審和協作顯得尤其重要。目前主流的方式基本都是基于Markdown和Plantuml的;前者可以用來存放文本,后者則可以用文本的格式來描述UML圖。