如何阅读一个大型项目代码
对于程序员来说,写代码永远比读代码来的舒服。但现实情况是,
程序员常常需要阅读其他人员写的代码,更多的时候这些代码可能即没
文档也没注释。不过,好象有个人说过一句话,代码之前,了无秘密。
运用适当的策略可以让阅读工作变的轻松很多。
1. 对于常用的系统函数进行追踪。
比如ReadFile,CreateDevice,CreateWindow,在这些函数处放几断点,
可以看到代码的调用过程。通过这种方式可以方便地把代码分为底层代码
和上层逻辑代码。
2.依据项目依赖关系进行阅读。
项目的依赖关系同时表明了项目的复杂程度。对于大型的项目通常都会
分割成若干子项目,根据项目的依赖关系,循序渐进的方式可以让阅读变的简单。
3.对于以lib形式提供的子项目。
在阅读时,可以先把lib的整个项目做为黑盒使用。根据_declspec(dllexport)或者
以头文件方式提供的调用接口,可以减少对于细节的阅读时间。根据模块进行大致的划分,
可以有效地对项目的结构有直接的感性认识。
4.识别项目中使用的设计模式。
对于大型项目来说,设计模式是必不可少的。在庞大的代码中识别设计模式,寻找代码
中使用相似手法的代码结构可以极大简化需要阅读的代码。
5.根据数据流程分析。
动态职责划分。
6.修改部分代码,进行调试。
修改部分常数或者饶过某些程序执行流程,或者以简化的数据对程序进行追踪。
========
怎么学习阅读大型项目的代码
http://blog.csdn.net/jk110333/article/details/7563718
第一章: 导论
++++++++++++
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.层叠if-else if-...-else序列可以看作是由互斥选择项组成的选择结构.
21.有时, 要想了解程序在某一方面的功能, 运行它可能比阅读源代码更为恰当.
22.在分析重要的程序时, 最好首先识别出重要的组成部分.
23.了解局部的命名约定, 利用它们来猜测变量和函数的功能用途.
24.当基于猜测修改代码时, 您应该设计能够验证最初假设的过程. 这个过程可能包括用编译器进行检查
|引入断言|或者执行适当的测试用例.
25.理解了代码的某一部分, 可能帮助你理解余下的代码.
26.解决困难的代码要从容易的部分入手.
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<
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编译器对类的源代码进行编译, 就可以容易地找
到那些可能隐式地生成异常的方法.
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的代码序列要格外注意: 错误可能就潜伏在其中.
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’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.采用一种广度优先查找策略, 从多方攻克代码阅读中存在的问题, 进到找出克服它们的方法为止.
========
大牛们是怎么阅读 Android 系统源码的
最近想去深入学习下底层的东西。
由于工作需要大量修改framework代码, 在AOSP(Android Open Source Project)源码上花费了不少功夫,
Application端和Services端都看和改了不少.
如果只是想看看一些常用类的实现, 在Android包管理器里把源码下载下来, 随便一个IDE配好Source
Code的path看就行.
但如果想深入的了解Android系统, 那么可以看下我的一些简单的总结.
知识
Java
Java是AOSP的主要语言之一. 没得说, 必需熟练掌握.
熟练的Android App开发
Linux
Android基于Linux的, 并且AOSP的推荐编译环境是Ubuntu 12.04. 所以熟练的使用并了解Linux这个系统
是必不可少的. 如果你想了解偏底层的代码, 那么必需了解基本的Linux环境下的程序开发. 如果再深入
到驱动层, 那么Kernel相关的知识也要具备.
Make
AOSP使用Make系统进行编译. 了解基本的Makefile编写会让你更清晰了解AOSP这个庞大的项目是如何构
建起来的.
Git
AOSP使用git+repo进行源码管理. 这应该是程序员必备技能吧.
C++
Android系统的一些性能敏感模块及第三方库是用C++实现的, 比如: Input系统, Chromium项目(WebView
的底层实现).
硬件
流畅的国际网络
AOSP代码下载需要你拥有一个流畅的国际网络. 如果在下载代码这一步就失去耐心的话, 那你肯定没有
耐心去看那乱糟糟的AOSP代码. 另外, 好程序员应该都会需要一个流畅的Google.
一台运行Ubuntu 12.04的PC.
如果只是阅读源码而不做太多修改的话, 其实不需要太高的配置.
一台Nexus设备
AOSP项目默认只支持Nexus系列设备. 没有也没关系, 你依然可以读代码. 但如果你想在大牛之路走的更
远, 还是改改代码, 然后刷机调试看看吧.
高品质USB线
要刷机时线坏了, 没有更窝心的事儿了.
软件
Ubuntu 12.04
官方推荐, 没得选.
Oracle Java 1.6
注意不要用OpenJDK. 这是个坑, 官方文档虽然有写, 但还是单独提一下.
安装:
sudo apt-get install python-software-properties
sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java6-installer
sudo apt-get install oracle-java6-set-default
Eclipse
估计会有不少人吐槽, 为什么要用这个老古董. 其实原因很简单, 合适. 刚开始搞AOSP时, 为了找到效
率最优的工具, 我尝试过Eclipse, IntelliJ IDEA, Vim+Ctags, Sublime Text+Ctags. 最终结果还是
Eclipse. 主要优点有:
有语法分析 (快速准确的类, 方法跳转).
支持C++ (IntelliJ的C++支持做的太慢了).
嵌入了DDMS, View Hierarchy等调试工具.
为了提高效率, 花5分钟背下常用快捷键非常非常值得.
调整好你的classpath, 不要导入无用的代码. 因为AOSP项目代码实在是太多了. 当你还不需要看C++代
码时, 不要为项目添加C++支持, 建索引过程会让你崩溃.
Intellij IDEA
开发App必备. 当你要调试系统的某个功能是, 常常需要迅速写出一个调试用App, 这个时候老旧的
Eclipse就不好用了. Itellij IDEA的xml自动补全非常给力.
巨人的肩膀
AOSP项目官方: https://source.android.com/source/index.html
这个一定要先读. 项目介绍, 代码下载, 环境搭建, 刷机方法, Eclipse配置都在这里. 这是一切的基础
.
Android官方Training: https://developer.android.com/training/index.html
这个其实是给App开发者看的. 但是里面也有不少关于系统机制的介绍, 值得细读.
老罗的Android之旅: http://blog.csdn.net/luoshengyang
此老罗非彼老罗. 罗升阳老师的博客非常有营养, 基本可以作为指引你开始阅读AOSP源码的教程. 你可
以按照博客的时间顺序一篇篇挑需要的看.但这个系列的博客有些问题:
早期的博客是基于旧版本的Android;
大量的代码流程追踪. 读文章时你一定要清楚你在看的东西在整个系统处于什么样的位置.
Innost的专栏: http://blog.csdn.net/innost
邓凡平老师也是为Android大牛, 博客同样很有营养. 但是不像罗升阳老师的那么系统. 更多的是一些技
术点的深入探讨.
Android Issues: http://code.google.com/p/android/issues/list
Android官方Issue列表. 我在开发过程中发现过一些奇怪的bug, 最后发现这里基本都有记录. 当然你可
以提一些新的, 有没有人改就是另外一回事了.
Google: https://www.google.com
一定要能流畅的使用这个工具. 大量的相关知识是没有人系统的总结的, 你需要自己搞定.
其它
代码组织
AOSP的编译单元不是和git项目一一对应的, 而是和Android.mk文件一一对应的. 善用mmm命令进行模块
编译将节省你大量的时间.
Binder
这是Android最基础的进程间通讯. 在Application和System services之间大量使用. 你不仅要知道AIDL
如何使用, 也要知道如何手写Binder接口. 这对你理解Android的Application和System services如何交
互有非常重要的作用. Binder如何实现的倒不必着急看.
HAL
除非你对硬件特别感兴趣或者想去方案公司上班, 否则别花太多时间在这一层.
CyanogenMod
这是一个基于AOSP的第三方Rom. 从这个项目的wiki里你能学到很多AOSP官方没有告诉你的东西. 比如如
何支持Nexus以外的设备.
DIA
这是一个Linux下画UML的工具, 能够帮你梳理看过的代码.
XDA
http://www.xda-developers.com/
这里有最新资讯和最有趣的论坛.
想到了再补充.
在Android系统源码上摸索4年,说说我的看法:
显然Eclipse不是阅读Android源码的好工具,不流畅,搜索低效,继承性关系/调用关系都无法有效查看
。推荐Source Insight,在这个工具帮助下,你才可以驾驭巨大数量的Android 源码,你可以从容在
Java,C++,C代码间遨游,你可以很快找到你需要的继承和调用关系。
顺便,现在东家是Linux+Samba+Windows的工作模式,Linux+Samba用于代码的同步/编译/管理,Windows
做代码编辑。
你需要先理解下这个图:Application层就是一个个应用程序,很好理解。Framework提供一个java的运
行环境以及对功能实现的封装,简单点说,你家装修总要留很多水电之类的接口吧!Runtime/ART是一个
java虚拟机,因为Android上层不是java吗,需要再编译一次成为低级一点的语言识别。从Libraries那
些名字也可以看出来,这里有很多高端大气库,它是功能实现区,多媒体编解码,浏览器渲染啊,数据
库实现啦,很多很多。Kernel部分负责陪硬件大哥玩,你那些功能实现的区域最终都要调硬件吧,
Kernel这家伙已经和硬件很熟了,你就直接通过它来和冷冰冰硬件大哥打交道吧!
好了,上面这些内容很好理解对不对,现在的问题是:当你拿到一份几G的源码,该从哪里开始呢?经过
上面的前言的洗礼,你应该能够很好理解下面这部分了
1.宏观上看,Android源码分为功能实现上的纵向,和功能拓展上的横向。在阅读源码时需要把握好着两
个思路。
譬如你需要研究音频系统的实现原理,纵向:你需要从一个音乐的开始播放追踪,一路下来,你发现解
码库的调用,共享内存的创建和使用,路由的切换,音频输入设备的开启,音频流的开始。
譬如你要看音频系统包括哪些内容,横向:通过Framework的接口,你会发现,音频系统主要包括:放音
,录音,路由切换,音效处理等。
2.Android的功能模块绝大部分是C/S架构
你心里一定需要有这个层级关系,你需要思路清晰地找到Server的位置,它才是你需要攻破的城,上面
的libraries是不是很亲切的样子?看完它长成啥样后,然后你才能发现HAL和Kernel一层层地剥离。
很多研究源码的同学兜兜转转,始终在JAVA层上,这是不科学的,要知道libraries才是它的精髓啊。
3.Android的底层是Linux Kernel。
在理解1,2后,还是需要对Kernel部分有个简单的理解,起码你要熟悉kernel的基础协议吧!你要能看懂
电路图吧!你要熟悉设备的开启和关闭吧!你要熟悉调寄存器了吧!这方面的书太多了,我建议根据实
例去阅读,它并不复杂,不需要一本本厚书来铺垫。
在libraries和kernel间,可能还会有个HAL的东东,其实它是对kernel层的封装,方便各个硬件的接口
统一。这样,如果我换个硬件,不用跑了长得很复杂的libraries里面改了,kernel调试好了后,改改
HAL就好了。
好了,你现在是不是跃跃欲试准备去找个突破口准备进攻了,但是好像每个宝库的入口都挺难找了
我大概在三个月前阅读完Android UI系统的源码,这是Android最复杂的部分,我要简单说下过程。
我需要先找宝库入口,我要研究UI,首先要找什么和UI有亲戚关系吧!
View大神跳出来了,沿着它往下找找看,发现它在贴图在画各种形状,但是它在哪里画呢,马良也要纸
吧?
很明显它就是某个宝藏,但是世人只是向我们描述了它有多美,却无人知在哪里?我们需要找一张地图
罗。
开发Android的同学逃不掉Activity吧!它有个setcontentview的方法,从这个名字看好像它是把view和
activity结合的地方。赶紧看它的实现和被调用。,然后我们就发现了Window,ViewRoot和
WindowManager的身影,沿着WM和WMS我们就惊喜会发现了Surface,以及draw的函数,它居然在一个
DeCorView上画东西哈。借助Source Insight, UI Java层的横向静态图呼之欲出了。
完成这个静态UML,我觉得我可以开始功能实现上追踪了,这部分主要是C++的代码(这也是我坚定劝阻
的放弃Eclipse的原因),我沿着draw函数,看到了各个层级的关系,SurfaceSession的控制和事务处理
,SharedBuffer读写控制,彪悍的SurfaceFlinger主宰一切,OpenGL ES的神笔马良。FrameBuffer和
FrameBufferDevice的图像输出,LCD设备打开后,开始接收FBD发过来的一帧帧图像,神奇吧。
好吧,就这样,再往底层我爱莫能助了!
工具:Android Studio
查看源码方法: http://androidperformance.com/2015/01/16/view-android-source-code-with-
androidstudio.html
看那一部分:
Framework/base
Package/apps
art
external
操作系统:推荐Linux,可以随时编译Android源码
测试机:推荐Nexus系列手机,编译源码后push进去可以随时验证。
Android源码两个部分看得最多,一个是packages,就是各个系统应用的实现,另外就是framework,框
架层的实现。具体看什么就看你想了解什么。
工具eclipse也很好,build一下生成class path,各种转跳非常方便,不需要grep了。
vim+grep. 读任何代码都是一样的. android 并没什么特殊的
我觉得vim+tags已经很爽了,当然要是source insight出个Mac版本就更好了。
最近想去深入学习下底层的东西。看的哪一部分?
代码里面的部分,那就学学Linux Kernel,Bootloader,再底层的需要学些硬件驱动,一步一步来。
有没有好的查看工具?
Windows 下推荐SoureInsight,建个工程把AOSP 所有代码丢进去,几个月不用关掉,看到感兴趣的地方
不停的往下追就好。当初就在这上面直接改代码,Ubuntu 开个窗口编译搞定,其实是懒得开IDE(逃
因为工作原因,接触 Android 源码并且折腾的很早,也因为工作原因,无法有效的利用各种 IDE, 所以
一直是用 VIM + grep 的方式。效率可能比较低下,但是读源码这种东西就是贵在坚持学习,看的时间
长了,慢慢哪些代码在什么地方都大概能有印象,所以读和找起来就很方便。蛮羡慕会用 taglist 的,
只是一直不大习惯使用。
再有比较关键的一点是多找一些身边喜欢学习的朋友交流,每个人看的可能都不一样,很多时候突破口
就在抽烟聊天的时间讨论出来的。
零、准备工作:
开发环境:建议在ubuntu(32/64位均可)下进行开发。
开发板:推荐cubieboard、pcduino
(资料开放,连相应的android源码以及硬件配置文件都有提供。
不推荐所谓的开源硬件Raspberry-Pi,毕竟Raspberry-Pi并不是真正的开源硬件。)
其他:串口线
强烈不推荐在windows环境下搞Android底层开发!!!
一个及格的Android工程师,就应该熟练在Linux环境下进行开发工作。
如果你不会“优雅”地使用Linux,不得不在windows下编辑代码,
恳请你记得设置编码utf-8!!!!!!!!!!
(怨念:每次编译固件看见乱码警告就各种强迫症啊摔ヽ(≧Д≦)ノ!尽管大多情况下不影响编译结果
。)
一、编译器的选择:
阅读/编译代码:eclipse (Java)、vim+ctags+cscope(C/C++)
搜索代码:shell
例如命令1:grep "Telephoney" -rn ./*
例如命令2:find -name Telephoney*
等等等
不会用eclipse,请不要说他不好用!
eclipse是阅读framework代码以及系统应用的居家旅行杀人必备神器。
正确使用eclipse阅读Android源码:
1.复制eclipse的classpath到Android源码根目录
cp ~/android/development/ide/eclipse/.classpath ~/android
2.修改eclipse缓存设置
把eclipse.ini文件的3个值改为下面的值(不要盲目参考,需要根据自己机器情况来定):
-Xms128m
-Xmx768m
-XX:MaxPermSize=512m
(mac路径:eclipse/Eclipse.app/Contents/MacOS/eclipse.ini)
3.导入eclipse代码风格
把android-formatting.xml和android.importorder导入Eclipse
~/android/development/ide/eclipse/android-formatting.xml
~/android/development/ide/eclipse/android.importorder
4.导入Android源码
新建Java Project(不是Android project),选择从已存在的工程导入,定位到Android源码的目录进
行导入即可。
PS:eclipse只是阅读/编辑代码的工具,编译源码还是需要通过make命令在终端编译的。
二、知识储备:
编程语言什么的没什么好说。
着重提一个:设计模式
由于Android源代码用到各种各样的设计模式,如果不会设计模式,将会大大降低你的阅读理解速度。
三、网站资料:
http://source.android.com/devices
android官方文档,很详细描述各模块的设计思路。
理解这个链接的内容,看个十遍八遍,一边看一边分析代码。
PS:由于比较笨(-’๏_๏’-),所以我当年是一边看一边翻译出来,大家也可以参考我这种方法,容易加
深理解、印象
四、书刊资料:
邓凡平大神的《深入理解Android》系列
《深入理解Android:卷I/卷II》 [Kindle电子书] ~ 邓凡平
链接:http://www.amazon.cn/dp/B00K6Y5OEM
作者Blog:http://blog.csdn.net/Innost/
五、学习方法:
要一下子把所有模仿都理解透,是件比较困难的事情。
可以分模块学习:
Telephoney
Audio & media
webkit & Browser
Wi-Fi & wpa supplicant
Dalvik
Display & Surface flinger
Camera
等等等
分享自己总结的一些阅读源码的方法,需要注意的几个点可以注意下
做什么事情都要知道从那里开始,读程序也不例外。在c语言里,首先要找到main()函数,然后逐层去阅
读,其他的程序无论是vb、delphi都要首先找到程序头,否则你是很难分析清楚程序的层次关系。
分层次阅读
在阅读代码的时候不要一头就扎下去,这样往往容易只见树木不见森林,阅读代码比较好的方法有
一点象二叉树的广度优先的遍历。在程序主体一般会比较简 单,调用的函数会比较少,根据函数的名字
以及层次关系一般可以确定每一个函数的大致用途,将你的理解作为注解写在这些函数的边上。当然很
难一次就将全部注 解都写正确,有时候甚至可能是你猜测的结果,不过没有关系这些注解在阅读过程是
不断修正的,直到你全部理解了代码为止。一般来说采用逐层阅读的方法可以是 你系统的理解保持在一
个正确的方向上。避免一下子扎入到细节的问题上。在分层次阅读的时候要注意一个问题,就是将系统
的函数和开发人员编写代码区分开。在 c, c++,java ,delphi中都有自己的系统函数,不要去阅读这些
系统函数,除非你要学习他们的编程方法,否则只会浪费你的时间。将系统函数表示出来,注明它们的
作用 即可,区分系统函数和自编函数有几个方法,一个是系统函数的编程风格一般会比较好,而自编的
函数的编程风格一般比较会比较差。从变量名、行之间的缩进、注 解等方面一般可以分辨出来,另外一
个是象ms c6++会在你编程的时候给你生成一大堆文件出来,其中有很多文件是你用不到了,可以根据文
件名来区分一下时候是系统函数,最后如果你实在确定不了,那就 用开发系统的帮助系统去查一下函数
名,对一下参数等来确定即可。
重复阅读
一次就可以将所有的代码都阅读明白的人是没有的。至少我还没有遇到过。反复的去阅读同一段代
码有助于得代码的理解。一般来说,在第一次阅读代码的时候 你可以跳过很多一时不明白的代码段,只
写一些简单的注解,在以后的重复阅读过程用,你对代码的理解会比上一次理解的更深刻,这样你可以
修改那些注解错误的 地方和上一次没有理解的对方。一般来说,对代码阅读3,4次基本可以理解代码的
含义和作用。
运行并修改代码
如果你的代码是可运行的,那么先让它运行起来,用单步跟踪的方法来阅读代码,会提高你的代码
速度。代码通过看中间变量了解代码的含义,而且对 以后的修改会提供很大的帮助
用自己的代码代替原有代码,看效果,但在之前要保留源代码
600行的一个函数,阅读起来很困难,编程的人不是一个好的习惯。在阅读这个代码的时候将代码进
行修改,变成了14个函数。每一个大约是40-50 行左右.
———————————————源码下载网站—————————————————————
跟你说几个我常用的源码下载网站
csdn(中文IT社区)它是集新闻、论坛、群组、Blog、文档、下载、读书、Tag、网摘、搜索、.NET、
Java、游戏、视频、人才、外包、第二书店、《程序员》等多种项目于一体的大型综合性IT门户网站,
源码只是其中的一项,但是很实用 里边有很多大牛。
DevStore(源码下载)主要是开发者服务平台,汇集国内外众多第三方开发者服务,为开发者提供从设
计开发到运营推广一站式的解决方案,源码和服务评测也是亮点,很专业,很实用,这里边聚集的都是
开发者和PM,可以看看。
站长之家(网站源码)针对个人站长,企业网管提供的资讯和源码,包含的语言和类型也比较多。
这个问题最好玩的地方就是在于一堆人喜欢东扯一点西扯一点 , 最后说拿eclipse来看看源码吧= =
真好玩.
工具就 vim + 一款自己熟悉的文本编辑器,关键是要选一个感兴趣的子系统/模块开始
之前好像就答过 目标导向是阅读一切源代码的不二法门 试试给android系统慢慢加一下小功能 先从ui
开始 然后再往下深 期间没准有彩蛋 遇到bug之类的 修掉提交给google吧
Eclipse里看最方便了。在Android SDK的platforms目录下建立sources文件夹,放入frameworks的源代
码,Eclipse里就能自动打开源代码了。网上有很多具体的说明。
如果是看所有源码,包括底层C/C++源码,可以用lxr,变量函数查找和跳转都非常方便,但配置起来比
较麻烦,需要多尝试。
有没有用记事本的?我就用Notepad++和SublimeText来看。搜索用Everything和baregrep,以及
Notepad++的搜索功能
多年的Android经验建议,debug, debug & debug!
动态跟踪android framework代码,属于service实现的相关代码就attach到systemserver上
比你看死代码强N倍!
阅读别人的代码作为开发人员是一件经常要做的事情。一个是学习新的编程语言的时候通过阅读别人的
代码是一个最好的学习方法,另外是积累编程经验。如果你有机会阅读一些操作系统的代码会帮助你理
解一些基本的原理。还有就是在你作为一个质量保证人员或一个小领导的时候如果你要做白盒测试的时
候没有阅读代码的能力是不能完成相应的任务。最后一个就是如果你中途接手一个项目的时候或给一个
项目做售后服务的时候是要有阅读代码的能力的。
收集所有可能收集的材料
阅读代码要做的第一件事情是收集所有和项目相关的资料。比如你要做一个项目的售后服务,那么
你首先要搞明白项目做什么用的,那么调研文档、概要设计文档、详细设计文档、测试文档、使用手册
都是你要最先搞到手的。如果你是为了学习那么尽量收集和你的学习有关的资料,比如你想学习linux的
文件系统的代码,那最好要找到linux的使用手册、以及文件系统设计的方法、数据结构的说明。(这些
资料在书店里都可以找到)。
材料的种类分为几种类型
1.基础资料。
比如你阅读turbo c2的源代码你要有turbo c2的函数手册,使用手册等专业书籍,msc 6.0或者java
的话不但要有函数手册,还要有类库函数手册。这些资料都是你的基础资料。另外你要有一些关于uml的
资料可以作为查询手册也是一个不错的选择
2.和程序相关的专业资料。
每一个程序都是和相关行业相关的。比如我阅读过一个关于气象分析方面的代码,因为里边用到了
一个复杂的数据转换公式,所以不得不把自己的大学时候课本 找出来来复习一下高等数学的内容。如果
你想阅读linux的文件管理的代码,那么找一本讲解linux文件系统的书对你的帮助会很大。
3.相关项目的文档资料
这一部分的资料分为两种,一个相关行业的资料,比如你要阅读一个税务系统的代码那么有一些财
务/税务系统的专业资料和国家的相关的法律、法规的资料是 必不可少的。此外就是关于这个项目的需
求分析报告、概要设计报告、详细设计报告,使用手册、测试报告等,尽量多收集对你以后的代码阅读
是很重要的
知识准备
了解基础知识,不要上来就阅读代码,打好基础可以做到事半功倍的效果
留备份,构造可运行的环境
代码拿到手之后的第一件事情是先做备份,最好是刻在一个光盘上,在代码阅读的时候一点不动代
码是很困难的一件事情,特别是你要做一些修改性或增强性维护的时候。而一旦做修改就可能发生问题
,到时候要恢复是经常发生的事情,如果你不能很好的使用版本控制软件那么先留一个备份是一个最起
码的要求了。
在做完备份之后最好给自己构造一个可运行的环境,当然可能会很麻烦,但可运行代码和不可运行
的代码阅读起来难度会差很多的。所以多用一点时间搭建一个环境是很值得的,而且我们阅读代码主要
是为了修改其中的问题或做移植操作。不能运行的代码除了可以学到一些技术以外,用处有限。
找开始的地方
做什么事情都要知道从那里开始,读程序也不例外。在c语言里,首先要找到main()函数,然后逐层
去阅读,其他的程序无论是vb、delphi都要首先找到程序头,否则你是很难分析清楚程序的层次关系。
分层次阅读
在阅读代码的时候不要一头就扎下去,这样往往容易只见树木不见森林,阅读代码比较好的方法有
一点象二叉树的广度优先的遍历。在程序主体一般会比较简 单,调用的函数会比较少,根据函数的名字
以及层次关系一般可以确定每一个函数的大致用途,将你的理解作为注解写在这些函数的边上。当然很
难一次就将全部注 解都写正确,有时候甚至可能是你猜测的结果,不过没有关系这些注解在阅读过程是
不断修正的,直到你全部理解了代码为止。一般来说采用逐层阅读的方法可以是 你系统的理解保持在一
个正确的方向上。避免一下子扎入到细节的问题上。在分层次阅读的时候要注意一个问题,就是将系统
的函数和开发人员编写代码区分开。在 c, c++,java ,delphi中都有自己的系统函数,不要去阅读这些
系统函数,除非你要学习他们的编程方法,否则只会浪费你的时间。将系统函数表示出来,注明它们的
作用 即可,区分系统函数和自编函数有几个方法,一个是系统函数的编程风格一般会比较好,而自编的
函数的编程风格一般比较会比较差。从变量名、行之间的缩进、注 解等方面一般可以分辨出来,另外一
个是象ms c6++会在你编程的时候给你生成一大堆文件出来,其中有很多文件是你用不到了,可以根据文
件名来区分一下时候是系统函数,最后如果你实在确定不了,那就 用开发系统的帮助系统去查一下函数
名,对一下参数等来确定即可。
写注解
写注解是在阅读代码中最重要的一个步骤,在我们阅读的源代码一般来说是我们不熟悉的系统,
阅读别人的代码一般会有几个问题,1搞明白别人的编程思想不 是一件很容易的事情,即使你知道这段
程序的思路的时候也是一样。2阅读代码的时候代码量一般会比较大,如果不及时写注解往往会造成读明
白了后边忘了前边的 现象。3阅读代码的时候难免会出现理解错误,如果没有及时的写注解很难及时的
发现这些错误。4不写注解有时候你发生你很难确定一个函数你时候阅读过,它的功能是什么,经常会发
生重复阅读、理解的现象。
好了,说一些写注解的基本方法:
1.猜测的去写,刚开始阅读一个代码的时候,你很难一下子就确定所有的函数的功能,不妨采用采用猜
测的方法去写注解,根 据函数的名字、位置写一个大致的注解,当然一般会有错误,但你的注解实际是
不但调整的,直到最后你理解了全部代码。
2.按功能去写,别把注解写成语法说明 书,千万别看到fopen就写打开文件,看到fread就写读数据,这
样的注解一点用处都没有,而应该写在此处开发参数配置文件(****。dat)读出 系统初始化参数。。。
。。,这样才是有用的注解。
3.在写注解的使用另外要注意的一个问题是分清楚系统自动生成的代码和用户自 己开发的代码,一般来
说没有必要写系统自动生成的代码。象delphi的代码,我们往往要自己编写一些自己的代码段,还要对
一些系统自动生成的代码段进行 修改,这些代码在阅读过程是要写注解的,但有一些没有修改过的自动
生成的代码就没有必要写注解了。
4.在主要代码段要写较为详细的注解。有一些函数或类在程序中起关键的作用,那么要写比较详细的注
解。这样对你理解代码有很大的帮助。
5.对你理解起来比较困难的地方要写详细的注解,在这些地方往往会有一些编程的技巧。不理解这些编
程技巧对你以后的理解或移植会有问题。
6.写中文注解。如果你的英文足够的好,不用看这条了,但很多的人英文实在不怎么样,那就写中文注
解吧,我们写注解是为了加快自己的理解速度。中文在大多数的时候比英文更适应中国人。与其写一些
谁也看不懂的英文注解还不如不写。
重复阅读
一次就可以将所有的代码都阅读明白的人是没有的。至少我还没有遇到过。反复的去阅读同一段代
码有助于得代码的理解。一般来说,在第一次阅读代码的时候 你可以跳过很多一时不明白的代码段,只
写一些简单的注解,在以后的重复阅读过程用,你对代码的理解会比上一次理解的更深刻,这样你可以
修改那些注解错误的 地方和上一次没有理解的对方。一般来说,对代码阅读3,4次基本可以理解代码的
含义和作用。
运行并修改代码
如果你的代码是可运行的,那么先让它运行起来,用单步跟踪的方法来阅读代码,会提高你的代码
速度。代码通过看中间变量了解代码的含义,而且对 以后的修改会提供很大的帮助
用自己的代码代替原有代码,看效果,但在之前要保留源代码
600行的一个函数,阅读起来很困难,编程的人不是一个好的习惯。在阅读这个代码的时候将代码进
行修改,变成了14个函数。每一个大约是40-50 行左右.
使用GrepCode.com查看API很方便。
vim+tags+cscope+grep
emacs gtags ggtags
========
Spring源码解析——如何阅读源码
http://www.cnblogs.com/xing901022/p/4178963.html
最近没什么实质性的工作,正好有点时间,就想学学别人的代码。也看过一点源码,算是有了点阅
读的经验,于是下定决心看下spring这种大型的项目的源码,学学它的设计思想。
手码不易,转载请注明:xingoo
这篇博文你可以了解到:
1 Spring jar包以及源码使用
2 简单的spring运行示例
3 利用断点调试程序,如何快速的阅读程序【快捷键等的使用】
这次阅读的源码比较老了,是3.0.5版本的,由于正好手头有这个版本的源码,而且平时基本上也是
用的这个版本Spring,因此后文的分析也都是针对这个版本。
下面贡献一下Jar包以及源码,由于百度云上传的压缩包总是解压失败,就放在csdn上面了。
如何使用jar包以及源码的source包
首先,在工程右键,属性中,添加必要的jar包。
选中必要的jar包,上面给出的源码jar包中,导入spring3.0.5中的所有jar包。
其中lib内的是spring的jar包,用到哪个导入哪个,不知道的话,全部导入就行了。
外面的几个jar包,用于日志以及mysql的驱动。commons-logging jar包是必须的,其他的随意吧。
不确定的话,lib外面的这几个jar包以及lib里面的都导入就行了。
导入jar包后,点开jar包,选中source attachment进行编辑,链接到源码的jar包。
选择相应的source源码包
全部导入后,如下
spring样例
首先是一个必要的POJO类,用于注入属性的值。
package com.test.bean;
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void info(){
System.out.println("name:"+getName()+" age:"+getAge());
}
}
主函数,用于读取资源文件,获取bean,调用info方法
package testSpring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.test.bean.Person;
public class test {
public static void main(String[] args){
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");//读取
bean.xml中的内容
Person p = ctx.getBean("person",Person.class);//创建bean的引用对象
p.info();
}
}
复制代码
bean.xml用于配置bean的资源文件
复制代码
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
复制代码
运行结果
阅读源码
首先,有了前面的jar包以及源码包,你就可以通过这个简单的程序,进行但不的调试,阅读源码。
简单的说下调试的快捷键:
1 F5:下一步,可以进入下一个函数栈
2 F6:当前函数的下一步,不会进入其他的函数。
3 F8:下一个断点。
4 也可以通过选中一个变量或者表达式,按ctrl+shift+i 来查看内容。或者添加监视的方式,查看
。
5 可以通过快捷键F2,来查看一个函数方法的javadoc,即说明
6 快捷键F3或者ctrl+鼠标点击,进入一个函数
7 ctrl+shift+G 查看当前方法都有谁在使用
8 F4查看类的继承关系,可以向上显示出类继承的父类以及接口。
有了调试的方法,接下来,就是如何阅读源码了!
1 参考书籍,推荐《Spring技术内幕》
这本书,基本上很详细的讲述了,spring的实现方式,以及类之间的复杂关系。可以帮助你快速的
理清复杂的类之间的关系。
2 使用StarUML画类图
比如你好不容易理清了一个部分的关系,很快就会忘记其中的关系,那么可以通过这个工具,简单
的画出其中的复杂关系。
这样,下一次看的时候也会清楚一些,这是我今天刚画好的ClassPathXmlApplicationContext的类
图关系:
========