軟件技術(shù)發(fā)展的使命之1就是控制復(fù)雜度(Complexity)。從高級(jí)語(yǔ)言的產(chǎn)生,到結(jié)構(gòu)化編程,再到面向?qū)ο缶幊獭⒔M件化編程等等。關(guān)于復(fù)雜度的定義其實(shí)不1致,想要詳細(xì)了解的可以讀讀The Many Faces of Complexity in Software Design.
英文中Complex和Complicated有著奧妙的不同。但總結(jié)起來(lái),軟件復(fù)雜度偏負(fù)面意義,包括兩個(gè)要點(diǎn):
- 難以理解 (難以保護(hù)和擴(kuò)大。)
- 沒(méi)法預(yù)測(cè)行動(dòng)
復(fù)雜度是隨著軟件范圍不斷擴(kuò)大而必定產(chǎn)生的。它本身又是1個(gè)相對(duì)的概念,同1個(gè)系統(tǒng)對(duì)設(shè)計(jì)者、開(kāi)發(fā)者,和保護(hù)者而言,復(fù)雜度是不同的。不同時(shí)期,1個(gè)程序員所能掌握的復(fù)雜度也是不同的,這也是1個(gè)程序員不斷提升的目標(biāo)。
既然業(yè)界已對(duì)抗復(fù)雜度幾10年了,我們就來(lái)整理1下。
以分解的方式進(jìn)行的設(shè)計(jì),主要特點(diǎn)是:
- 分離職責(zé)(Seperation of Concerns,參考單1職責(zé)原則)
- 關(guān)注接口(定義交互)
這是最常使用的技術(shù)了。將1個(gè)大問(wèn)題,不斷的拆解為各個(gè)小問(wèn)題進(jìn)行分析研究,然后再組合到1起。在西方稱為Divide and Conquer Principle (分而治之原則)。
在結(jié)構(gòu)化編程的時(shí)期,提倡模塊化(Modularization)。最早提出軟件復(fù)雜度的工程師提出了基于組件的軟件(Component Based Software)。不知道是否是從樂(lè)高積木上得到的啟發(fā),將系統(tǒng)中拆分為不同的組件,各自實(shí)現(xiàn),然后再組裝在1起。
在架構(gòu)設(shè)計(jì)中,不管是C/S風(fēng)格,分層,還是N-Tier,SOA,和前面組件式1樣,都是在進(jìn)行分解,它們都更加強(qiáng)調(diào)組合交互。設(shè)計(jì)上,分分職責(zé),定義好接口,就能夠各自開(kāi)發(fā)了。然后將交互限定于接口層,就可以夠很好的控制全部系統(tǒng)的復(fù)雜度。
比如利用層使用1個(gè)語(yǔ)音庫(kù)(Speech Library,1個(gè)以庫(kù)的情勢(shì)的模塊化利用), 根本不用關(guān)心其內(nèi)部實(shí)現(xiàn),只要了解如何使用它的API就能夠了。
改進(jìn)依賴關(guān)系的要點(diǎn):
- 無(wú)環(huán)形依賴
- 穩(wěn)定依賴原則(SDP)
分解可以下降系統(tǒng)層級(jí)的復(fù)雜度,但還有1種復(fù)雜度沒(méi)法解決,即依賴的問(wèn)題。這在敏捷軟件開(kāi)發(fā):原則、模式與實(shí)踐中關(guān)于依賴性的討論很詳細(xì)。當(dāng)參與者增加時(shí),交互就會(huì)隨之變得復(fù)雜。而當(dāng)前的軟件范圍,系統(tǒng)中的各類SDK的API, Framework的API, 各種第3方庫(kù)愈來(lái)愈多,模塊間的依賴就會(huì)愈來(lái)愈復(fù)雜。
明顯系統(tǒng)中的模塊或組件太多了,需要進(jìn)1步整理。但真實(shí)的問(wèn)題在于出現(xiàn)了雙向和環(huán)形的依賴。比如上圖中負(fù)責(zé)計(jì)算的Computing模塊也依賴到了UI模塊,也許是由于UI層持有1個(gè)計(jì)算所需的關(guān)鍵參數(shù)。如果UI層變更,便可能會(huì)影響到Computing,出現(xiàn)沒(méi)法預(yù)測(cè)的行動(dòng),給客戶以不穩(wěn)定的印象。
所以模塊間的依賴關(guān)系必須簡(jiǎn)化,絕對(duì)不能出現(xiàn)環(huán)形的依賴。以Chromium為例,它對(duì)各個(gè)模塊的依賴就有嚴(yán)格的定義,并且有DEPS在編譯期保證程序員不會(huì)出錯(cuò)。下圖是Chromium Component依賴關(guān)系的定義,其中Component內(nèi)部目錄的依賴關(guān)系也有定義:
當(dāng)?shù)讓幽K需要依賴上層模塊的實(shí)現(xiàn)時(shí),就要通過(guò)依賴顛倒(DIP)來(lái)處理。簡(jiǎn)單而言就是由底層模塊定義1個(gè)接口,要求上層模塊實(shí)現(xiàn)并注入到底層模塊。
人的學(xué)習(xí)進(jìn)程最有效的1種方式就是歸類,其中應(yīng)用的就是抽象思惟。面對(duì)變幻無(wú)常的天氣,人類通過(guò)對(duì)云的形狀進(jìn)行抽象,就能夠預(yù)測(cè)天氣變化。這里有1個(gè)抽象建模的進(jìn)程。
抽象其實(shí)不是面向?qū)ο笳Z(yǔ)言專屬,其實(shí)它和語(yǔ)言無(wú)關(guān),本質(zhì)上是1個(gè)思考的方式。它和分離的最大區(qū)分在于,抽象強(qiáng)調(diào)將細(xì)節(jié)隱藏,只關(guān)注核心的本質(zhì)。而后者則重視于細(xì)節(jié)問(wèn)題的分解和組合。
以求固定兩點(diǎn)的最快捷線路為例。從分離的角度來(lái),可以分解為以下問(wèn)題:
- 步行需要多少時(shí)間?
- 乘公共交通多少時(shí)間?
- 乘的士多少時(shí)間?
- 組合以上答案,再評(píng)估哪個(gè)最快捷的方式。
而從抽象的角度來(lái)看,解決的思路會(huì)是這樣的:
遍歷所有可能的交通工具,取耗時(shí)最小的:
1. 步行
2. 乘公共交通
3. 乘的士
先給出1個(gè)抽象的解決思路,至于細(xì)節(jié),則是進(jìn)1步的實(shí)現(xiàn)。抽象最大的威力在于它比實(shí)現(xiàn)要穩(wěn)定,也最能用于固化核心設(shè)計(jì)。在開(kāi)發(fā)進(jìn)程中,常常圍繞著各種細(xì)節(jié)討論,仿佛抽象過(guò)于虛。但是如果沒(méi)有以抽象來(lái)建立系統(tǒng)的設(shè)計(jì)全景,有些討論將變得效力低下。
在敏捷軟件開(kāi)發(fā):原則、模式與實(shí)踐中,Martin大叔簡(jiǎn)單的用抽象類在總類個(gè)數(shù)中的占比作為抽象性的度量,再結(jié)合穩(wěn)定性的度量,用來(lái)評(píng)估設(shè)計(jì)。詳情可以參考組件設(shè)計(jì)原則之概念篇(3)。
設(shè)計(jì)和實(shí)現(xiàn)時(shí)引入沒(méi)必要要的抽象或分解,也是1種復(fù)雜度.斟酌擴(kuò)大性也是肯定會(huì)產(chǎn)生的需求才要斟酌進(jìn)來(lái),否則就是引入沒(méi)必要要的復(fù)雜性.這也是敏捷設(shè)計(jì)所提倡的.
1些約定俗成的命名,常常隱含著設(shè)計(jì).比如Observer, Client, Adapter等等.我們要學(xué)習(xí)這些模式,也要準(zhǔn)確加以命名.否則很容易造成理解上的問(wèn)題.
軟件設(shè)計(jì)是1個(gè)平衡的進(jìn)程,軟件的復(fù)雜度決定著系統(tǒng)的可保護(hù)性、可擴(kuò)大性和靈活性。我們?cè)賮?lái)回顧1下前人定義出軟件設(shè)計(jì)的3原則:模塊化、抽象和信息隱藏。McCabe也曾有論文專門討論將圈復(fù)雜度利用度量設(shè)計(jì)的復(fù)雜度,不過(guò)已歷史久遠(yuǎn)。現(xiàn)在來(lái)看以依賴關(guān)系來(lái)評(píng)估設(shè)計(jì)的復(fù)雜度會(huì)更加有效。有興趣可以了解1下CppDepend。另外Google的工程師則基于LLVM IR也實(shí)現(xiàn)了1個(gè)工具用于依賴關(guān)系分析(Generateing Precise Dependencies for Large Software)。
轉(zhuǎn)載請(qǐng)注明出處: http://blog.csdn.net/horkychen