从刻舟求剑的故事评析劣质代码

开发 架构
我们今天将从一个习题出发,讲讲不会写代码的人毛病,就是急着先把能写的内容一下子写完。

 【题目】

  将一个5*5的矩阵中***的元素中***的元素放在中心,4个角分别放4个最小的元素(顺序为从左到右,从上到下依次从小到大存放),写一函数实现之,用main函数调用。

    ——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p108

【评析】

  这其实是一个相当复杂的问题,题目作者自己大概根本没意识到,更没有真正想清楚这个问题的解决,拍拍脑袋就出题了。

题目中明显的问题是语义不清,比如什么是“***”?什么是“最小”?什么是倒数第二小?这些含义都不明确。所以如果不进一步对问题进行补充说明,题目的解并不能保证唯一。客气点说题目不严格,如果事实求是说,题目本身就是错误的。

  例如,1、1、2、2、3、3这6个数中,究竟1是这组整数中第二小的数,还是2是第二小的整数?这个问题的答案很可能有歧义,更有歧义的是究竟哪个位置上的数是第2小的数?

  “写一函数实现之”完全是一个过分的要求,也是一个外行的要求,就跟要求别人用脚指头敲键盘一样。

【原代码】

  1. . #include <stdio.h> 
  2. int main() 
  3. . {void change(int *p); 
  4. .  int a[5][5],*p,i,j; 
  5. .  printf("input matrix:\n"); 
  6. .  for(i=0;i<5;i++) 
  7. .    for(j=0;j<5;j++) 
  8. .      scanf("%d",&a[i][j]); 
  9. .  p=&a[0][0]; 
  10. .  change(p); 
  11. .  printf("Now matrix:\n"); 
  12. .  for(i=0;i<5;i++) 
  13. .    {for(j=0;j<5;j++) 
  14. .       printf("%d ",a[i][j]); 
  15. .     printf("\n"); 
  16. .    } 
  17. .  return 0; 
  18. . } 
  19. void change(int *p) 
  20. .  {int i,j,temp; 
  21. .   int *pmax,*pmin; 
  22. .   pmax=p; 
  23. .   pmin=p; 
  24. .   for(i=0;i<5;i++) 
  25. .     for(j=i;j<5;j++) 
  26. .      {if(*pmax<*(p+5*i+j))pmax=p+5*i+j; 
  27. .       if(*pmin>*(p+5*i+j))pmin=p+5*i+j; 
  28. .      } 
  29. .   temp=*(p+12); 
  30. .   *(p+12)=*pmax; 
  31. .   *pmax=temp; 
  32. .   temp=*p; 
  33. .   *p=*pmin; 
  34. .   *pmin=temp; 
  35. .   pmin=p+1; 
  36. .   for(i=0;i<5;i++) 
  37. .     for(j=0;j<5;j++) 
  38. .       if(((p+5*i+j)!=p)&&(*pmin>*(p+5*i+j)))pmin=p+5*i+j; 
  39. .   temp=*pmin; 
  40. .   *pmin=*(p+4); 
  41. .   *(p+4)=temp; 
  42. .   pmin=p+1; 
  43. .   for(i=0;i<5;i++) 
  44. .     for(j=0;j<5;j++) 
  45. .       if(((p+5*i+j)!=(p+4))&&((p+5*i+j)!=p)&&(*pmin>*(p+5*i+j))) 
  46. .            pmin=p+5*i+j; 
  47. .   temp=*pmin; 
  48. .   *pmin=*(p+20); 
  49. .   *(p+20)=temp; 
  50. .   pmin=p+1; 
  51. .   for(i=0;i<5;i++) 
  52. .      for(j=0;j<5;j++) 
  53. .        if(((p+5*i+j)!=p)&&((p+5*i+j)!=(p+4))&&((p+5*i+j)!=(p+20))&& 
  54. .             (*pmin>*(p+5*i+j))) pmin=p+5*i+j; 
  55. .   temp=*pmin; 
  56. .   *pmin=*(p+24); 
  57. .   *(p+24)=temp; 
  58. . } 

  ——谭浩强 ,《C程序设计(第四版)学习辅导》,清华大学出版社,2010年7月,p109~110

【评析】

  1. .  printf("input matrix:\n"); 
  2. .  for(i=0;i<5;i++) 
  3. .    for(j=0;j<5;j++) 
  4. .      scanf("%d",&a[i][j]); 
  5. .  p=&a[0][0]; 
  6. .  change(p); 
  7. .  printf("Now matrix:\n"); 
  8. .  for(i=0;i<5;i++) 
  9. .    {for(j=0;j<5;j++) 
  10. .       printf("%d ",a[i][j]); 
  11. .     printf("\n"); 
  12. .    } 

 

不会写代码的人毛病,就是急着先把能写的内容一下子写完。在这段代码中可以看到change(p)像是掉进了一堆乱麻里的一只牙签一样,整个代码的结构乱作一团。这是一只很拙劣的风格,对比一下下面的写法就知道这段代码有多糟糕了。

 

  1. #include <stdio.h> 
  2.   
  3. int main( void ) 
  4.    int a[5][5] ; 
  5.   
  6.    input(a,5); 
  7.    change(*a); 
  8.    output(a,5); 
  9.   
  10.    return 0; 

 

  哪个写法清晰明了是一目了然的吧?

  结构化程序设计中有一条著名的原则,这就是自顶向下(Top Down),这个原则说的是先从大处着眼,把问题分解为若干小问题,然后再把这些小问题进一步分解为更小的问题,直到无法再分解为止。这叫逐步细化。

