半身不遂和粗中有细
如果有一个老板,要员工完成某个项目但却不提供必要的条件,比如不提供必要的设备。而员工为了完成任务,从别处偷来了来路不明的但却是项目所必须的设备,最后完成了项目。对于这样的一件事,应该怎么看?
假如只考察最终的结果,那么这件事情的后果是项目完成了,似乎没什么好说的。但如果考察这件事情的完成过程,就不难发现,老板不提供必要的条件非常无理,而员工为了完成任务从别处偷来路不明的设备也很不正当。现实中不可能用这种不正当不合理的方式开发项目,因为用这种方式最后的结果几乎一定失败。
显然,不能依据最终结果来判断实现过程是否合理。过程的不合理之处并不体现在结果一定很荒唐,而体现在结果一定不会总是很不荒唐。
代码也是如此。不能光看最终的运行结果,还应该考察代码的完成过程。完成过程的不合理,一旦被潜意识所认可,迟早一定会产生这样或那样的糟糕后果。
考察一下下面的样本代码:
#include <stdio.h>
int main( )
{ float average(float array[10]);
float score[10],aver;
int i;
printf("input 10 scores:\n");
for(i=0;i<10;i++)
scanf("%f",&score[i]);
printf("\n");
aver=average(score);
printf("average score is%5.2f\n",aver);
return0;
}
float average(float array[10])
{ int i;
float aver,sum=array[0];
for(i=1;i<10;i++)
sum=sum+array[i];
aver=sum/10;
return(aver);
}
首先看main()函数,其中的“aver=average(score);”一句是main()函数要求average()函数求出数组score各个元素的平均值并返回,然而实参只有一个score,而score在这里表示的仅仅是指向数组score首个元素的指针,并不代表整个数组。也就是说main()函数并没有提供给average()函数进行计算的必要条件。这等同于老板要员工完成项目但却不提供必要的条件。
再看一下average()函数的定义。首先,形参类型声明“float array[10]”中,“[]”内的那个10不知所云,是完全多余的,毫无必要。可能有人认为写这个10并没有编译错误,因为编译器会忽视这个10。可问题是,既然这个10对编译及程序没有任何效用,为什么要写上这个10呢?如果要写一个函数求两int类型数据的和,是否会有人写成下面那样呢?
int sum ( int a, int b ,int c) { return a + b ; }
这个函数同样没有编译错误,但人人都知道那个形参c是可以没有的。可以没有的就是并不需要的,并不需要的就是多余的,多余的就是错误的。函数定义中“[]”内的那个10和这段代码中的“c”就“多余”这点来说,没什么两样。因而,average()函数定义形参类型声明应该写为“float array[]”。这表示函数计算的依据是一个由实参传来的指针。
然而,仅仅依据一个指针并不可能计算一个数组各个元素的平均值,从实参获得的信息并不充分完全。average()函数采取的办法是“偷”来一个数据——10。average()函数定义中的那个10异常扎眼,这个10来的没头没脑,比所谓的“magic number”还坏。
所谓“magic number”就是代码中出现的那些让人不容易看懂的各种字面常量。这种看起来让人感到莫名其妙的字面常量,通常是劣质代码的一个表现,这一点早有定论。average()函数定义里的这个10不但是“magic number”,而且来路不正。因为通常函数定义里的数据若不是方法本身所固有的特定数据(例如求圆面积的函数中的圆周率)就应该是来自形参,但是这个10显然即不是求平均值方法本身特定数据,也不是来自形参。
正是由于这个来路不正的10,自觉或不自觉地限制了函数的功能,把average()函数弄成了“半身不遂”。因为这个函数只能计算具有10个float类型元素数组的平均值,企图计算任何其他个数的数组的平均值都不可能。在真正的程序开发中,这样的函数没有任何实用价值。因为总不能为具有10个float元素的数组写一个函数,再为具有11个float元素的数组写一个函数,……。本来函数的一个基本功能就是减少重复的代码,如果这样一来,函数非但不能起到减少重复的作用,相反只能增加重复的代码。因此average()这样的函数属于不合格的函数,因为它缺乏起码的普遍适用性。这种只能一次性使用的半残废的函数写法在初学者中很常见,但是由于初学者往往只关注最终的结果,因而容易忽视这种函数结构本身的荒谬性。
向函数传递数组的数据,通常都应该有两个参数才足以让函数知道的完备的数组信息(个别情况例外,比如处理字符串的函数)。所以代码中average()函数定义应该:
float average(float array[] , int size) { int i; float sum = 0.F ; for ( i = 0 ; i < size ; i++ ) sum += array[i]; return sum / size ; }
样本代码中的另一个问题是,在main()中要完成数组的输入、计算平均值和输出这样三个任务。其中后两个任务都是简单地通过函数调用完成的,然而第一个任务却不是。所以main()的结构显得异常的不对称——“粗中有细”。
这种粗中有细不是张飞的那种“粗中有细”,而仿佛是一个导演一边在给演员说戏,一边同时在自己扮演自己承担的角色。这种粗中有细是代码中的败笔,有人把这称为颗粒度不均匀。和“内裤外穿”相仿,这种写法是把“外裤内穿”——把应该由函数完成的工作移到了函数的上一层。通常这种代码都是在没有很好的整体构思下完成的,这种写法也不利于对程序整体构思进行进一步思考和优化。按照结构化程序设计原则,良好的代码风格是,在main()中只考虑粗节,细节交给各个函数继续完成。譬如
int main ( void ) { float score[10]; //输入数据 //计算平均值 //输出 return 0; }
在这个总体方案确定以后再通过各个函数逐步完成全部代码。这样写出的代码是:
#include <stdio.h> void input ( float [] , int ); float average( float [] , int ); int main ( void ) { float score[10]; //输入数据 input ( score , 10 ); //计算、输出平均值 printf ("平均值为%f\n" , average( score , 10 ) ); return 0; } float average( float array[] , int size ) { int i; float sum = 0.F ; for ( i = 0 ; i < size ; i++ ) sum += array[i]; return sum / size ; } void input( float array[] , int size ) { int i ; printf("请输入%d个浮点数据:\n" , size ); for ( i = 0 ; i < size ; i ++ ) scanf("%f",&array[i]); }