程式者的胡言亂語

pageicon 星期二 六月 24, 2008

漫談程式碼的相依性 - 2

高相依性的程式碼會成為軟體開發時的諸般障礙,例如:(1)對某程式模組所做的更動,會波及到許多的程式模組(2)因為高度的相依性,很難單獨對單一模組進行理解,因為特定的一項操作多半會牽扯進多個程式模組之間的相依互動,難以將此操作歸於單一程式模組的特性表徵(3)高相依性也使得想要重覆運用程式模組更為困難,因為引入某一模組也被迫引入更多該模組所相依的其餘模組,單一模組的獨立重覆運用性便大幅降低。

程式員是如何的造成程式模組間相依性高的局面呢?首先,抽象化的程度不夠,或根本不做抽象化,是一個重要的主因。「針對介面來撰寫程式,而不要針對實作(Program to interface, not an implementation)」這個原則相信讀者們肯定看過好多次了。這個原則代表的是一個實際的做法,也就是要程式員盡量透過介面進行存取及操控,而不要直接存取實際。但它背後的中心思想,其實是要程式員建立起有層次的抽象化層級。介面就是實作的抽象化,在實作層次上再建構出抽象層次,目的便在於希望客戶端的程式碼能夠透過抽象層次做為存取的門戶,而非直搗實作層次。建立抽象化層級是抵抗改變的一大支柱。原因為何?愈是抽象化,涉及實作的成份便愈少,而在軟體開發中,實作會發生改變的機會大過於抽象概念發生改變的機會。因此,針對抽象的介面來撰寫程式,相較於針對實作細目來撰寫程式,被改變所影響的機會就小多了,這是這麼做之所以能夠抵禦改變的重要原因。

在前一回中,我們提到了類別的相依圖。在相依圖上兩類別間若存在路徑,那麼當它們之間的距離便可被視為是二者之間的相依間接層級。很明顯的,當二者之間的間接層級愈高時,當某一類別發生變動,此變動的影響傳遞至另一個類別的機會就會愈低。

當我們為客戶端程式碼與實際的實作程式碼之間加上了若干層的抽象介面,那麼它們之間的抽象介面便能隔離客戶端與實作細目,形成扮演改變發生時的緩衝角色。例如在下述的相依圖中:


(圖一)

RDBMSAuthenticator的作用是依據資料庫中所記錄的帳號ID及密碼做認證的動作,而Client代表客戶端程式。倘若讓Client直接存取RDBMSAuthenticator,那麼當RDBMSAuthenticator所代表的實作有所更動時,對Client的威脅便大,因為二者乃是直接相連。但倘若我們抽取了RDBMSAuthenticator的抽象化概念,成為Authenticator抽象類別或介面,那麼ClientRDBMSAuthenticator的相依距離(dependency distance)便可以從直接相依的1提昇到間接相依的2。而且更重要的是,Client所倚賴的Authenticator是個較不會發生變動的概念,因此,能夠吸收掉RDBMSAuthenticator所發生的變化,不致於讓它的變化對Client產生影響。在設計時,要盡可能的讓客戶端程式碼面對最抽象的介面,如此一來,便能讓客戶端程式碼所受到的威脅降到最低。

除了相依圖上兩類別間的相依距離之外,相依圖上某類別所相依的類別個數(如果換成圖論(graph theory)的術語,就是它的外分支度(out degree)),同樣也會影響到某類別受到改變的機會。


(圖二)

圖二中所表示的設計,我們讓Client直接碰觸四種可能的Authenticator實作,這麼一來,它的外分支度就是四,也就是說,這四種Authenticator實作只要有任一者有所改變,Client便有可能也會受到影響。可以想見的,相依圖上外分支度為一的類別,它受到改變的機會,多半會小於外分支度為四的類別。倘若我們修改我們的設計:


(圖三)

那麼Client的外分支度就從四降到一了,也就是說,受到改變造成影響的機會也降低了。

唔,或許你會問道:「這豈不是換湯不換藥?Authenticator的外分支度仍舊還是四呀!」這樣的問題帶出了下一個主題。

