Chinaunix首页 | 论坛 | 博客
  • 博客访问: 139743
  • 博文数量: 22
  • 博客积分: 1326
  • 博客等级: 中尉
  • 技术积分: 258
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-02 00:52
文章分类
文章存档

2012年(1)

2011年(21)

分类: C/C++

2011-09-18 10:33:52

    如果有一个老板,要员工完成某个项目但却不提供必要的条件,比如不提供必要的设备。而员工为了完成任务,从别处偷来了来路不明的但却是项目所必须的设备,最后完成了项目。对于这样的一件事,应该怎么看?
    假如只考察最终的结果,那么这件事情的后果是项目完成了,似乎没什么好说的。但如果考察这件事情的完成过程,就不难发现,老板不提供必要的条件非常无理,而员工为了完成任务从别处偷来路不明的设备也很不正当。现实中不可能用这种不正当不合理的方式开发项目,因为用这种方式最后的结果几乎一定失败。
    显然,不能依据最终结果来判断实现过程是否合理。过程的不合理之处并不体现在结果一定很荒唐,而体现在结果一定不会总是很不荒唐。
    代码也是如此。不能光看最终的运行结果,还应该考察代码的完成过程。完成过程的不合理,一旦被潜意识所认可,迟早一定会产生这样或那样的糟糕后果。
    考察一下下面的样本代码:
  1. #include <stdio.h>
  2. int main( )
  3. { float average(float array[10]);
  4.   float score[10],aver;
  5.   int i;
  6.   printf("input 10 scores:\n");
  7.   for(i=0;i<10;i++)
  8.     scanf("%f",&score[i]);
  9.   printf("\n");
  10.   aver=average(score);
  11.   printf("average score is%5.2f\n",aver);
  12.   return 0;
  13. }
  14. float average(float array[10])
  15. { int i;
  16. float aver,sum=array[0];
  17. for(i=1;i<10;i++)
  18.     sum=sum+array[i];
  19. aver=sum/10;
  20. return(aver);
  21. }
————谭浩强 ,《C程序设计》(第四版),清华大学出版社,2010年6月,p194
    首先看main()函数,其中的“aver=average(score);”一句是main()函数要求average()函数求出数组score各个元素的平均值并返回,然而实参只有一个score,而score在这里表示的仅仅是指向数组score首个元素的指针,并不代表整个数组。也就是说main()函数并没有提供给average()函数进行计算的必要条件。这等同于老板要员工完成项目但却不提供必要的条件。
    再看一下average()函数的定义。首先,形参类型声明“float array[10]”中,“[]”内的那个10不知所云,是完全多余的,毫无必要。可能有人认为写这个10并没有编译错误,因为编译器会忽视这个10。可问题是,既然这个10对编译及程序没有任何效用,为什么要写上这个10呢?如果要写一个函数求两int类型数据的和,是否会有人写成下面那样呢?
  1. int sum ( int a, int b ,int c)
  2. {
  3.    return a + b ;
  4. }
    这个函数同样没有编译错误,但人人都知道那个形参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()函数定义应该:
  1. float average(float array[] , int size)
  2. {
  3. int i;
  4. float sum = 0.F ;
  5. for ( i = 0 ; i < size ; i++ )
  6.     sum += array[i];
  7. return sum / size ;
  8. }
    样本代码中的另一个问题是,在main()中要完成数组的输入、计算平均值和输出这样三个任务。其中后两个任务都是简单地通过函数调用完成的,然而第一个任务却不是。所以main()的结构显得异常的不对称——“粗中有细”。
    这种粗中有细不是张飞的那种“粗中有细”,而仿佛是一个导演一边在给演员说戏,一边同时在自己扮演自己承担的角色。这种粗中有细是代码中的败笔,有人把这称为颗粒度不均匀。和“内裤外穿”相仿,这种写法是把“外裤内穿”——把应该由函数完成的工作移到了函数的上一层。通常这种代码都是在没有很好的整体构思下完成的,这种写法也不利于对程序整体构思进行进一步思考和优化。按照结构化程序设计原则,良好的代码风格是,在main()中只考虑粗节,细节交给各个函数继续完成。譬如
  1. int main ( void )
  2. {
  3.   float score[10];
  4.   //输入数据
  5.   //计算平均值
  6.   //输出
  7.   return 0;
  8. }
在这个总体方案确定以后再通过各个函数逐步完成全部代码。这样写出的代码是:
  1. #include <stdio.h>
  2. void input ( float [] , int );
  3. float average( float [] , int );
  4. int main ( void )
  5. {
  6.   float score[10];
  7.   //输入数据
  8.   input ( score , 10 );
  9.   //计算、输出平均值
  10.   printf ("平均值为%f\n" , average( score , 10 ) );
  11.   return 0;
  12. }
  13. float average( float array[] , int size )
  14. {
  15.   int i;
  16.   float sum = 0.F ;
  17.   for ( i = 0 ; i < size ; i++ )
  18.       sum += array[i];
  19.   return sum / size ;
  20. }
  21. void input( float array[] , int size )
  22. {
  23.   int i ;
  24.   printf("请输入%d个浮点数据:\n" , size );
  25.   for ( i = 0 ; i < size ; i ++ )
  26.      scanf("%f",&array[i]);
  27. }
阅读(2832) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~