軟體設計的複雜度

Horky發表於2015-04-30

什麼是軟體設計的複雜度

軟體技術發展的使命之一就是控制複雜度(Complexity)。從高階語言的產生,到結構化程式設計,再到物件導向程式設計、元件化程式設計等等。關於複雜度的定義並不一致,想要詳細瞭解的可以讀讀The Many Faces of Complexity in Software Design.

英文中Complex和Complicated有著微妙的不同。但總結起來,軟體複雜度偏負面意義,包括兩個要點:
- 難以理解 (難以維護和擴充套件。)
- 無法預測行為

複雜度是隨著軟體規模不斷擴大而必然產生的。它本身又是一個相對的概念,同一個系統對於設計者、開發者,以及維護者而言,複雜度是不同的。不同時期,一個程式設計師所能掌握的複雜度也是不同的,這也是一個程式設計師不斷提升的目標。

既然業界已經對抗複雜度幾十年了,我們就來整理一下。

以分解降低複雜度

以分解的方式進行的設計,主要特點是:
- 分離職責(Seperation of Concerns,參考單一職責原則)
- 關注介面(定義互動)

這是最常使用的技術了。將一個大問題,不斷的拆解為各個小問題進行分析研究,然後再組合到一起。在西方稱為Divide and Conquer Principle (分而治之原則)。

在結構化程式設計的時代,提倡模組化(Modularization)。最早提出軟體複雜度的工程師提出了基於元件的軟體(Component Based Software)。不知道是不是從樂高積木上得到的啟發,將系統中拆分為不同的元件,各自實現,然後再組裝在一起。

在架構設計中,無論是C/S風格,分層,還是N-Tier,SOA,和前面元件式一樣,都是在進行分解,它們都更加強調組合互動。設計上,分分職責,定義好介面,就可以各自開發了。然後將互動限定於介面層,就能夠很好的控制整個系統的複雜度。

比如應用層使用一個語音庫(Speech Library,一個以庫的形式的模組化應用), 根本不用關心其內部實現,只要瞭解如何使用它的API就可以了。

改善低賴降低複雜度

改進依賴關係的要點:
- 無環形依賴
- 穩定依賴原則(SDP)

分解可以降低系統層級的複雜度,但還有一種複雜度無法解決,即依賴的問題。這在敏捷軟體開發:原則、模式與實踐中關於依賴性的討論很詳細。當參與者增加時,互動就會隨之變得複雜。而當前的軟體規模,系統中的各類SDK的API, Framework的API, 各種第三方庫越來越多,模組間的依賴就會越來越複雜。
cycle_dependencies
顯然系統中的模組或者元件太多了,需要進一步整理。但真正的問題在於出現了雙向和環形的依賴。比如上圖中負責計算的Computing模組也依賴到了UI模組,或許是因為UI層持有一個計算所需的關鍵引數。如果UI層變更,就可能會影響到Computing,出現無法預測的行為,給客戶以不穩定的印象。

所以模組間的依賴關係必須簡化,絕對不能出現環形的依賴。以Chromium為例,它對各個模組的依賴就有嚴格的定義,並且有DEPS在編譯期保證程式設計師不會犯錯。下圖是Chromium Component依賴關係的定義,其中Component內部目錄的依賴關係也有定義:
chromium_component

當底層模組需要依賴上層模組的實現時,就要通過依賴倒置(DIP)來處理。簡單而言就是由底層模組定義一個介面,要求上層模組實現並注入到底層模組。
DIP

使用抽象降低複雜度

人的學習過程最有效的一種方式就是歸類,其中運用的就是抽象思維。面對變幻無常的天氣,人類通過對雲的形狀進行抽象,就可以預測天氣變化。這裡有一個抽象建模的過程。

抽象並不是面嚮物件語言專屬,其實它和語言無關,本質上是一個思考的方式。它和分離的最大區別在於,抽象強調將細節隱藏,只關注核心的本質。而後者則重視於細節問題的分解和組合。

以求固定兩點的最快捷路線為例。從分離的角度來,可以分解為以下問題:

  1. 步行需要多少時間?
  2. 乘公共交通多少時間?
  3. 乘的士多少時間?
  4. 組合以上答案,再評估哪一個最快捷的方式。

而從抽象的角度來看,解決的思路會是這樣的:

遍歷所有可能的交通工具,取耗時最小的:
1. 步行
2. 乘公共交通
3. 乘的士

先給出一個抽象的解決思路,至於細節,則是進一步的實現。抽象最大的威力在於它比實現要穩定,也最能用於固化核心設計。在開發過程中,常常圍繞著各種細節討論,似乎抽象過於虛。但是如果沒有以抽象來建立系統的設計全景,有些討論將變得效率低下。

敏捷軟體開發:原則、模式與實踐中,Martin大叔簡單的用抽象類在總類個數中的佔比作為抽象性的度量,再結合穩定性的度量,用來評估設計。詳情可以參考元件設計原則之概念篇(三)

以通俗原則降低複雜度

設計和實現時引入不必要的抽象或分解,也是一種複雜度.考慮擴充套件性也是確定會發生的需求才要考慮進來,否則就是引入不必要的複雜性.這也是敏捷設計所倡導的.
一些約定俗成的命名,常常隱含著設計.比如Observer, Client, Adapter等等.我們要學習這些模式,也要準確加以命名.否則很容易造成理解上的問題.

小結

軟體設計是一個平衡的過程,軟體的複雜度決定著系統的可維護性、可擴充套件性和靈活性。我們再來回顧一下前人定義出軟體設計的三原則:模組化、抽象和資訊隱藏。McCabe也曾有論文專門討論將圈複雜度應用度量設計的複雜度,不過已經歷史久遠。現在來看以依賴關係來評估設計的複雜度會更為有效。有興趣可以瞭解一下CppDepend。另外Google的工程師則基於LLVM IR也實現了一個工具用於依賴關係分析(Generateing Precise Dependencies for Large Software)。

轉載請註明出處: http://blog.csdn.net/horkychen

相關文章