鸡窝里飞出伪凤凰
不少初学者在写代码时喜欢一main()到底——把所有的代码都写在main()函数中。代码在作茧自缚的main()函数“迷宫”中像没头苍蝇一样左冲右突,写到哪儿算哪儿。忙活了半天之后,终于成功“突围”——可以运行程序了,然后就一脸轻松。但是却把main()函数弄得凌乱不堪,一地鸡毛,形同鸡窝。
这种代码是一种“爬行”式思维的产物,是人类思维的一种返祖现象。人类在学会直立行走之前就是按照这种方式思考的。俗话说,站得高才能看得远。而匍匐在地上爬行,只能得到脏、乱、差的代码。
这种代码有一种变形,就是在一地鸡毛的main()中可能会冷不丁地突然窜出一个令人眼前一亮的自定义函数调用,犹如一地鸡毛的鸡窝里仿佛要飞出一只金凤凰似的。这样的代码多半出自那种半生不熟的新手,他们并不是自觉自愿地而往往是被强迫地使用一下自定义函数。这就使得代码产生了一种滑稽的喜感,犹如一只小狗被要求站立起来作揖一样,站立一秒钟之后,很快就会重新伏到地上继续爬行。下面的代码就是一例:
/*例8.8 将数组a中的n个整数按相反顺序存放*/
#include <stdio.h>
int main( void )
{void inv(int x[],int n);
int i, a[10]={3,7,9,11,0,6,7,5,4,2};
printf("The original array:\n");
for(i=0;i<10;i++)
printf("%d ",a[i]);
printf("\n");
inv(a,10);
printf("The array has been vnverted:\n");
for(i=0;i<10;i++)
printf("%d ",a[i]);
printf("\n");
return 0;
}
void inv(int *x,int n)
{int *p,temp,*i,*j,m=(n-1)/2;
i=x;j=x+n-1;p=x+m;
for(;i<=p;i++,j--)
{temp=*i;*i=*j;*j=temp;}
return ;
}
————谭浩强 ,《C程序设计》(第四版),清华大学出版社, 2010年6月,p241
首先来看main():
一进门就可以看到main()函数的头上顶着一条条幅——不是“北京欢迎您”,而是函数类型声明“void inv(int x[],int n);”。这样做的效果是使得main()显得更加凌乱,并且有让main()函数独霸inv()函数使用权之嫌。因为其他代码中的其他函数若需要调用inv()函数还得重新进行函数类型声明。这种不加思索地把函数类型说明随便塞在某个地方的做法,绝不可能是出于事先周密思考权衡的结果,其原因估计多半只是为了对付编译器的类型检查而已。和下面的写法对比一下,就不难发现函数类型声明塞在函数内部的荒谬性:
#include <stdio.h>
void inv(int x[],int n);
int main( void )
{
/*……*/
}
显然,后者不但能完全实现样本代码的功能,而且main()更清爽,此外代码的其他部分若需要调用inv()也不需要无谓地再次写函数类型声明了。
再继续往下看
printf("The original array:\n");
for(i=0;i<10;i++)
printf("%d ",a[i]);
printf("\n");
一地鸡毛!在main()这样重要的地方忙活鸡毛蒜皮是庸人最擅长的事情。其原因在于:思想一直是在代码层面上爬行,而又缺乏代码重构的意识。这是违背结构化程序设计原则的一个报应。结构化程序设计要求“自顶向下”地思考,要达到这个境界,前提是要“站直了,别趴下”。一直趴着绝对写不出优雅,简洁的代码。优秀的代码首先要站得高,其次很忌讳把事做“绝”。再往下看
inv(a,10);
前后都是一地鸡毛,唯独这个充分且简洁的函数调用犹如金凤一样摆出了一副展翅欲飞的优美姿态。赞一个!再继续看
printf("The array has been vnverted:\n");
for(i=0;i<10;i++)
printf("%d ",a[i]);
printf("\n");
天哪!竟然写出了和前面一模一样的代码!对于任何合格的程序员来说,这绝对是一种耻辱。这和车轱辘话来回说没什么区别。这使得main()即使苍蝇没来下蛆就已经脏乱得腐败不堪了。当然,有些人对此是不介意的。“不干不净,吃了没病”,你没办法对那些在垃圾桶里寻找食物的人说清楚什么叫做卫生。“不干不净,吃了没病”这种想法在程序设计界的翻版“只要程序能运行”,你同样也无法和有些人说清楚代码为什么应该简洁优美,这是没有办法的事情。但是,如果把这种丑陋的恶习写在教科书里,那就成了一种教唆,我个人认为应该判刑。因为这就和《卫生》课本里示范如何食用地沟油异曲同工。
再往后,就是那只从main()中飞出来的inv()函数的定义了。
void inv(int *x,int n)
{int *p,temp,*i,*j,m=(n-1)/2;
i=x;j=x+n-1;p=x+m;
for(;i<=p;i++,j--)
{temp=*i;*i=*j;*j=temp;}
return ;
}
首先,把“int *p,temp,*i,*j,m=(n-1)/2;”这几个变量定义紧密地写在“{”之后是一种令人作呕的风格。此外,令人吃惊的是,实现如此简单的功能,居然一口气使用了5个“蒙头盖脸”的变量——你从名字上绝对不可能看出这些变量是做什么用的。这也是趴在地上思考的成果。由于事先缺乏充分且有高度的思考,就免不了“东一榔头西一棒子”地滥用变量(反正变量不要钱)。同样是由于缺乏缜密的思考,所以使用这些变量只是由于一时兴起,事后代码作者自己恐怕也弄不清楚究竟这些变量的真正含义。实际上根本不需要这么多的变量。
首先来看p,它的作用仅仅是用来表示x+m这个值,显然毫无必要,代码完全可以写成
void inv(int *x,int n)
{
int temp,*i,*j,m=(n-1)/2;
i=x;j=x+n-1;
for(;i<=x+m;i++,j--)
{temp=*i;*i=*j;*j=temp;}
return ;
}
再看m,它只是记录了(n-1)/2这个在代码中从没有改变过的值而已,因而也没有必要。
void inv(int *x,int n)
{
int temp,*i,*j;
i=x;j=x+n-1;
for(;i<=x+(n-1)/2;i++,j--)
{temp=*i;*i=*j;*j=temp;}
return ;
}
现在对代码走查一下,假设n的值为3,那么i,j的变化情况为:
i j
x x+2
x+1 x+1
不难发现,当i变化成x+1时,程序进行了一次毫无意义的交换。这说明“i<=x+(n-1)/2”这个表达式不但在写法上过于啰嗦,在逻辑上也很非常蹩脚。实际上只要简单地
void inv(int *x,int n)
{
int temp,*i,*j;
for(i=x,j=x+n-1 ; i < j ;i++,j--)
{temp=*i;*i=*j;*j=temp;}
return ;
}
就可以了。
最后,
{temp=*i;*i=*j;*j=temp;}
不但风格上奇丑无比,而且同样犯了把事做“绝”的毛病——绝则错。
所以,尽管从main()这个鸡窝里飞了出来,但inv()只是一只“伪”凤凰而已。
最后,对这段代码重构如下:
#include <stdio.h> void inv(int [],int ); void output( int [] , int , char *); void swap ( int * , int * ); int main( void ) { int a[10]={3,7,9,11,0,6,7,5,4,2}; output(a,10,"The original array:"); inv(a,10); output(a,10,"The array has been inverted:"); return 0; } void output( int x[] , int n , char *s ) { int i; puts(s); for( i = 0 ; i < n ; i++ ) printf("%d ", x[i]); putchar('\n'); } void inv(int *p_begin , int n ) { int *p_end = p_begin + n - 1 ; while ( p_end > p_begin ) swap ( p_end -- , p_begin ++ ); } void swap ( int *p , int *q ) { int temp ; temp = *p ; *p = *q ; *q = temp ; }