每一层分解都是一种抽象,抽象的目的是为了概括,概括的目的是为了有一个简单的表现形式,从而把不必要的细节隐藏在这种简单的表现形式的后面。函数是实现这种思想的利器。在每次的分解过程中,问题总是被分解为同一层次上的问题。这样的代码才可能具有优雅的均衡感和良好的可读性。

  1. p=&a[0][0]; 
  2. change(p); 

  是典型的多此一举。因为p这个变量多余。这两句话无非就是

  1. change(*a);   //或change(a[0]);  //或change(&a[0][0]); 

 

而已。需要注意的是,change()函数要操作的是整个数组,而这里的实参仅仅是一个指向a[0][0]的指针。很显然,change()得到的信息并不充分,它不可能知道它要操作的是一个int [5][5]类型的数组。换言之,chang()函数不可能完成任务,除非它再通过别的歪门邪道获得其他必要的信息。

  下面考察chang()函数定义。

  从没见过这么丑的函数!居然能把函数写得如此之丑,不禁叹为奇迹。

  1. void change(int *p) 

前面提到过,参数不完整。这注定后面要走歪门邪道(比如许多无厘头的Magic Number)。

  1. for(i=0;i<5;i++) 
  2.   for(j=i;j<5;j++) 
  3.   {if(*pmax<*(p+5*i+j))pmax=p+5*i+j; 
  4.    if(*pmin>*(p+5*i+j))pmin=p+5*i+j; 
  5.    } 

  这段代码试图寻找***值与最小值的位置。但是正如前面讨论过的那样,***、最小值的位置可能不是唯一的。那么究竟要找的是哪一个呢?其实代码作者自己也不知道,找到哪个算哪个,无头苍蝇撞大运。

  这其中有一个明显的错误,就是内层for语句中的j=i。这表明它并不是在整个数组中寻找***最小值的位置,而只是在数组中的一个局部寻找***最小值的位置。这样的代码显然是错误的。

  即使改正了这个错误,从C语言的语法角度讲,这段代码也还是错误的。因为p是指向main()中a[0][0]的指针,而a[0][0]是一维数组a[0]的一个元素。根据C语言的规则,这个p做加减法得到的结果必须依然指向a[0]中的元素或者a[0]中***一个元素的下一个位置,否则代码行为是未定义的。而在p+5*i+j这个表达式中,由于5*i+j的值可能大于5,所以是未定义行为。所谓未定义行为通俗地来说就是,C语言并没有承诺这样写会得到什么,这样的代码可能得到任何结果。就像疯子的胡言乱语可以随意解释一样。

  即使抛开前面一系列错误不谈,后面的代码依然是错误的:

  1. temp=*(p+12); 
  2.  *(p+12)=*pmax; 
  3.  *pmax=temp; 
  4.  temp=*p; 
  5. *p=*pmin; 
  6. *pmin=temp; 

  这里,代码把pmax所指向的数据对象与p+12(这个表达式本身就不靠谱)所指向的数据对象交换,然后又把pmin所指向的数据对象与p所指向的数据对象交换。但是代码作者忘记了,pmin所指向的位置可能恰恰是p+12所指向的位置,在这种情况下,第二次交换就是***元素与p指向的元素的交换,而非所期待的最小元素与*p的交换,这样得到的结果显然是错误的。在这里代码作者犯了中国古代寓言“刻舟求剑”中一模一样的错误。古希腊哲学家说人不能两次走入同一条河,说的也是同样的道理。

  这个错误从另一个方面提示我们,写代码应该一个问题一个问题地解决,不要眉毛胡子一把抓,把几个问题搅和在一块。

  好了。从内容到形式,从算法到语法,代码已经是错得一塌糊涂了,差不多要亮瞎我的双眼了。没有任何理由再继续看下去了。就此打住。

 

原文链接:http://www.cnblogs.com/pmer/archive/2012/07/22/2604229.html

【编辑推荐】

 

 

责任编辑:彭凡 来源: 博客园
相关推荐

2011-06-03 08:58:14

2018-01-31 07:47:10

线缆电线电缆

2011-06-15 13:20:33

2021-04-14 10:05:04

中国联通薪资员工

2013-08-22 10:56:34

大数据

2009-08-01 15:47:04

网线故障

2010-03-10 17:09:09

交换机

2017-07-05 15:09:52

密码token浏览器

2010-12-24 09:36:37

2011-06-19 18:35:14

打印机常见问题

2021-07-12 05:01:11

云原生软件架构

2013-10-15 09:56:54

大数据

2011-06-20 09:31:20

项目经理

2012-06-13 11:48:34

国双科技软件外包大数据

2016-12-02 14:15:52

2012-03-12 09:46:21

Solaris2.5illumos

2012-04-01 14:41:10

Solarisillumos

2009-06-16 10:33:26

华为任正非创业故事

2012-10-17 13:50:25

2021-02-06 23:21:35

SaaS开发低代码
点赞
收藏

51CTO技术栈公众号