A. 如何阅读源代码
一个大项目的源代码,不要过份详细的阅读。大项目,其代码量基本上是可以吓死人的。过份的关注细节,常常会拘泥于细节,而忽略了整体框架。当你能够看清框架的时候,亦花费了太多的时间。
因此,阅读一个大项目的源代码,其目的不在于欣赏代码细节,而在于迅速看清项目整体框架的大概面貌:都有那些模块,这些模块是干嘛的(不关心具体怎么干),模块之间的通讯机制大概是怎样的,然后在考虑子模块,通常只要掌握两级子模块就够了。花上1,2天的时间掌握这一切,就达到了阅读大项目源码的目的。因为一旦你掌握了框架,你就可以按照这个框架实现这个项目,虽然和原项目全然不同,但是完成的需求却是一样的。
在软件中,架构才是本质。
也许你指望详细阅读大项目源代码能看到高质量的代码,但是,大项目通常都是团队的劳动成果,每个人的不同水平造就了代码质量的高高低低,一个人在不同时间不同环境的代码质量也是不同的。要指望在大片源码面前找到高质量,简直是天方夜谭。
也许你要从阅读源码中掌握某项技术细节,比如bsp,又或者换装,那么,最好的建议是查找相关的技术文档以及文档上所附带的sample code,这种sample code一般不会附带任何干扰,简洁得只是为了证明该技术而存在的。如果没有这些东西,而只能从大项目源码中找的话,你提前先了解了框架,能更快的查找和定位到表达该技术的文件。但是通常都会比较不幸,因为你为了明白这一技术,通常要先理解混入其中的另一技术。
最后谈谈怎样才能阅读到高质量的源代码。何谓高质量?是指算法出人一表(比如某种o(1)的排序法)?还是采用了极端深奥的语言特性将某实现完美表达(比如模板的灵活运用)?无论是哪种,最好的来源是书,如《STL详解》,或者《inside XX》这样的东西。书的作者通常就是这些高质量代码的作者,他会带领你探索这些源码背后的真相。
B. 怎样查看百度文库源代码
查看源代码,通过浏览器就可以查看的。以搜狗浏览器为例,在浏览器页面的右上角有【查看】按钮,点击查看按钮在下拉菜单中找到【查看源代码】,点击就可以看到页面的源码。
查看网络文库的源代码,首先打开网络文库的页面,然后按照上面的方法点击【查看】—【查看源代码】,即可!
C. 怎样读取exe文件的源代码
1、在网络上搜索下载反编译工具ILSpy,解压后如图,双击.exe文件打开解压工具
D. 如何阅读源代码
”, 除了阅读代码以外, 没有更好的方法. 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<<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编译器对类的源代码进行编译, 就可以容易地找到那些可能隐式地生成异常的方法. 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.可以试着向别人介绍您在阅读的代码, 这样做
E. 如何高效阅读源代码
下面是之前写的一篇文章:《如何快速阅读源码》
本文探讨在需要了解一个开源项目时,如何快速的理清开源项目的代码逻辑!
以下是个人认为行之有效的方法:
本文以Mybatis为例来进行演示!
先“跑起来”程序界有个老传统,学习新技术时都是从“Hello World”开始的!无论是学习新语言时,打印“Hello World”;还是学习新框架时编写个demo!那为什么这里的“跑起来”要打个引号呢?
实际上,当你想要阅读一个开源项目的源码时,绝大部分情况下,你已经能够使用这个开源项目了!所以这里的“跑起来”就不是写个“Hello World”,也不是能跑起来的程序了!而是能__在你的脑子里“跑起来”__!什么意思?
Mybatis你会用了吧?那么请问Mybatis是如何执行的呢?仔细想想,你能否用完整的语句把它描述出来?
这里是Mybatis的官方入门文章!你是如何看这篇文章的?读一遍就行了吗?还是跟着文章跑一遍就够了吗?从这篇文章里你能获得多少信息?
我们来理一下:
回答出了上面这些问题!你也就基本能在脑子里把Mybatis“跑起来”了!之后,你才能正真的开始阅读源码!
当你能把一个开源项目“跑起来”后,实际上你就有了对开源项目最初步的了解了!就像“ 书的索引 ”一样!基于这个索引,我们一步步的进行拆解,来细化出下一层的结构和流程,期间可能需要深入技术细节,考量实现,考虑是否有更好的实现方案!也就是说后面的三步并不是线性的,而是__不断交替执行__的一个过程!最终就形成一个完整的源码执行流程!
自顶向下拆解继续通过Mybatis来演示(限于篇幅,我只演示一个大概流程)!我们现在已经有了一个大概的流程了:
虽说每个点都可以往下细化,但是也分个轻重缓急!
很明显,SqlSession去执行 sql才是Mybatis的核心!我们先从这个点入手!
首先,你当然得先下载Mybatis的源码了(请自行下载)!
我们直接去看SqlSession!它是个接口,里面有一堆执行sql的方法!
这里只列出了一部分方法:
SqlSession就是通过这些方法来执行sql的!我们直接看我们常用的,也是Mybatis推荐的用法,就是基于Mapper的执行!也就是说“SqlSession通过Mapper来执行具体的sql”!上面的流程也就细化成了:
那SqlSession是如何获取Mapper的呢?Mapper又是如何执行sql的呢?
深入细节我们来看SqlSession的实现!SqlSession有两个实现类SqlSessionManager和DefaultSqlSession!通过IDE的引用功能可以查看两个类的使用情况。你会发现SqlSessionManager实际并没有使用!而DefaultSqlSession是通过DefaultSqlSessionFactory构建的!所以我们来看DefaultSqlSession是如何构建Mapper的!
它直接委托给了Configuration的getMapper方法!
Configuration又委托给了MapperRegistry类的getMapper方法!
在MapperRegistry类的getMapper中:
在这里knowMappers是什么?MapperProxyFactory又是什么?mapperProxyFactory.newInstance(sqlSession)具体做了什么?
其实很简单,knowMappers是个Map,里面包含了class与对应的MapperProxyFactory的对应关系!MapperProxyFactory通过newInstance来构建对应的Mapper(实际上是Mapper的代理)!
快接近真相了,看mapperProxyFactory.newInstance(sqlSession)里的代码:
这里干了什么?
最终实际还是委托给了sqlSession去执行具体的sql!后面具体怎么实现的就自行查看吧!
延伸改进现在我们的流程大概是这样的一个过程:
现在我们大概知道了:
那么,
这个问题列表可以很长,可以按个人需要去思考并尝试回答!可能最终这些问题已经和开源项目本身没有什么关系了!但是你思考后的收获要比看源码本身要多得多!
再循环一轮结束后,可以再次进行:
不断的拆解->深入->改进,最终你能__通过一个开源项目,学习到远比开源项目本身多得多的知识__!
最重要的是,你的流程是完整的。无论是最初的大致流程:
还是到最终深入的细枝末节,都是个完整的流程!
这样的好处是,你的时间能自由控制:
而不像debug那样的方式,需要一下子花费很长的时间去一步步的理流程,费时费力、收效很小,而且如果中断了就很难继续了!
总结本文通过梳理Mybatis源码的一个简单流程,来讲述一个个人认为比较好的阅读源码的方式,并阐述此方法与传统debug方式相比的优势。
阅读源码是每个优秀开发工程师的必经之路,那么这篇文章就来讲解下为什么要阅读源码以及如何阅读源码。
首先来说下为什么要读源码,有学习源码的必要吗?
为什么要阅读源码?
关于为什么阅读和学习源码,我个人认为可能有以下几点:
(一)吊打面试官,应对面试
为了找到更好的工作,应对面试,因为在面试中肯定会问到源码级别的问题,比如:为什么 HashMap 是线程不安全的?
如果你没有阅读过源码,面试官可能会对回答的结果不满意,进而导致面试结果不太理想,但如果你对源码有所研究,并能够很好地问答面试官的问题,这可能就是你的加分点,可以形成自己独特的竞争力,吊打面试官,升职加薪不是梦。
(二)解决问题(bug)
在开发过程中,我们或多或少会遇到 bug,比如:在 foreach 循环里进行元素的 remove/add 操作,为啥有可能会报 异常?
我们可以先在 Google、Stack Overflow 以及对应项目的 Issues 里看有没有类似问题以及解决办法,如果没有的话,我们只能通过阅读源码的方式去解决了。如果我们对相关源码有所涉猎,就可以快速定位到问题所在。
(三)提升编程能力
和阅读一本好书一样,阅读源码就是和编程大牛面对面交流的机会,在许多优秀的开源项目中,它们的编码规范和架构设计都是很棒的,另外在设计上也使用了大量的设计模式,通过阅读和学习源码,能够快速提升我们的编码水平,以及对设计模式有更深的理解。
同时,在我们阅读完一个源码后,可以触类旁通,能够快速地对其他框架的源码进行阅读和学习,减少时间成本。
除了上述提到的原因之外,可能还有许多,在这里就不一一赘述了,那么在确定了要阅读源码之后,就让我们看下如何阅读源码吧!
如何阅读源码?
如何阅读源码取决于你为什么要读源码,比如:
下面大概说下阅读源码的几点建议:
在阅读之前,可以先从开源项目的官网上看它的架构设计和功能文档,了解这个项目的 整体架构、模块组成以及各个模块之间的联系 。
如果没有对应的项目文档,可以根据代码的模块进行梳理,以形成对项目的初步了解,或者 查看已有的源码解析文章或者书籍 ,在阅读源码之前,了解项目的架构和思路会使阅读源码事半功倍。
在了解一个类的时候,可以使用 ctrl+F12 来查看类中的成员变量和方法。
可以通过 IDEA 的 Diagrams 功能去了解一个类的继承关系。
多打 断点调试 ,断点追踪源码是很好的阅读源码的方式,可以先通过 debug 了解下调用逻辑,都和哪些类有关联,有大致了解后再通过 debug 了解整体代码的功能实现,各个类都起到了什么作用,有没有涉及到设计模式等。
另外,优秀的开源项目中肯定会有许多地方应用到了 设计模式 ,建议在阅读源码之前,需要对常用的设计模式有大致的了解,不然阅读源码的效率会大大降低。
如果遇到读不懂某部分源码的时候,可以先跳过,之后再回来看,如果属于搞不懂这部分就茶不思饭不想的人,可以在网上找是否有该部分源码的解析或者文档,也可以自己通过 源码注释和测试用例 去阅读学习。
一般优秀的开源项目都会有 单元测试 ,可以通过对应类的单元测试去了解方法的含义和用法,加深对源码逻辑的理解。
在阅读源码的时候,可以在代码上加上 注释和总结 ,同时还可以画出 时序图和类图 ,这样对阅读源码有很大的帮助,可以很清楚地知道类之间的调用关系和依赖关系,也方便以后回顾,重新阅读。
在这里推荐大家一个 IDEA 插件 SequenceDiagram,可以根据源码生成调用时序图,便于阅读源码。
刚开始阅读源码,不建议直接看框架源码,可以先从 jdk 源码看起:
jdk 源码也是非常庞大的,可以分模块来阅读,下面是建议的阅读顺序:
其他包下的代码也可以做下了解,JDK源码阅读笔记:https://github.com/wupeixuan/JDKSourceCode1.8
再有了一定的源码阅读经验后,可以再去学习 Spring、Spring Boot、Dubbo、Spring Cloud 等框架的源码。
总结主要介绍了为什么读源码以及如何读源码,供大家参考,每个人都有适合自己的阅读源码的方式,希望可以在学习中去摸索出一套属于自己的方式。
阅读源码不是一蹴而就的,这是持久战,只要你能够坚持下来,肯定受益匪浅。阅读源码的过程比较枯燥,可以在社群里一起讨论学习,这样可能效率更高些。
没看过源代码,都不好意思出来说了,最近刚好在看一些,来说一个。
先看使用 https://element.eleme.cn/#/zh-CN/component/installation
先看一下这个库是做什么用的,然后提供了哪些功能。
看GitHub https://github.com/elemefe
一般会看下项目最新的情况,然后没有关闭的issue,看下wiki,大家在讨论什么。
再看代码
clone 一份到本地,然后先看下目录结构,然后根据文档看几个简单的组件的时候,一边看掘金上的分析,一边自己看下实现。
e le
饿了么这个框架代码结构还是很清楚的,基本上每个组件都是分开的,所以你只要看其他的一个文件夹就行。然后一些工具的都在src文件夹。
要学会看issue,一般开源的项目都有人会来提建议,有些是bug,有些是功能,你可以看看自己是否有能力去解决,如果可以的话,你可以去fork代码,然后自己修改,再提pr。
我最近恰好找摸索出一个梳理遗留系统架构的技巧:自底向上 找到一个典型的切面 沿着调用和回调的路径 在代码中添加结构化注释(比如eclipse中加//TAG 流程A1.1 甲->>乙),这样便得到了一个code地图,并且在tasks视图中看起来很直观(看起来跟书的目录一样)可快速跳转。将目录到有道云笔记的markdown序列图中 就自动生成了一个序列图。
我觉得这基本上就是可缩放的可视化架构地图了,对维护一个比较乱和庞大的遗留系统非常有帮助,定位代码 修改维护都方便多了。
1、需要过硬的基础知识,这个前提。不然基本语法、常用的模式都不晓得怎么读。
2、多参考 历史 版本和更新变化,好的源码都是反复迭代出来的精华,开始就读精华是很不明智的,所以看看版本更新说明,版本的 历史 演变。就想人一样是怎样进化过来的。
3、参考别人阅读注释,想必在你读源码之前也有人读过了源码,并且总结,注释。和分享原理,可供你参考,毕竟每个人读一篇文章,理解的东西是有差异化的。
4、直接买书,有些作品直接出书就是源码精解
5、找个大神给你慢慢分析,这个最快。娓娓道来,直接面授比啥都强。缺点是,你容易跟着他的思维走下去。
我觉得阅读代码就不应该高效,而应该像看小说一样,看的过程就像是在和作者交流,有趣才是看代码的动力。
画图,看数据走向,逻辑走向
先弄清楚这些代码实现了哪些功能,然后从主线开始往下看,好的代码光看变量和接口名称就能明白是什么意思?扒出源码实现的整体框架逻辑,然后再对自己感兴趣的模块进行剖析,还是从整体把握,细节深入,慢慢地整个框架就被丰满了。
接下来是思考为什么要如此设计,这样设计的好处是什么?如果是你来做应该怎么设计,把你觉得源码缺点的地方进行仔细研究,了解里面是否包含自己不清楚的细节,避免遗漏。
接下来就是根据代码改造或者是调试错误,对于源码中遇到的不理解的地方一定要弄明白,有的确实是画蛇添足,有的有独特的作用。
多多学习,对每一种主流框架铭记于心,对主流设计模式了如指掌,万变不离其宗,源码看多了,跟看一个电视机遥控器的操作说明一样。
1、一边阅读代码一边写注释。这是我用过的最好的方法,对代码理解得更深入,看一些重要代码或者特别难懂的代码时挺有用。更何况,注释也是一种文档嘛。
2、一边阅读代码一边绘制UML。这个方法适用于类之间的关系较复杂和调用层次较深的情况,我一般都是先绘制顺序图,然后为顺序图中的类绘制关系图。
3、通过Debug来跟踪程序的主要执行过程,这样就可以分清主次了,阅读的时候更有针对性。
4、类的快速阅读。先弄清楚它在继承链中的位置,看看它的内部状态,也就是成员变量,一般来说,类的对外接口都是对成员变量的访问、加工、代理等,然后看看它的对外接口,也就是公有成员函数,识别核心的一个或多个函数,这时候你应该可以大概了解这个类的职责或作用了。可能这个类是某个设计模式中的一个组成部分,所以,设计模式的掌握对代码的快速阅读也是很有帮助的。
5、带着问题去阅读。比如想了解android中的消息机制,那么看看Looper、Handler、MessegeQueue这几个类就可以了,其他的不要去看,要不然就跑题了。
下面列几个阅读源码时所处的情景,在特定场景下用哪些方法: 不太熟悉业务逻辑,还不是很清楚它是干啥的,可以用3、5。 代码量很大,有几十万行,甚至百万行,可以用2、3、5。 你无法看见程序的运行过程,比如没有用户界面,也有可能是无法运行的,可以用3、5。 设计复杂,用了大量的设计模式,调用链很深,可以用1、2、3、4、5。 时间有限,没有那么多时间让你看源码,可以用3、5。
画出逻辑流程图,先了解整体流程,再详解具体函数