Chinaunix首页 | 论坛 | 博客
  • 博客访问: 126453
  • 博文数量: 16
  • 博客积分: 287
  • 博客等级: 二等列兵
  • 技术积分: 329
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-22 21:16
文章分类
文章存档

2014年(1)

2013年(6)

2012年(9)

分类: IT业界

2012-08-23 19:48:15

作为IT从业人员,我们绕不开阅读代码这扇通往更高一级殿堂的神秘之门。也许你是出于兴趣,也许你是被迫,但生活总得继续,代码还得读着。就像坐在一个教室里的孩子学习成绩有好有坏,阅读同一份代码的程序员也存在优劣之分;学习成绩的高低依赖于学习方法,而阅读代码的水平也是由阅读技巧决定。

我们可能总是要面对各种不同类型的、不同质量的甚至不同语言编写的代码,不过总有一些一般性的原则是适用于普遍意义的,《代码阅读》一书大致为我们总结了为了快速读懂一段代码需要做到的几点:

1.如果有可能,找到这份代码对应的说明文档,阅读之。如果非要举个例子,我觉得MSDN很好地解释了这一点。相信做过Windows API程序开发的读者对MSDN不会陌生,诸多底层函数正是有了这个超大文档才让我们清楚地了解了其功能和用法。说明文档的好处就在于,在你真正去阅读代码内部的实现细节之前,至少它会清晰地告诉你这段代码是用来干什么的。

 

2.除非高质量的开源软件或是商业软件,否则上面一条一般都难以实现,那么就尝试看一下这段代码的头部是否有关于它的注释,如果有就仔细阅读一下吧。下面列举了一份函数头部注释,我们来看看具体讲些什么吧:

点击(此处)折叠或打开

  1. /***************************************************************************************************
  2. Function : Velocity
  3. Description : To set or modify the velocity of a robot's left and right wheel
  4. Calls : Nothing
  5. Called By :
  6. Input :
  7. env --- The pointer for the environment struct
  8. robot --- The No. of the robot
  9. vl --- The velocity of the left wheel to be set
  10. vr --- The velocity of the right wheel to be set
  11. Output :
  12. env --- The pointer for the environment struct
  13. Return : Nothing
  14. Author : B.Y.
  15. Version : 1.0
  16. Date : 20110817
  17. Modified :
  18. ***************************************************************************************************/

相信不难看出,这段注释对应的代码一定是给机器人设置轮速的程序,有了这个作为基础,相信具体去阅读代码细节的时候应该底气更足了吧。

 

3.上面一条也许需要足够的运气碰到一位严谨的程序员才能做到,否则就试试通过函数名或模块名来大致猜猜你要读的这段代码可能要实现的功能吧。相信我们看到swap这样的函数名一般都会联想到交换两个数,而BubbleSort这种除了冒泡排序还会是什么呢?不过如果看到dfs或者bfs这样的名字,我们只能知道这是关于深度优先搜索和广度优先搜索的算法,除此以外一无所知,那就只能自认倒霉了。

 

4.即使上面几条能够实现,那也不意味着阅读代码本身是多余的,因为说明文档不见得100%正确(各种文档定期会有一些勘误,这简直是家常便饭了),代码头部注释可能不是最新的(随代码变更修改注释绝对是一个好习惯,可惜坚持好习惯的人不多),函数名也可能是作者情急之下随意取的(看到aaafunc这样的函数名你还笑得出来吗?),所以要真正了解代码段的意义,从下面的第5条开始具体阅读代码吧。

 

5.对代码段分块是不错的主意,即完成特定功能的一组语句看做一个整体来阅读;至于如何分块,要具体问题具体分析,通常分支跳转、循环及其它函数调用或递归都是新的代码块的标志。例如经典的快速排序代码:

