劣質代碼評析——刻舟求劍的故事


 【題目】

  將一個5*5的矩陣中最大的元素中最大的元素放在中心,4個角分別放4個最小的元素(順序為從左到右,從上到下依次從小到大存放),寫一函數實現之,用main函數調用。

    ——譚浩強 ,《C程序設計(第四版)學習輔導》,清華大學出版社,2010年7月,p108
【評析】
  這其實是一個相當復雜的問題,題目作者自己大概根本沒意識到,更沒有真正想清楚這個問題的解決,拍拍腦袋就出題了。
  題目中明顯的問題是語義不清,比如什么是“最大”?什么是“最小”?什么是倒數第二小?這些含義都不明確。所以如果不進一步對問題進行補充說明,題目的解並不能保證唯一。客氣點說題目不嚴格,如果事實求是說,題目本身就是錯誤的。
  例如,1、1、2、2、3、3這6個數中,究竟1是這組整數中第二小的數,還是2是第二小的整數?這個問題的答案很可能有歧義,更有歧義的是究竟哪個位置上的數是第2小的數?
  “寫一函數實現之”完全是一個過分的要求,也是一個外行的要求,就跟要求別人用腳指頭敲鍵盤一樣。
【原代碼】

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

  ——譚浩強 ,《C程序設計(第四版)學習輔導》,清華大學出版社,2010年7月,p109~110
【評析】

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

 

  不會寫代碼的人毛病,就是急着先把能寫的內容一下子寫完。在這段代碼中可以看到change(p)像是掉進了一堆亂麻里的一只牙簽一樣,整個代碼的結構亂作一團。這是一只很拙劣的風格,對比一下下面的寫法就知道這段代碼有多糟糕了。

#include <stdio.h>

int main( void )
{
   int a[5][5] ;

   input(a,5);
   change(*a);
   output(a,5);

   return 0;
}

 

  哪個寫法清晰明了是一目了然的吧?
  結構化程序設計中有一條著名的原則,這就是自頂向下(Top Down),這個原則說的是先從大處着眼,把問題分解為若干小問題,然后再把這些小問題進一步分解為更小的問題,直到無法再分解為止。這叫逐步細化。
每一層分解都是一種抽象,抽象的目的是為了概括,概括的目的是為了有一個簡單的表現形式,從而把不必要的細節隱藏在這種簡單的表現形式的后面。函數是實現這種思想的利器。在每次的分解過程中,問題總是被分解為同一層次上的問題。這樣的代碼才可能具有優雅的均衡感和良好的可讀性。

8.  p=&a[0][0];
9.  change(p);

 

  是典型的多此一舉。因為p這個變量多余。這兩句話無非就是

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

 

而已。需要注意的是,change()函數要操作的是整個數組,而這里的實參僅僅是一個指向a[0][0]的指針。很顯然,change()得到的信息並不充分,它不可能知道它要操作的是一個int [5][5]類型的數組。換言之,chang()函數不可能完成任務,除非它再通過別的歪門邪道獲得其他必要的信息。
  下面考察chang()函數定義。
  從沒見過這么丑的函數!居然能把函數寫得如此之丑,不禁嘆為奇跡。

void change(int *p)

 

前面提到過,參數不完整。這注定后面要走歪門邪道(比如許多無厘頭的Magic Number)。

23.   for(i=0;i<5;i++)
24.     for(j=i;j<5;j++)
25.      {if(*pmax<*(p+5*i+j))pmax=p+5*i+j;
26.       if(*pmin>*(p+5*i+j))pmin=p+5*i+j;
27.      }

 

  這段代碼試圖尋找最大值與最小值的位置。但是正如前面討論過的那樣,最大、最小值的位置可能不是唯一的。那么究竟要找的是哪一個呢?其實代碼作者自己也不知道,找到哪個算哪個,無頭蒼蠅撞大運。
  這其中有一個明顯的錯誤,就是內層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語言並沒有承諾這樣寫會得到什么,這樣的代碼可能得到任何結果。就像瘋子的胡言亂語可以隨意解釋一樣。
  即使拋開前面一系列錯誤不談,后面的代碼依然是錯誤的:

28.   temp=*(p+12);
29.   *(p+12)=*pmax;
30.   *pmax=temp;
31.   temp=*p;
32.   *p=*pmin;
33.   *pmin=temp;

 

  這里,代碼把pmax所指向的數據對象與p+12(這個表達式本身就不靠譜)所指向的數據對象交換,然后又把pmin所指向的數據對象與p所指向的數據對象交換。但是代碼作者忘記了,pmin所指向的位置可能恰恰是p+12所指向的位置,在這種情況下,第二次交換就是最大元素與p指向的元素的交換,而非所期待的最小元素與*p的交換,這樣得到的結果顯然是錯誤的。在這里代碼作者犯了中國古代寓言“刻舟求劍”中一模一樣的錯誤。古希臘哲學家說人不能兩次走入同一條河,說的也是同樣的道理。
  這個錯誤從另一個方面提示我們,寫代碼應該一個問題一個問題地解決,不要眉毛胡子一把抓,把幾個問題攪和在一塊。
  好了。從內容到形式,從算法到語法,代碼已經是錯得一塌糊塗了,差不多要亮瞎我的雙眼了。沒有任何理由再繼續看下去了。就此打住。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM