怎样阅读源码
第一章: 导论
++++++++++++
1.要养成一个习惯, 经常花时间阅读别人编写的高品质代码.
2.要有选择地阅读代码, 同一时候, 还要有自己的目标. 您是想学习新的模式|编码风格|还是满足某些需求的方法.
3.要注意并重视代码中特殊的非功能性需求, 这些需求或许会导致特殊的实现风格.
4.在现有的代码上工作时, 请与作者和维护人员进行必要的协调, 以避免反复劳动或产生厌恶情绪.
5.请将从开放源码软件中得到的益处看作是一项贷款, 尽可能地寻找各种方式来回报开放源码社团.
6.多数情况下, 假设您想要了解”别人会怎样完毕这个功能呢?”, 除了阅读代码以外, 没有更好的方法.
7.在寻找bug时, 请从问题的表现形式到问题的根源来分析代码. 不要沿着不相关的路径(误入歧途).
8.我们要充分利用调试器|编译器给出的警告或输出的符号代码|系统调用跟踪器|数据库结构化查询语言的日志机制|包转储工具和Windows的消息侦查程序,定出的bug的位置.
9.对于那些大型且组织良好的系统, 您仅仅须要最低限度地了解它的全部功能, 就能够对它做出改动.
10.当向系统中添加新功能时, 首先的任务就是找到实现相似特性的代码, 将它作为待实现功能的模板.
11.从特性的功能描写叙述到代码的实现, 能够依照字符串消息, 或使用关键词来搜索代码.
12.在移植代码或改动接口时, 您能够通过编译器直接定位出问题涉及的范围, 从而降低代码阅读的工作量.
13.进行重构时, 您从一个能够正常工作的系统開始做起, 希望确保结束时系统能够正常工作. 一套恰当的測试用例(testcase)能够帮助您满足此项约束.
14.阅读代码寻找重构机会时, 先从系统的构架開始, 然后逐步细化, 能够获得最大的效益.
15.代码的可重用性是一个非常诱人, 但难以理解与分离, 能够试着寻找粒度更大一些的包, 甚至其它代码.
16.在复查软件系统时, 要注意, 系统是由非常多部分组成的, 不仅仅仅仅是运行语句. 还要注意分析以下内容:文件和文件夹结构|生成和配置过程|用户界面和系统的文档.
18.能够将软件复查作为一个学习|讲授|援之以手和接受帮助的机会.
++++++++++++++++++++
第二章: 基本编程元素
++++++++++++++++++++
19.第一次分析一个程序时, main是一个好的起始点.
20.层叠if-else if-…-else序列能够看作是由相互排斥选择项组成的选择结构.
21.有时, 要想了解程序在某一方面的功能, 运行它可能比阅读源码更为恰当.
22.在分析重要的程序时, 最好首先识别出重要的组成部分.
23.了解局部的命名约定, 利用它们来推測变量和函数的功能用途.
24.当基于推測改动代码时, 您应该设计能够验证最初假设的过程. 这个过程可能包括用编译器进行检查|引入断言|或者运行适当的測试用例.
25.理解了代码的某一部分, 可能帮助你理解余下的代码.
26.解决困难的代码要从easy的部分入手.
27.要养成遇到库元素就去阅读相关文档的习惯; 这将会增强您阅读和编写代码的能力.
28.代码阅读有很多可选择的策略: 自底向上和自顶向下的分析|应用试探法和检查凝视和外部文档, 应该依据问题的须要尝试全部这些方法.
29.for (i=0; i
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.将每一个控制结构的控制表达式看作是它所包括代码的断言.
49.return, goto, break和continue语句, 还有异常, 都会影响结构化的运行流程.因为这些语句一般都会终止或又一次開始正在进行的循环,因此要单独推理它们的行为.
50.用复杂循环的变式和不变式, 对循环进行推理.
51.使用保持含义不变的变换又一次安排代码, 简化代码的推理工作.
+++++++++++++++++++
第三章: 高级C数据类型
+++++++++++++++++++
52.了解特定语言构造所服务的功能之后, 就能够更好地理解使用它们的代码.
53.识别并归类使用指针的理由.
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
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编译器对类的源码进行编译, 就能够easy地找到那些可能隐式地生成异常的方法.
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.能够使用相关的命令, 显示可运行文件里的修订标识keyword, 从而将可运行文件与它的源码匹配起来.
126.使用修订日志中出现的bug跟踪系统内的编号, 能够在bug跟踪系统的数据库中找到有关的问题的说明.
127.能够使用修订控制系统的版本号储存库, 找出特定的变更是怎样实现的.
128.定制编译工具用在软件开发过程的很多方面, 包括配置|编译过程管理|代码的生成|測试和文档编制.
129.程序的调试输出能够帮助我们理解程序控制流程和数据元素的关键部分.
130.跟踪语句所在的地点一般也是算法运行的重要部分.
131.能够用断言来检验算法运作的步骤|函数接收的參数|程序的控制流程|底层硬件的属性和測试用例的结果.
132.能够使用对算法进行检验的断言来证实您对算法运作的理解, 或将它作为推理的起点.
133.对函数參数和结果的断言经常记录了函数的前置条件和后置条件.
134.我们能够将測试整个函数的断言作为每一个给定函数的规格说明.
135.測试用例能够部分地取代函数规格说明.
136.能够使用測试用例的输入数据对源码序列进行预演.
+++++++++++++++++++
第七章: 编码规范和约定
+++++++++++++++++++
137.了解了给定代码库所遵循的文件组织方式后, 就能更有效率地浏览它的源码.
138.阅读代码时, 首先要确保您的编辑器或优美打印程序的tab设置, 与代码遵循的风格规范一致.
139.能够使用代码块的缩进, 高速地掌握代码的总体结构.
140.对编排不一致的代码, 应该马上给予足够的警惕.
141.分析代码时, 对标记为XXX, FIXME和TODO的代码序列要格外注意: 错误可能就潜伏在当中.
142.常量使用大写字母命名, 单词用下划线分隔.
143.在遵循Java编码规范的程序中, 包名(package name)总是从一个顶级的域名開始(比如, org, com),类名和接口名由大写字母開始, 方法和变量名由小写字母開始.
144.用户界面控件名称之前的匈牙利记法的前缀类型标记能够帮助我们确定它的作用.
145.不同的编程规范对可移植构造的构成有不同的主张.
146.在审查代码的可移植性, 或以某种给定的编码规范作为指南时, 要注意了解规范对可移植性需求的界定与限制.
147.假设GUI功能都使用对应的编程结构来实现, 则通过代码审查能够轻易地验证给定用户界面的规格说明是否被正确地採用.
148.了解项目编译过程的组织方式与自己主动化方式之后, 我们就能够高速地阅读与理解对应的编译规则.
149.当检查系统的公布过程时, 经常能够将对应发行格式的需求作为基准.
++++++++++++
第八章: 文档
++++++++++++
150.阅读代码时, 应该尽可能地利用不论什么能够得到的文档.
151.阅读一小时代码所得到的信息仅仅只是相当于阅读一分钟文档.
152.使用系统的规格说明文档, 了解所阅读代码的运行环境.
153.软件需求规格说明是阅读和评估代码的基准.
154.能够将系统的设计规格说明作为认知代码结构的路线图, 阅读详细代码的指引.
155.測试规格说明文档为我们提供能够用来对代码进行预演的数据.
156.在接触一个未知系统时, 功能性的描写叙述和用户指南能够提供重要的背景信息,从而更好地理解阅读的代码所处的上下文.
157.从用户參考手冊中, 我们能够高速地获取, 应用程序在外观与逻辑上的背景知识,
从管理员手冊中能够得知代码的接口|文件格式和错误消息的详细信息.
158.利用文档能够快捷地获取系统的概况, 了解提供特定特性的代码.
159.文档经常能够反映和提示出系统的底层结构.
160.文档有助于理解复杂的算法和数据结构.
161.算法的文字描写叙述能够使不透明(晦涩, 难以理解)的代码变得能够理解.
162.文档经常能够阐明源码中标识符的含义.
163.文档能够提供非功能性需求背后的理论基础.
164.文档还会说明内部编程接口.
165.因为文档非常少像实际的程序代码那样进行測试, 并受人关注, 所以它经常可能存在错误|不完整或过时.
166.文档也提供測试用例, 以及实际应用的样例.
167.文档经常还会包括已知的实现问题或bug.
168.环境中已知的缺点一般都会记录在源码中.
169.文档的变更能够标出那些故障点.
170.对同一段源码反复或互相冲突的更改, 经常表示存在根本性的设计缺陷, 从而使得维护人员须要用一系列的修补程序来修复.
171.相似的修复应用到源码的不同部分, 经常表示一种易犯的错误或疏忽, 它们相同可能会在其它地方存在.
172.文档经常会提供不恰当的信息, 误导我们对源码的理解.
173.要警惕那些未归档的特性: 将每一个实例归类为合理|疏忽或有害, 对应地决定是否应该修复代码或文档.
174.有时, 文档在描写叙述系统时, 并不是依照已完毕的实现, 而是系统应该的样子或将来的实现.
175.在源码文档中, 单词gork的意思通常是指”理解”.
176.假设未知的或特殊使用方法的单词阻碍了对代码的理解, 能够试着在文档的术语表(假设存在的话)|New Hacker’sDictionary[Ray96]|或在Web搜索引擎中查找它们.
177.总是要以批判的态度来看待文档, 注意非传统的来源,比方凝视|标准|出版物|測试用例|邮件列表|新闻组|修订日志|问题跟踪数据库|营销材料|源码本身.
178.总是要以批判的态度来看待文档; 因为文档永远不会运行, 对文档的測试和正式复查也非常少达到对代码的相同水平, 所以文档经常会误导读者,或者全然错误.
179.对于那些有缺陷的代码, 我们能够从中判断出它的真实意图.
180.在阅读大型系统的文档时, 首先要熟悉文档的总体结构和约定.
181.在对付体积庞大的文档时, 能够使用工具, 或将文本输出到高品质输出设备上, 比方激光打印机, 来提高阅读的效率.
++++++++++++++
第九章: 系统构架
++++++++++++++
182.一个系统能够(在重大的系统中也确实如此)同一时候出多种不同的构架类型. 以不同的方式检查同一系统|分析系统的不同部分|或使用不同级别的分解,都有可能发现不同的构架类型.
183.协同式的应用程序, 或者须要协同訪问共享信息或资源的半自治进程, 通常会採用集中式储存库构架.
184.黑板系统使用集中式的储存库, 存储非结构化的键/值对, 作为大量不同代码元件之间的通信集线器.
185.当处理过程能够建模|设计和实现成一系列的数据变换时, 经常会使用数据流(或管道—过滤器)构架.
186.在批量进行自己主动数据处理的环境中, 经常会採用数据流构架, 在对数据工具提供大量支持的平台上尤其如此.
187.数据流构架的一个明显征兆是: 程序中使用暂时文件或流水线(pipeline)在不同进程间进行通信.
188.使用图示来建模面向对象构架中类的关系.
189.能够将源码输入到建模工具中, 逆向推导出系统的构架.
190.拥有大量同级子系统的系统, 经常依照分层构架进行组织.
191.分层构架一般通过堆叠拥有标准化接口的软件组件来实现.
192.系统中每一个层能够将以下的层看作抽象实体, 而且(仅仅要该层满足它的需求说明)不关心上面的层怎样使用它.
193.层的接口既能够是支持特定概念的互补函数族, 也能够是一系列支持同一抽象接口不同底层实现的可互换函数.
194.用C语言实现的系统, 经常常使用函数指针的数组, 表达层接口的多路复用操作.
195.用面向对象的语言实现的系统, 使用虚方法调用直接表达对层接口的多嘴复用操作.
196.系统能够使用不同的|独特的层次分解模型跨各种坐标轴进行组织.
197.使用程序切片技术, 能够将程序中的数据和控制之间依赖关系集中到一起.
198.在并发系统中, 一个单独的系统组件起到集中式管理器的作用, 负责启动|停止和协调其它系统进程和任务的运行.
199.很多现实的系统都会博採众家之长. 当处理此类系统时, 不要徒劳地寻找无所不包的构架图; 应该将不同构架风格作为独立但相关的实体来进行定位|识别并了解.
200.状态变迁图经常有助于理清状态机的动作.
201.在处理大量的代码时, 了解将代码分解成单独单元的机制极为重要.
202.大多数情况下, 模块的物理边界是单个文件|组织到一个文件夹中的多个文件或拥有统一前缀的文件的集合.
203.C中的模块, 由提供模块公开接口的头文件和提供对应实现的源文件组成.
204.对象的构造函数经经常使用来分配与对象相关的资源, 并初始化对象的状态. 函数一般用来释放对象在生命期中占用的资源.
205.对象方法经常使用类字段来存储控制全部方法运作的数据(比方查找表或字典)或维护类运作的状态信息(比如, 赋给每一个对象一个标识符的计数器).
206.在设计良好的类中, 全部的字段都应在声明为private, 并用公开的訪问方法提供对它们的訪问.
207.在遇到friend声明时, 要停下来分析一下, 看看绕过类封装在设计上的理由.
208.能够有克制地用运算符增强特定类的可用性, 但用运算符重载, 将类实现为拥有内建算术类型相关的全部功能的类实体, 是不恰当的.
209.泛型实现不是在编译期间通过宏替换或语言所支持的功能(比方C++模板和Ada的泛型包)来实现,就是在运行期间通过使用数据元素的指针和函数的指针|或对象的多态性实现.
210.抽象数据类型经经常使用来封装经常使用的数据组织方案(比方树|列表或栈), 或者对用户隐藏数据类型的实现细节.
211.使用库的目的多种多样: 重用源码或目标代码, 组织模块集合, 组织和优化编译过程, 或是用来实现应用程序各种特性的按需加载.
212.大型的|分布式的系统经常实现为很多互相协作的进程.
213.对于基于文本的数据储存库, 能够通过浏览存储在当中的数据, 破译出它的结构.
214.能够通过查询数据字典中的表, 或使用数据库专有的SQL命令, 比方show table, 来分析关系型数据库的模式.
215.识别出重用的构架元素后, 能够查找其最初的描写叙述, 了解正确地使用这样的构架的方式, 以及可能出现的误用.
216.要详细分析建立在某种框架之上的应用程序, 行动的最佳路线就是从研究框架自身開始.
217.在阅读向导生成的代码时, 不要期望太高, 否则您会感到失望.
218.学习几个主要的设计模式之后, 您会发现, 您查看代码构架的方式会发生改变: 您的视野和词汇将会扩展到能够识别和描写叙述很多通用的形式.
219.频繁使用的一些模式, 但并不显式地指出它们的名称, 这是因为构架性设计的重用经常先于模式的形成.
220.请试着依照底层模式来理解构架, 即使代码中并没有明白地提及模式.
221.大多数解释器都遵循相似的处理构架, 环绕一个状态机进行构建, 状态机的操作依赖于解释器的当前状态|程序指令和程序状态.
222.多数情况下, 參考构架仅仅是为应用程序域指定一种概念性的结构, 详细的实现并不是必须遵照这样的结构.
+++++++++++++++++
第十章: 代码阅读工具
+++++++++++++++++
223.词汇工具能够高效地在一个大代码文件里或者跨多个文件查找某种模式.
224.使用程序编辑器和正則表達式查找命令, 浏览庞大的源码文件.
225.以仅仅读方式浏览源码文件.
226.使用正則表達式 ^function name 能够找出函数的定义.
227.使用正則表達式的字符类, 能够查找名称遵循特定模式的变量.
228.使用正則表達式的否定字符类, 能够避免非积极匹配.
229.使用正則表達式 symbol-1. *symbol-2, 能够查找出如今同一行的符号.
230.使用编辑器的 tags 功能, 能够高速地找出实体的定义.
231.能够用特定的 tag 创建工具, 添加编辑器的浏览功能.
232.使用编辑器的大纲视图, 能够获得源码结构的俯视图.
233.使用您的编辑器来检測源码中圆括号|方括号和花括号的匹配.
234.使用 grep 跨多个文件查找代码模式.
235.使用 grep 定位符号的声明|定义和应用.
236.当您不能精确地表述要查找的内容时, 请使用关键单词的词干对程序的源码进行查找.
237.用 grep 过滤其它工具生成的输出, 分离出您要查找的项.
238.将 grep 的输出输送到其它工具, 使复杂处理任务自己主动化.
239.通过对 grep 的输出进行流编辑, 重用代码查找的结果.
240.通过选取与噪音模式不匹配的输出行(grep-v), 过滤虚假的 grep 输出.
241.使用 fgrep 在源码中查找字符串列表.
242.查找凝视, 或标识符大写和小写不敏感的语言编写的代码时, 要使用大写和小写不敏感的模式匹配(grep -i).
243.使用 grep –n 命令行开关, 能够创建与给定正則表達式匹配的文件和行号的检查表.
244.能够使用 diff 比較文件或程序不同版本号之间的区别.
245.在运行 diff 命令时, 能够使用 diff –b, 使文件比較算法忽略结尾的空格, 用 –w 忽略全部空白区域的差异, 用 –i使文件比較对大写和小写不敏感.
246.不要对创建自己的代码阅读工具心存畏惧.
247.在构建自己的代码阅读工具时: 要充分利用现代高速原型语言所提供的能力; 从简单開始, 依据须要逐渐改进; 使用利用代码词汇结构的各种试探法;要同意一些输出噪音或寂静(无关输出或缺失输出); 使用其它工具对输入进行预处理, 或者对输出进行后期处理.
248.要使编译器成为您的: 指定恰当级别的编译器警告, 并小心地评估生成的结果.
249.使用C预处理器理清那些滥用预处理器特性的程序.
250.要彻底地了解编译器怎样处理特定的代码块, 须要查看生成的符号(汇编)代码.
251.通过分析对应目标文件里的符号, 能够清晰地了解源文件的输入和输出.
252.使用源码浏览器浏览大型的代码集合以及对象类型.
253.要抵制住依照您的编码规范对外部代码进行美化的诱惑; 不必要的编排更改会创建不同的代码, 并妨碍工作的组织.
254.优美打印程序和编辑器语法着色能够使得程序的源码为易读.
255.cdecl 程序能够将难以理解的C和C++类型声明转换成纯英语(反之亦然).
256.实际运行程序, 往往能够更深刻地理解程序的动作.
257.系统调用|事件和数据包跟踪程序能够增进对程序动作的理解.
258.运行剖析器能够找出须要着重优化的代码, 验证输入数据的覆盖性, 以及分析算法的动作.
259.通过检查从未运行的代码行, 能够找出測试覆盖的弱点, 并据此修正測试数据.
260.要探究程序动态动作时的每一个细节, 须要在调试器中运作它.
261.将您觉得难以理解的代码打印到纸上.
262.能够绘制图示来描绘代码的动作.
263.能够试着向别人介绍您在阅读的代码, 这样做通常会增进您对代码的理解.
264.理解复杂的算法或巧妙的数据结构, 要选择一个安静的环境, 然后全神贯注地考虑, 不要借助于不论什么计算机化或自己主动化的帮助.
+++++++++++++++++++++
第十一章: 一个完整的样例
+++++++++++++++++++++
265.模仿软件的功能时, 要依照相似实体的线路(类|函数|模块). 在相似的现有实体中, 为简化对源码库的文本查找, 应选取比較罕见的名称.
266.自己主动生成的文件经常会在文件的开关有一段凝视, 说明这样的情况.
267.假设试图精确地分析代码, 通常会陷入数量众多的类|文件和模块中, 这些内容会非常快将我们淹没; 因此,我们必须将须要理解的代码限定在绝对必需的范围之内.
268.採用一种广度优先查找策略, 从多方攻克代码阅读中存在的问题, 进到找出克服它们的方法为止.