点击(此处)折叠或打开

  1. void QuickSort(RecType R[],int s,int t) /*对R[s]至R[t]的元素进行快速排序*/
  2. {
  3.     int i=s,j=t;
  4.     RecType tmp;
  5.     if (s<t)                 /*区间内至少存在两个元素的情况*/
  6.     {    
  7.         tmp=R[s];         /*用区间的第1个记录作为基准*/
  8.         while (i!=j)         /*从区间两端交替向中间扫描,直至i=j为止*/
  9.         {    
  10.             while (j>i && R[j].key>tmp.key)
  11.                 j--;         /*从右向左扫描,找第1个小于tmp.key的R[j]*/
  12.             R[i]=R[j];        /*找到这样的R[j],R[i] R[j]交换*/
  13.             while (i<j && R[i].key<tmp.key)
  14.                 i++;        /*从左向右扫描,找第1个大于tmp.key的记录R[i]*/
  15.             R[j]=R[i];        /*找到这样的R[i],R[i] R[j]交换*/
  16.         }
  17.         R[i]=tmp;
  18.         QuickSort(R,s,i-1);     /*对左区间递归排序*/
  19.         QuickSort(R,i+1,t);     /*对右区间递归排序*/
  20.     }
  21. }

显然前两行是变量定义,后面的if语句是函数主体。在if语句内部,一个while循环无疑自成一块,其前后的赋值无非就是为了元素交换,而后面的递归调用又是另一块;这样分块处理,不是清晰很多吗?

 

6.理解循环的意义,将其抽象出来。我们还以上面的快速排序代码为例,其中的while循环语句是蛮有意思的:

点击(此处)折叠或打开

  1. while (j>i && R[j].key>tmp.key)
  2.     j--; /*从右向左扫描,找第1个小于tmp.key的R[j]*/

显然,抽象出来的意思就是注释里面的内容,不过不经过仔细分析,这层意义是不容易想到的。

 

7.留意分支跳转,因为这些代码会导致下面某些语句不被执行,而这将在很大程度上左右代码段的功能。笔者曾有一位朋友写一段代码想要实现向一个文本里写数的功能,但不知为何程序有时好使有时崩溃,下面是它的代码段:

点击(此处)折叠或打开

  1. void WriteData()
  2. {
  3.     ...
  4.     FILE *fp;
  5.     int ready, a, b;
  6.     
  7.     scanf("%d", &ready);
  8.     if (ready == 1)
  9.     {
  10.         fp = fopen("data.txt", "w");
  11.     }
  12.     
  13.     ...
  14.     
  15.     fprintf(fp, "%d %d\n", a, b);
  16. }

相信有经验的程序员一眼就能看出问题所在,fp未被初始化,当ready不等于1时,fp仍然指向不明,此时调用fprintffp执行写操作是非法的!而这一切问题所在就是那个if判断,分支的跳转使得fopen不一定总会在调用fprintf之前执行,这就是程序时好时坏的原因。因此被类似问题困扰的读者不妨看看你的代码中那些分支跳转是否都处理得十分恰当。

 

8.最后的工作就用来搞定普通语句,不过有时这也是需要动些脑筋的。例如,下面的代码相信很多人都知道是用来交换两个数的:

点击(此处)折叠或打开

  1. void swap(int* a, int* b)
  2. {
  3.     int tmp = *a;
  4.     *a = *b;
  5.     *b = tmp;
  6. }

但你是否知道下面的两份代码也可以用来交换两个数呢:

点击(此处)折叠或打开

  1. void swap1(int* a, int* b)
  2. {
  3.     *a = *a + *b - (*b = *a);
  4. }

点击(此处)折叠或打开

  1. void swap2(int* a, int* b)
  2. {
  3.     *a ^= *b;
  4.     *b ^= *a;
  5.     *a ^= *b;
  6. }

上面的第一份代码利用了和差反转及运算符的结合顺序,而第二份代码则利用了位运算,至于为何成立留给读者自己思考。

 

好了,说了这么多,如果把阅读技术书籍看作程序员像一位音乐爱好者一样学习基本乐理知识的话,那么阅读代码就是鉴赏品味音乐家(也可能是音符堆砌家)的作品,这是由爱好者通往被爱好者的必由之路。不过俗语有云,实践出真知,想要真正快速地掌握一段代码的精髓,必须亲自上阵,多去品味精品代码,再加以理论辅助,方能将之变成自己的财富。这样日后无论做开发还是测试,你会发现,能够写出漂亮的代码或是找出别人代码的bug使之变得漂亮,是因为曾经读过并读懂漂亮的代码,抑或曾被不漂亮的代码深深刺痛并了解其症结所在。总之,阅读代码,其乐无穷!

阅读(1243) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~