11子程序設計中的內聚性
一. 子程序設計
? 對於子程序而言,內聚性是指子程序中各種操作之間聯系的緊密程度。有些程序員更喜歡使用“強度”這一術語:一個子程序中各種操作之間的聯系有多強?想Consine()(余弦函數)這樣的函數就是極端內聚的,因為整個程序只完成一項功能。而CosineAndTan() (余弦余正切)這個函數的內聚性相對較弱,因為它完成了多余一項的操作。我們的目標是讓每一個子程序只把一件事情做好,不再做其他任何事情。
? 這樣做得好處是得到更高的可靠性。一項針對450個子程序所做的研究發現,高內聚性的子程中有50%沒有任何錯誤,而低內聚性的子程序中只有18%是沒有錯誤的。
? 關於內聚性的討論一般會涉及到內聚性的幾個層次。理解一些概念要比記住一些特定的術語更重要。
1. 功能的內聚性
? 功能的內聚性是最強也是最好的一種內聚性,也就是讓一個子程序僅執行一項操作。例如 sin() 這樣的子程序都是高度內聚的。當然,以這種方式來評估內聚性,前提是子程序所執行的操作與其名字相符——如果它還做了其他的操作,那麽它就不夠內聚,同時其命名也有問題。
2. 不夠理想的內聚性
2.1. 順序上的內聚性
? 順序上的內聚性是指在子程序內包含有需要特定順序執行的操作,這些步驟需要共享數據,而且只有在全部執行完畢後才完成了一項完整的功能。
? 舉一個順序上的內聚性的例子,假設某個子程序需要按照給定出生日期來計算出員工的年齡和退休時間。如果子程序先計算員工的年齡,再根據他的年齡來計算退休的時間,那麽他就有順序上的內聚性。而如果子程序先計算員工的年齡,然後再重新計算他的退休時間,兩次計算之間只是碰巧使用了相同的出生日期,那麽這個子程序就只具有通信上的內聚性。
? 那麽該怎樣設計具有功能上的內聚性的子程序呢?你可以創建兩個不同的子程序,他們能根據給定的生日分別計算員工的年齡和退休時間。其中,計算退休時間的子程序可以調用計算年齡的子程序。這樣兩者就都具有功能上的內聚性了。而其他的子程序則可以調用二者之一或者全部。
2.2 通信上的內聚性
? 通信上的內聚性是指一個子程序中的不同操作使用了同樣的數據,但不存在其他任何聯系。例如某個子程序先根據傳給它的匯總數據打印一份匯總報表,然後再把這些匯總數據重新初始化,那麽這個子程序就具有通信上的內聚性;因為這兩項操作只是使用了相同的數據才彼此產生聯系。
? 要改善這個子程序的內聚性,應該讓重新初始化匯總數據的操作盡可能靠近創建數據匯總的地方,而不是放在打印報表的子程序裏。應該把這些子程序進一步拆分成你個獨立的子程序:一個負責打印報表,一個負責在靠近創建或修改數據的代碼的地方重新初始化數據。然後在原本調用那個具有通信內聚性的子程序的更高層的子程序中調用這兩個子程序。
2.3. 臨時的內聚性
? 臨時的內聚性是指含有一些因為需要同時執行才放到一起的操作的子程序。典型的例子有:Startup()、Shuntdown() 等。有些程序員認為臨時的內聚性是不可取的,因為他們有時與不良的編程實踐相關——比如在 Startup() 子程序裏塞進一大堆互不相關的代碼等。
? 為避免這個問題,可以把臨時性的子程序看做是一系類實踐的組織者。前面提到的 Sartup() 子程序可能需要讀取配置文件、初始化臨時文件、設置內存管理,再顯示啟動畫面。要想使它最有效,應該讓原來那個具有臨時內聚性的子程序去調用其他的子程序,由這些子程序來完成特定的操作,而不是由它直接執行所有的操作。
? 這個例子提出這樣一個問題,即如何選擇一個能夠在恰當的抽象層次上描述子程序的名字。你可能決定把一個子程序命名為 ReadConfigFileInitScratchFileEtc(),它可以暗示該子程序只有巧合的內聚性。而如果你把它命名為 Startup(),那麽很明顯,這個子程序就只具有一個功能,切具有功能上的內聚性。
3. 不可取的內聚性
? 一般來說,其他類型的內聚性都是不可取的。它們都會導致代碼組織混亂、難於調試、不便修改。如果一個子程序具有不良的內聚性,那最好還是花功夫重新編寫,使其具有更好的內聚性,而不是再去花精力精確地診斷問題所在了。因此,知道應該避免什麽是非常有用的,下面列舉一些不可取的內聚性。
3.1 過程上的內聚性
? 過程上的內聚性是指一個子程序中的操作是按特定的順序進行的。一個例子是依次獲取員工的姓名、住址和電話號碼的子程序。這些操作執行的順序之所以重要,只是因為它和用戶按屏幕提示而輸入數據的順序相一致。另一個子程序用來獲取員工的其他數據。這段程序也具有過程上的內聚性,因為它把一組操作賦以特定的順序,而這些操作並不需要為ile除此之外的任何原因而彼此聯系。
? 為了得到更好的內聚性,可以把不同的操作納入各自的子程序中。讓調用放你的子程序具有單一而完整的功能:GetEmployee() 就比 GetFirstPartOfEmployeeData() 更為可取。你可能還需要修改用來讀取其余數據的子程序。為了讓所有的子程序都具有功能上的內聚性,對兩個或更多的原有子程序進行修改是很常見的。
3.2 邏輯上的內聚性
? 邏輯上的內聚性是指若幹操作被放入同一個子程序中,通過傳入的控制標誌選擇執行其中的一項操作。之所以稱之為邏輯上的內聚性,是因為子程序的控制流或所謂的“邏輯”是將這些操作放到一起的唯一原因——他們都被包在一個很大的 if 語句或者 case 語句中,而不是因為各項操作之間有任何邏輯關系。
? 如果子程序裏的代碼僅由一系列的 if 語句或者 case 語句,以及調用其他子程序的語句組成,那麽創建這樣一個具有邏輯上的內聚性的子程序通常也是可以的。在這種情況下,如果子程序唯一的功能就是發布各種命令,其自身並不做任何處理,著通常一個是一個不錯的設計。這類子程序的技術術語便是“事件處理器”。事件處理器通常用在各種交互性的環境中,例如像 Apple Macintonsh、Microsoft Windows 以及其他一些 GUI 環境。
3.3. 巧合的內聚性
? 巧合的內聚性是指子程序中的各個操作之間沒有任何可以看到的關聯。它也可稱為”無內聚性“或”混亂的內聚性“。
11子程序設計中的內聚性