建立子程式最主要的目的是提高程式的可管理性,其次,節省代碼空間也隻是一個次要原因:提高可讀性,可靠性和可修改性等原因都更重要一些。
我們先來看一個低品質的子程式的例子:
void HandleStuffle( CORP_DATA & inputRec, int crntQtr, EMP_DATA empRec, double & estimRevenue, double
ytdRevenue, int screenX, int screenY, COLOR_TYPE & newColor, COLOR_TYPE & prevColor, StatusType & status, int expenseType )
{
int i;
for ( i = 0; i < 100; i++ ) {
inputRec.revenue[i] = 0;
inputRec.expense[i] = corpExpense[crntQtr][i];
}
UpdateCorpDatabase( empRec );
estimRevenue = ytdRevenue * 4.0 / (double) crntQtr;
newColor = prevColor;
status = SUCCESS;
if ( expenseType == 1){
for (i = 0; i < 12; i++ )
profit[i] = revenue[i] - expense.type1[1];
}
else if ( expenseType == 2){
profit[i] = revenue[i] - expense.type2[1];
}
else if ( expenseType == 3){
profit[i] = revenue[i] - expense.type3[1];
}
}
以下列出該段代碼存在的問題:
- 子程式有個差勁的名字。讓人一點兒也看不出這個子程式究竟是做什麼的;
- 沒有說明文檔;
- 布局不好,過于随意;
- 這個子程式的輸入變量 inputRec 的值被改變了。如果它是一個輸入變量,她的值就不應該被修改(而且在C++中它應該被定義為 const)。如果變量的值就是要被修改的,那就不要把它命名為 inputRec;
- 這個子程式讀寫了全局變量,它從 corpExpense 中讀取數值,寫入 profit 。它應該更直接地與其他子程式通信,而不是去讀寫全局變量。
- 沒有單一的目的。它初始化了一些變量,向資料庫寫入資料,又做了一些計算。從這些事情之間看不出任何聯系。子程式應該有單一而明确的目的。
- 沒有注意防範錯誤資料。如果 crntQtr = 0,那麼表達式 ytdRevenue * 4.0 / (double) crntQtr 将會導緻除零錯誤。
- 使用了若幹神秘數值。100, 4.0, 12, 2, 3 等。
- 未使用其中一些參數:screenX 和 screenY 都沒有被引用過。
- 一個參數傳遞方式有誤:prevColor 被标為引用參數,單在這個字程式内卻未對其指派。
- 參數太多。合理的參數個數,其上限大概是 7 個左右,而這裡有 11 個。而且參數的排布方式也難以了解。
- 參數順序混亂且未經注釋。
建立子程式的正當理由:
- 降低複雜度。
- 引入中間,易懂的抽象。把一段代碼放入一個命名恰當的子程式内,是說明這段代碼用意最好的方法之一。被良好命名的子程式提供了更高層次的抽象,進而使代碼更具可讀性,也更容易了解,同僚也降低了原來包含代碼的程式的複雜度。
- 避免代碼重複。這是建立子程式最普遍的原因。
- 支援子類化。覆寫(override)簡短而規整的子程式所需新代碼的數量,要比覆寫冗長而邋遢的子程式更少。
- 隐藏順序。把處理事件的順序隐藏起來是個好主意,比讓它們在系統内到處散步要好很多。
- 隐藏指針操作。
- 提高可移植性。
- 簡化複雜的布爾判斷。
- 改善性能。
内聚性:
對于子程式來說,内聚性是指子程式中各種操作之間聯系的緊密程度。像 Cosine() (餘弦函數)這樣 的函數就是極端内聚的,因為整個程式隻完成一項功能。而 consineAndTan()(餘弦與正切)這個函數的内聚性相對較弱,因為它完成了多于一項的操作。
關于内聚性的幾個層次:
- 功能的内聚性。是最強也是最好 的一種内聚性,也就是說讓一個子程式僅執行一項操作。
- 順序上的内聚性。指子程式内包包含有需要按的定順序執行的操作,這些步驟需要共享資料,而且隻有在全部執行完畢後才完成了一項完整的功能。
- 通信上的内聚性。指一個子程式的不同操作使用了同樣的資料,但不存在其他任何聯系。
- 臨時的内聚性。含有一些因為需要同時執行才放到一起的操作的子程式。典型的例子有:Startup(), Shutdown() 等。