代码阅读⽅法与实践总结
++++++++++++
第⼀章: 导论
++++++++++++
1.要养成⼀个习惯, 经常花时间阅读别⼈编写的⾼品质代码.
2.要有选择地阅读代码, 同时, 还要有⾃⼰的⽬标. 您是想学习新的模式|编码风格|还是满⾜某些需求的⽅法.
3.要注意并重视代码中特殊的⾮功能性需求, 这些需求也许会导致特殊的实现风格.
4.在现有的代码上⼯作时, 请与作者和维护⼈员进⾏必要的协调, 以避免重复劳动或产⽣厌恶情绪.
5.请将从开放源码软件中得到的益处看作是⼀项贷款, 尽可能地寻各种⽅式来回报开放源码社团.
6.多数情况下, 如果您想要了解"别⼈会如何完成这个功能呢?", 除了阅读代码以外, 没有更好的⽅法.
7.在寻bug时, 请从问题的表现形式到问题的根源来分析代码. 不要沿着不相关的路径(误⼊歧途).
8.我们要充分利⽤调试器|编译器给出的警告或输出的符号代码|系统调⽤跟踪器|数据库结构化查询语⾔的⽇志机制|包转储⼯具和Windows 的消息侦查程序, 定出的bug的位置.
9.对于那些⼤型且组织良好的系统, 您只需要最低限度地了解它的全部功能, 就能够对它做出修改.
10.当向系统中增加新功能时, ⾸先的任务就是到实现类似特性的代码, 将它作为待实现功能的模板.
11.从特性的功能描述到代码的实现, 可以按照字符串消息, 或使⽤关键词来搜索代码.
12.在移植代码或修改接⼝时, 您可以通过编译器直接定位出问题涉及的范围, 从⽽减少代码阅读的⼯作量.
13.进⾏重构时, 您从⼀个能够正常⼯作的系统开始做起, 希望确保结束时系统能够正常⼯作. ⼀套恰当的测试⽤例(test case)可以帮助您满⾜此项约束.
14.阅读代码寻重构机会时, 先从系统的构架开始, 然后逐步细化, 能够获得最⼤的效益.
15.代码的可重⽤性是⼀个很诱⼈, 但难以理解与分离, 可以试着寻粒度更⼤⼀些的包, 甚⾄其他代码.
16.在复查软件系统时, 要注意, 系统是由很多部分组成的, 不仅仅只是执⾏语句. 还要注意分析以下内容: ⽂件和⽬录结构|⽣成和配置过程|⽤户界⾯和系统的⽂档.
18.可以将软件复查作为⼀个学习|讲授|援之以⼿和接受帮助的机会.
++++++++++++++++++++
第⼆章: 基本编程元素
++++++++++++++++++++
19.第⼀次分析⼀个程序时, main是⼀个好的起始点.
20.层叠-else序列可以看作是由互斥选择项组成的选择结构.
21.有时, 要想了解程序在某⼀⽅⾯的功能, 运⾏它可能⽐阅读源代码更为恰当.
22.在分析重要的程序时, 最好⾸先识别出重要的组成部分.
23.了解局部的命名约定, 利⽤它们来猜测变量和函数的功能⽤途.
24.当基于猜测修改代码时, 您应该设计能够验证最初假设的过程. 这个过程可能包括⽤编译器进⾏检查|引⼊断⾔|或者执⾏适当的测试⽤例.
25.理解了代码的某⼀部分, 可能帮助你理解余下的代码.
26.解决困难的代码要从容易的部分⼊⼿.
27.要养成遇到库元素就去阅读相关⽂档的习惯; 这将会增强您阅读和编写代码的能⼒.
28.代码阅读有许多可选择的策略: ⾃底向上和⾃顶向下的分析|应⽤试探法和检查注释和外部⽂档, 应该依据问题的需要尝试所有这些⽅法.
29.for (i=0; i<n; i++)形式的循环执⾏n次; 其他任何形式都要⼩⼼.
30.涉及两项不等测试(其中⼀项包括相等条件)的⽐较表达式可以看作是区间成员测试.
31.我们经常可以将表达式应⽤在样本数据上, 借以了解它的含义.
32.使⽤De Morgan法则简化复杂的逻辑表达式.
33.在阅读逻辑乘表达式时, 问题可以认为正在分析的表达式以左的表达式均为true; 在阅读逻辑和表达式时, 类似地, 可以认为正在分析的表达式以左的表达式均为false.
34.重新组织您控制的代码, 使之更为易读.
35.将使⽤条件运⾏符? :的表达式理解为if代码.
36.不需要为了效率, 牺牲代码的易读性.
37.⾼效的算法和特殊的优化确实有可能使得代码更为复杂, 从⽽更难理解, 但这并不意味着使代码更为紧凑和不易读会提⾼它的效率.
38.创造性的代码布局可以⽤来提⾼代码的易读性.
39.我们可以使⽤空格|临时变量和括号提⾼表达式的易读性.
40.在阅读您所控制的代码时, 要养成添加注释的习惯.
41.我们可以⽤好的缩进以及对变量名称的明智选择, 提⾼编写⽋佳的程序的易读性.
42.⽤diff程序分析程序的修订历史时, 如果这段历史跨越了整体重新缩排, 常常可以通过指定-w选项, 让diff忽略空⽩差异, 避免由于更改了缩进层次⽽引⼊的噪⾳.
43.do循环的循环体⾄少执⾏⼀次.
44.执⾏算术运算时, 当b=2n-1时, 可以将a&b理解为a%(b+1).
45.将a<<n理解为a*k, k=2n.
46.将a>>n理解为a/k, k=2n.
47.每次只分析⼀个控制结构, 将它的内容看作是⼀个⿊盒.
48.将每个控制结构的控制表达式看作是它所包含代码的断⾔.
50.⽤复杂循环的变式和不变式, 对循环进⾏推理.
51.使⽤保持含义不变的变换重新安排代码, 简化代码的推理⼯作.
+++++++++++++++++++
第三章: ⾼级C数据类型
+++++++++++++++++++
52.了解特定语⾔构造所服务的功能之后, 就能够更好地理解使⽤它们的代码.
53.识别并归类使⽤指针的理由.
continue语句执行过程
54.在C程序中, 指针⼀般⽤来构造链式数据结构|动态分配的数据结构|实现引⽤调⽤|访问和迭代数据元素|传递数组参数|引⽤函数|作为其他
值的别名|代表字符串|以及直接访问系统内存.
55.以引⽤传递的参数可以⽤来返回函数的结果, 或者避免参数复制带来的开销.
56.指向数组元素地址的指针, 可以访问位于特定索引位置的元素.
57.指向数组元素的指针和相应的数组索引, 作⽤在⼆者上的运算具有相同的语义.
58.使⽤全局或static局部变量的函数⼤多数情况都不可重⼊(reentrant).
59.字符指针不同于字符数组.
60.识别和归类应⽤结构或共⽤体的每种理由.
61.C语⾔中的结构将多个数据元素集合在⼀起, 使得它们可以作为⼀个整体来使⽤, ⽤来从函数中返回多个数据元素|构造链式数据结构|映射数据在硬件设备|⽹络链接和存储介质上的组织⽅式|实现抽象数据类型|以及以⾯向对象的⽅式编程.
62.共⽤体在C程序中主要⽤于优化存储空间的利⽤|实现多态|以及访问数据不同的内部表达⽅式.
63.⼀个指针, 在初始化为指向N个元素的存储空间之后, 就可以作为N个元素的数组来使⽤.
64.动态分配的内在块可以电焊⼯地释放, 或在程序结束时释放, 或由垃圾回收器来完成回收; 在栈上分配的内存块当分配它的函数退出后释放.
65.C程序使⽤typedef声明促进抽象, 并增强代码的易读性, 从⽽防范可移植性问题, 并模拟C++和Java的类声明⾏为.
66.可以将typedef声明理解成变量定义: 变量的名称就是类型的名称; 变量的类型就是与该名称对应的类型.
+++++++++++++++
第四章: C数据结构
+++++++++++++++
67.根据底层的抽象数据类型理解显式的数据结构操作.
68.C语⾔中, ⼀般使⽤内建的数组类型实现向量, 不再对底层实现进⾏抽象.
69.N个元素的数组可以被序列for (i=0; i<N; i++)完全处理; 所有其他变体都应该引起警惕.
70.表达式sizeof(x)总会得到⽤memset或memcpy处理数组x(不是指针)所需的正确字节数.
71.区间⼀般⽤区间内的第⼀个元素和区间后的第⼀个元素来表⽰.
72.不对称区间中元素的数⽬等于⾼位边界与低位边界的差.
73.当不对称区间的⾼位边界等于低位边界时, 区间为空.
74.不对称区间中的低位边界代表区间的第⼀个元素; ⾼位边界代表区间外的第⼀个元素.
75.结构的数组常常表⽰由记录和字段组成的表.
76.指向结构的指针常常表⽰访问底层记录和字段的游标.
77.动态分配的矩阵⼀般存储为指向数组列的指针或指向元素指针的指针; 这两种类型都可以按照⼆维数组进⾏访问.
78.以数组形式存储的动态分配矩阵, ⽤⾃定义访问函数定位它们的元素.
79.抽象数据类型为底层实现元素的使⽤(或误⽤)⽅式提供⼀种信⼼的量度.
80.数组⽤从0开始的顺序整数为键, 组织查表.
81.数组经常⽤来对控制结构进⾏⾼效编码, 简化程序的逻辑.
82.通过在数组中每个位置存储⼀个数据元素和⼀个函数指针(指向处理数据元素的函数), 可以将代码与数据关联起来.
83.数组可以通过存储供程序内的抽象机(abstract machine)或虚拟机(virtual machine)使⽤的数据或代码, 控制程序的运作.
84.可以将表达式sizeof(x) / sizeof(x[0])理解为数组x中元素的个数.
85.如果结构中含有指向结构⾃⾝|名为next的元素, ⼀般说来, 该结构定义的是单向链表的结点.
86.指向链表结点的持久性(如全局|静态或在堆上分配)指针常常表⽰链表的头部.
87.包含指向⾃⾝的next和prev指针的结构可能是双向链表的结点.
88.理解复杂数据结构的指针操作可以将数据元素画为⽅框|指针画为箭头.
89.递归数据结构经常⽤递归算法来处理.
90.重要的数据结构操作算法⼀般⽤函数参数或模板参数来参数化.
91.图的结点常常顺序地存储在数组中, 链接到链表中, 或通过图的边链接起来.
92.图中的边⼀般不是隐式地通过指针, 就是显式地作为独⽴的结构来表⽰.
93.图的边经常存储为动态分配的数组或链表, 在这两种情况下, 边都锚定在图的结点上.
94.在⽆向图中, 表达数据时应该将所有的结点看作是等同的, 类似地, 进⾏处理任务的代码也不应该基于它们的⽅向来区分边.
95.在⾮连通图中, 执⾏遍历代码应该能够接通孤⽴的⼦图.
96.处理包含回路的图时, 遍历代码应该避免在处理图的回路进⼊循环.
97.复杂的图结构中, 可能隐藏着其他类型的独⽴结构.
+++++++++++++++++
第五章: ⾼级控制流程
+++++++++++++++++
98.采⽤递归定义的算法和数据结构经常⽤递归的函数定义来实现.
99.推理递归函数时, 要从基准落伍测试开始, 并认证每次递归调⽤如何逐渐接近⾮递归基准范例代码.
100.简单的语⾔常常使⽤⼀系列遵循该语⾔语法结构的函数进⾏语法分析.
101.推理互递归函数时, 要基于底层概念的递归定义.
102.尾递归调⽤等同于⼀个回到函数开始处的循环.
103.将throws⼦句从⽅法的定义中移除, 然后运⾏Java编译器对类的源代码进⾏编译, 就可以容易地到那些可能隐式地⽣成异常的⽅法. 104.在多处理器计算机上运⾏的代码常常围绕进程或线程进⾏组织.
105.⼯作并⾏模型⽤于在多个处理器间分配⼯作, 或者创建⼀个任务池, 然后将⼤量需要处理标准化的⼯作进⾏分配.
106.基于线程的管理者/⼯⼈并⾏模型⼀般将耗时的或阻塞的操作分配给⼯⼈⼦任务, 从⽽维护中⼼任务的响应性.
107.基于进程的管理者/⼯⼈并⾏模型⼀般⽤来重⽤现有的程序, 或⽤定义良好的接⼝组织和分离粗粒度的系统模块.
108.基于流⽔线的并⾏处理中, 每个任务都接收到⼀些输⼊, 对它们进⾏⼀些处理, 并将⽣成的输出传递给下⼀个任务, 进⾏不同的处理. 109.竞争条件很难捉摸, 相关的代码常常会将竞争条件扩散到多个函数或模块; 因⽽, 很难隔离由于竞争条件导致的问题.
110.对于出现在信号处理器中的数据结构操作代码和库调⽤要保持⾼度警惕.
111.在阅读包含宏的代码时, 要注意, 宏既⾮函数, 也⾮语句.
112.do…while(0)块中的宏等同于控制块中的语句.
113.宏可以访问在它的使⽤点可见的所有局部变量.
114.宏调⽤可改变参数的值
115.基于宏的标记拼接能够创建新的标记符.
+++++++++++++++++
第六章: 应对⼤型项⽬
+++++++++++++++++
116.我们可以通过浏览项⽬的源代码树—包含项⽬源代码的层次⽬录结构, 来分析⼀个项⽬的组织⽅式. 源码树常常能够反映出项⽬在构架和软件过程上的结构.
117.应⽤程序的源代码树经常是该应⽤程序的部署结构的镜像.
118.不要被庞⼤的源代码集合吓倒; 它们⼀般⽐⼩型的专门项⽬组织得更出⾊.
119.当您⾸次接触⼀个⼤型项⽬时, 要花⼀些时间来熟悉项⽬的⽬录树结构.
120.项⽬的源代码远不只是编译后可以获得可执⾏程序的计算机语⾔指令; ⼀个项⽬的源码树⼀般还包括规格说明|最终⽤户和开发⼈员⽂档|测试脚本|多媒体资源|编译⼯具|例⼦|本地化⽂件|修订历史|安装过程和许可信息.
121.⼤型项⽬的编译过程⼀般声明性地借助依赖关系来说明. 依赖关系由⼯具程序, 如make及其派⽣程序, 转换成具体的编译⾏动.
122.⼤型项⽬中, 制作⽂件常常由配置步骤动态地⽣成; 在分析制作⽂件之前, 需要先执⾏项⽬特定的配置.
123.检查⼤型编译过程的各个步骤时, 可以使⽤make程序的-n开关进⾏预演.
124.修订控制系统提供从储存库中获取源代码最新版本的⽅式.
125.可以使⽤相关的命令, 显⽰可执⾏⽂件中的修订标识关键字, 从⽽将可执⾏⽂件与它的源代码匹配起来.
126.使⽤修订⽇志中出现的bug跟踪系统内的编号, 可以在bug跟踪系统的数据库中到有关的问题的说明.
127.可以使⽤修订控制系统的版本储存库, 出特定的变更是如何实现的.
128.定制编译⼯具⽤在软件开发过程的许多⽅⾯, 包括配置|编译过程管理|代码的⽣成|测试和⽂档编制.
129.程序的调试输出可以帮助我们理解程序控制流程和数据元素的关键部分.
130.跟踪语句所在的地点⼀般也是算法运⾏的重要部分.
131.可以⽤断⾔来检验算法运作的步骤|函数接收的参数|程序的控制流程|底层硬件的属性和测试⽤例的结果.
132.可以使⽤对算法进⾏检验的断⾔来证实您对算法运作的理解, 或将它作为推理的起点.
133.对函数参数和结果的断⾔经常记录了函数的前置条件和后置条件.
134.我们可以将测试整个函数的断⾔作为每个给定函数的规格说明.
135.测试⽤例可以部分地代替函数规格说明.
136.可以使⽤测试⽤例的输⼊数据对源代码序列进⾏预演.
+++++++++++++++++++
第七章: 编码规范和约定
+++++++++++++++++++
137.了解了给定代码库所遵循的⽂件组织⽅式后, 就能更有效率地浏览它的源代码.
138.阅读代码时, ⾸先要确保您的编辑器或优美打印程序的tab设置, 与代码遵循的风格规范⼀致.
139.可以使⽤代码块的缩进, 快速地掌握代码的总体结构.
140.对编排不⼀致的代码, 应该⽴即给予⾜够的警惕.
141.分析代码时, 对标记为XXX, FIXME和TODO的代码序列要格外注意: 错误可能就潜伏在其中.

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。