创建子程序最主要的目的是提高程序的可管理性,其次,节省代码空间也只是一个次要原因:提高可读性,可靠性和可修改性等原因都更重要一些。
我们先来看一个低质量的子程序的例子:
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() 等。