如何阅读别人的代码(二)
+++++++++++++++++++
第七章: 编码规范和约定
+++++++++++++++++++
137.了解了给定代码库所遵循的文件组织方式后, 就能更有效率地浏览它的源代码.
138.阅读代码时, 首先要确保您的编辑器或优美打印程序的tab设置, 与代码遵循的风格规范一致.
139.可以使用代码块的缩进, 快速地掌握代码的总体结构.
140.对编排不一致的代码, 应该立即给予足够的警惕.
141.分析代码时, 对标记为XXX, FIXME和TODO的代码序列要格外注意: 错误可能就潜伏在其中.
142.常量使用大写字母命名, 单词用下划线分隔.
143.在遵循Java编码规范的程序中, 包名(package name)总是从一个顶级的域名开始(例如, org, com), 类名和接口名由大写字母开始, 方法
和变量名由小写字母开始.
144.用户界面控件名称之前的匈牙利记法的前缀类型标记可以帮助我们确定它的作用.
145.不同的编程规范对可移植构造的构成有不同的主张.
146.在审查代码的可移植性, 或以某种给定的编码规范作为指南时, 要注意了解规范对可移植性需求的界定与限制.
147.如果GUI功能都使用相应的编程结构来实现, 则通过代码审查可以轻易地验证给定用户界面的规格说明是否被正确地采用.
148.了解项目编译过程的组织方式与自动化方式之后, 我们就能够快速地阅读与理解对应的编译规则.
149.当检查系统的发布过程时, 常常可以将相应发行格式的需求作为基准.
&<60;
&<60;
++++++++++++
第八章: 文档
++++++++++++
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’s Dictionary[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.采用一种广度优先查找策略, 从多方攻克代码阅读中存在的问题, 进到找出克服它们的方法为止.