第三本书我选择了代码阅读方法与实践,说实话,觉得三本书里面最好的就是这一本书了,每一段话,每一段代码打偶让我受益匪浅。下面是我的收获:
1.1为什么以及如何阅读代码 
将代码作为文献;要养成一个习惯,经常花时间阅读别人编写的高品质代码。就像阅读高品质的散文能够丰富词汇、激发想象力、扩展思维一样,分析设计良好的软件系统的内部结构可以学到新的构架模式、数据结构、编码方法、算法、风格和文档规范、应用程序编程接口(API),甚至新的计算机语言。阅读高品质的代码还可以提高您编写代码的水准。 
通过下面的这些征兆,可以容易地识别出低品质的代码: 
编程风格不一致;结构不必要的复杂或难以理解;明星的逻辑错误或疏忽;过度使用不可移植的构造;缺乏维护。 
要有选择的阅读代码,同时,还要有自己的目标。 
模式pattern编码风格coding style 设计模式design pattern 
要注意并重视代码中特殊的非功能性需求,这些需求也许会导致特定的实现风格。对可移植性、时间和空间效率、易读性、甚至迷惑性的需求都可能导致代码具有非常特殊的特征。 
Slicing 切片 
关于维护:从问题的表现形式到问题的根源来分析代码。不要沿着不相干的路径误入歧途。编译程序时请加入调试支持,并使用调试器的栈跟踪机制、单步执行、以及数据和代码断点来缩小搜索的范围。 
关于演进:关键的思想是对所分析的代码的范围有所选择;大多数情况下,实际需要理解的代码只是系统全部实现的很小一部分。在实际工作中,通过选择性地了解与更改一两个文件,就能够修改一个具有上百万行代码的系统(如典型内核或窗口系统);我强烈建议,读者应尽可能地亲身体会一下这种操作的成功所带来的令人愉悦的感受。在有选择地处理大型系统的各个组成部分时,应该采用的策略概括如下:定位到感兴趣的代码;单独了解各个特定的部分;推断节选出的代码与其余代码的关系。 
当向系统中增加新功能时,首先的任务就是找到实现类似特性的代码,将它作为待实现功能的模板。相似的,当修改一个现存的特性时,首先需要定位底层的代码。从特性的功能描述定位到代码的实现,可以按照字符串消息,或使用关键字来搜索代码。 
定位到该特性后,就可以开始研究它的实现(跟踪所有相关的代码)、设计新的特性或增加新的功能,以及定位可能影响到的区域――代码中与新代码发生交互的其他部分。在大多数情况下,您只需要彻底了解这些代码就足够了。 
阅读代码寻找重构机会时,先从系统的架构开始,然后逐步细化,能够获得最大的效益。 
关于重用:不要期望太高。代码的可重用性是一个很诱人,但难以掌握的思想;降低期望就不会感到失望。编写可重用的软件可能会增加50%的开发工作量。 
关于审查: 
1.2 如何阅读这本书 
第二章   基本编程元素 
我们所观测到的不是自然本身,而是大自然在我们所用的观察方法下展现出来的特性。
代码阅读过程中可以使用的武器之一就是:用编译器对代码进行编译,检查产生的警告消息。 
标准c、c++、Java程序从函数(Java中为方法)main开始执行。第一次分析一个程序时,main是一个好的起点。要注意,一些其他系统可能使用其他函数作为程序的入口点,例如WinMain或init。 
在C/C++程序中,main函数的两个参数(通常命名为argc和argv用来将指定的命令行参数从操作系统传递到程序。Argc变量存储程序参数的个数,argv是一个字符串数组,包含所有的实参(包括程序自身的名称――位置0,即argv的第一个元素)。Argv数组由一个NULL元素结束。 
在比较两个字符串的相等性时,函数strcmp的返回值很不直观。当字符串相等时,它返回0,即C语言中的false。为此,很多C程序中会定义一个STREQ宏,使之在两个字符串相等时返回true,在这个宏中,常常通过立即比较前两个字符来优化比较过程。 
#define STREQ(a,b) (*(a)==*(b)&&strcmp((a),(b))==0) 
If-else if-…-else序列可以看作是由互斥选择项组成的选择结构。 
使用未经初始化的变量是引发问题的一个常见原因。在检查代码时,一定要核实:是否所有的程序控制路径在使用变量前都恰当地对它们进行了初始化。一些编译器可以检测出部分此类错误,但是不应该依赖于这项功能。 
在用一个新行结束了它的输出后,echo调用exit结束程序,用(0)表示成功。我们还会经常看到在main函数中返回0,这两种做法的结果相同。 
2.2函数和全局变量 
// expand程序将参数指定的文件(如果没有指定文件参数,则为标准输入)中硬制表符(\t ASCII字符9)展开成若干空格。默认的行为是每8个字符设置一个制表位; 
在分析重要的程序时,最好首先识别出重要的组成部分。 
函数前置声明:允许编译器检查传递给函数的参数,以及它们的返回值,相应地生成正确的代码。如果没有给出前置声明(forward declaration),C编译器将根据函数第一次使用时的情况对函数的返回值类型和参数作出假定;C++编译器将这种情况标记为错误。如果之后的函数定义与这些假定不相符,编译器将发出一条警告或错误消息。但是,如果定义于另一个文件中的一个函数满足这个错误的声明,则程序也许能够编译通过,但在运行时会失败。 
// expand程序中,两个函数被声明为static,而变量并非static。这意味着,这两个函数只在该文件中可见,而变量则对组成程序的所有文件都是可见的。因此,在检查代码时,一个好的做法是确保所有只用于单一文件的变量都声明为static。(C) 
要了解函数(或方法)的功用,可以使用下面的策略: 
猜,基于函数名;阅读位于函数开始部分的注释;分析如何使用该函数;阅读函数体的代码;查阅外部的程序文档。 
当基于猜测修改代码时,您应该设计能够验证最初设计的过程。这个过程可能包括用编译器进行检查、引入断言、或者执行适当的测试用例。 
2.3while循环、条件和块 
要养成遇到库元素就去阅读相关文档的习惯,这将增强我们阅读和编写代码的能力。 
2.4switch语句 
即使switch语句只处理一系列确定的值,也要尽量包括default标记,这是一个好的保护性编程习惯。这类default标记能够捕获产生意外值的编程错误,并能警告程序的维护人员。 
2.5for循环 
无限循环:for(;;)。大多数情况下,无限循环用来表达在循环开始或结束时退出条件无法指定的循环。大这些循环一般不是通过return语句退出函数,就是通过break语句退出循环体,或者调用exit或类似的函数退出整个程序。C++,C#和Java程序还可以通过异常跳出这类循环。 
2.6break和continue语句
Break语句将程序转移到最内层的循环或switch语句之后执行。大多数情况下,break用于提前退出循环。Continue语句则跳过该语句到循环末尾之间的语句,继续最内层循环的迭代。Continue语句会再次计算while条件表达式的值,并执行循环。在for循环中,该语句将首先计算第三表达式的值,之后是条件表达式。Continue用在循环体分开处理不同情况的地方;每种情况一般都以continue结束,以便进行下一次循环迭代。
2.7字符和布尔型表达式
多数现代语言中,布尔表达式只对需要的部分进行求值。在用&&运算符(逻辑与)连接起来的表达式序列中,第一个表达式的求值结果如果为false,则会结束整个表达式的求值,并生成false结果。类似的,在用||运算符(逻辑或)连接起来的表达式序列中,如果第一个表达式求值为true,则会终止对整个表达式的求值,产生一个true结果。很多表达式都基于这种短路求值(short-circuit evaluation)特性,在阅读时也应该采用同样的方式。
大多数从C派生的语言,都有短路求值的特性,比如C+=,Perl和Java。
2.8 goto语句
Goto语句应尽量少用。
执行某些行动后(比如打印一条错误消息,或释放分配的资源),常常用goto语句退出程序或函数。有时用做公共的错误处理器。程序的正常退出路径位于错误处理器之前,从而保证当没有错误发生时,处理器不会被调用。
Goto语句还经常用来重新执行某一段代码,可能是在某些变量的值发生改变,或者执行完某些处理之后。虽然这样的构造经常可以用结构化循环语句结合break和continue来完成,但实践中,使用goto有时能够更好地传达编码者的意图。一个单独的标记――几乎总是命名为again或retry,用做goto的目的地。
在嵌套循环和switch语句中,goto语句可以用来替代break和continue――这两个语句只影响最内层循环的控制流程,改变程序的控制流程。在大型、复杂的循环中使用这种做法可以澄清控制流程的走向,同时避免了向嵌套循环中添加特定break或continue语句引发错误的可能性。
2.9小范围重构
在阅读自己所控制的代码时,要养成添加注释的习惯。
2.10 do循环和整型表达式
执行算术运算时,当b=2n-1时,可以将a&b理解为a%(b+1)。这样书写表达式是为了将除法替换为逐位与指令(有时计算起来更高效)。但现代处理器上,这种差别已经很小了,我们要学会阅读使用这些小技巧,但要避免使用它。
还有两种情况,会用位指令来替代算术指令。它们是移位(shift)运算<<和>>――将整数的位向左或向右移动,由于整数每个二进制位的值均为2的幂,移位一个整数就等同于用2的幂去乘或除该整数,幂的次数就是移位的数目。因此,我们可以从算术意义来思考移位运算符。
2.11再论控制结构
第一个要记住的:每次只分析一个控制结构,将它的内容看作是一个黑盒。
第二条规则:将每个控制结构的控制表达式看作是它所包含代码的断言。

第3章   高级C数据类型

人们在设计完全傻瓜式的系统时,常犯的错误就是低估了十足傻瓜的能力。

                                                           ――Douglas Adarns

一般将malloc封装到执行检查malloc返回值的函数中(一般命名为xmalloc),在需要调用malloc的地方用这个函数来替代。

如果需要更多的空间,则调用C库函数realloc,将第一个参数指向的空间,调整为由函数的第二个参数指定的新大小。该函数返回一个指向调整后内存块的指针,它的地址可能和最初块的地址并不相同。原内存块中的内容被复制到新的位置。

posted on 2015-12-07 09:45  Tilefish  阅读(274)  评论(0编辑  收藏  举报