在評量程式模組的品質時有許多指標,而程式模組自身內部的與其他模組間的相依性,就是其中之一。一個與其他模組間相依性低的模組,我們稱它為低耦合(low coupling)的模組。通常,有著低耦合力的模組,便會有著較高的聚合力(high cohension)。聚合力和耦合力,皆是由Larry Constantine所提出的軟體品質評量指標。所謂的聚合力,指的便是位於同一個模組內之組成的相關程度。從類別層級來看,一類別的聚合力高低,取決於其所含函式的相關程度。倘若所含函式的相關程度高,那麼便可謂之為高聚合力。倘若所含函式彼此之間相關程度低,那麼此類別本身的聚合力也就低。如果放到程式庫的層級來看,所謂高聚合力的程式庫,意指程式庫中的類別彼此之間的相關度高。

上述的相依圖所表示的設計,Authenticator及其四個實作類別事實上是位於同一個程式庫中。也就是說,它們之間的高相關度代表著整個程式庫具有較高的內聚力,這反而是往好的方向發展。另一方面,你也可以觀察到,在這個設計中,僅有Authenticator與客戶端程式碼相介接,這意謂著這一組類別所構成的模組,與外界的耦合力是比較低的,而低耦合力高聚合力正是好的軟體設計的重要表現。我們在前一回中所提到的Façade設計模式,其中心思想便在於將一群具有高度相關的類別凝聚於同一子系統內,並且以Façade類別做為外界存取整個子系統服務的唯一入口,如此一來,便形成了高聚合低耦合的特性。

雖然我們應該在設計上盡量的消除相依性,但無論如何,我們會發現解決設計問題時,每個不同的問題都有其本質的相依性極限。如果我們用整個相依圖中的連接來評估相依的程度,我們會發現,我們只能將相依性降低到某種程度,也就是說,相依圖中的連結個數是怎麼也無法消除了。

但是,當我們在消去不必要的相依性之後,透過設計,我們可以調整相依的關係。因為相依性出現在同一模組中是健康的表現,將相依性的連結移至同一模組內,反而可以轉化耦合及聚合的性質。例如在圖二中相依關係連結數為四,而在圖三中相依關係連結數為五(存在五個類別間的相依關係)。雖然在圖三中的設計相依關係連結數反而增加了,但卻是將一個原先為高耦合低聚合的設計,轉變成為一個低耦合高聚合的設計。這正突顯出設計良窳與否的重要性。我們沒有額外多寫程式碼,不過只是單純的調整類別間的關係,就能將一個具有不佳特質的設計,轉變成為一個具有良好特質的設計。

此外,雖然兩類別間存在相依關係,代表著一類別的變動可能會傳遞到另一類別,但是否會發生傳遞的情況,和相依關係的強度也有關聯。同是類別間的相依關係,事實上也有強弱的分別。例如,繼承關係便是一種很強的相依關係,而僅僅只指涉到類別名稱、但卻未存取其資料成員或函式(例如將傳入的引數再做為引數傳入另一個類別的函式,這種情況時常被稱為左手進右手出),類別間的相依關係相較起來就弱得多了。如果類別間非要存在相依性關係,那麼假若能以強度較弱的相依關係型態來取代較強的相依關係型態,便能降低變動因為這相依關係而被傳遞的可能性。在Design Patterns一書中,建議我們盡量用合成(composition)取代繼承,便是因為合成的相依強度弱於繼承。

我們在從事設計的同時,除了觀察留意相依關係的數量以及所造成的耦合聚合特性之外,也應該盡可能的使用弱相依關係來替代強相依關係。軟體設計的迷人之處及藝術美感所在便在此處,小小的設計變化,便有可能引發截然不同的結果。

Blogged with Flock

迴響:

發表迴響:
  • HTML 語法: 關閉
把對母乳媽媽的感謝與支持傳出去

« 七月 2009
星期日星期一星期二星期三星期四星期五星期六
   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 
       
今日

Search this blog

Links

Weblog menu

Today's referrers

Feeds