框架源码阅读的方法与技巧

代码是形式,逻辑是神韵。html


引子

“解锁优秀源代码的基本方法与技巧” 一文中,探讨了阅读优秀源码的基本步骤、方法、技巧、所面临的障碍及克服之策。多加训练,应该能够达成以下目标:程序员

  • 可以读懂独立类和基本容器的实现;
  • 可以读懂小型的基础库和框架;
  • 经过源码阅读来调试和解决实际中的问题。

本文进一步探索如何阅读成熟框架的源码。

web

舒适提示

欲速则不达。阅读源码很容易理解为就是直接去阅读代码自己。实际上,代码只是形式,逻辑才是神韵。面试

凡有助于去理解逻辑,理解其原理、架构、实现的,都是值得阅读的。包括而不限于官方文档和 API 文档、架构设计分析文章、原理分析文章、源码阅读分析文章。磨刀不误砍柴工。准备工做作充足,充分借助各类资源辅助,阅读源码才能事半功倍。算法


预思考

有需求才有目标,有目标才有设计,有设计才有框架。在阅读某个源码模块以前,思考若干基本问题是必要的。编程

  • 需求是什么?用一句话说清楚;
  • 设计目标是什么?用一句话说清楚;
  • 核心优点和适用场景是什么?分别用一句话说清楚;
  • 基本原理是怎样的?先本身思考怎么实现,而后阅读框架原理文章;
  • 总体设计是怎样的? 先本身思考怎么设计,而后阅读架构设计的文章;
  • 技术难点是什么?先本身思考其中的难点及解决方案,而后阅读相关文章;
  • 数据结构及算法流程是如何设计的?阅读框架的源码解析文章。

好比 SpringBean 模块:设计模式

  • 需求:有一套通用机制去建立和装配应用所须要的完整的 Bean 实例,使得应用无需关注 Bean 实例的建立和管理,只要按需获取;
  • 设计目标:根据指定的配置文件或注解,生成和存储应用所须要的装配完整的 Bean 实例,并提供多种方式来获取 Bean 实例;
  • 核心优点:支持多种装配方式、自动装配、依赖关系自动注入;支持不一样做用域的 Bean 实例建立和获取;稳定高效;
  • 适用场景:有大量的 Bean 须要建立,这些 Bean 存在复杂的依赖关系;
  • 基本原理:反射机制 + 缓存;
  • 算法流程:建立 bean 工厂对象 -> 扫描资源路径,得到 bean 的 class 文件 -> 生成 bean 定义的 beanDefinition 实例 -> 根据 beanDefinitioin 实例建立 bean 实例并缓存到 bean 工厂对象 -> 依赖自动注入 -> 执行钩子方法 -> 完整的 bean 实例准备就绪。
  • 技术难点:依赖自动装配、循环引用;解决自动依赖注入和循环引用问题须要用到缓存机制。

需求与目标缓存

需求与目标每每容易混为一谈。但需求不等于目标。网络

  • 需求是宽泛的,目标是具体的;
  • 目标是需求的一种实现途径,每每是设计一个具有某些关键特性的系统或产品。

目标是功能与质量的结合体;除了功能部分,肯定质量指标也是尤其关键的。质量指标可参阅:“Web服务端软件的服务品质概要”数据结构

对于某个框架来讲,需求、适用场景和核心优点,都是能够直接在官网或项目主页获取到的。如何还原框架的设计目标呢?能够从核心优点中获取基本说明,更多的就要从 API 文档里来提炼了。

方法

不少开发童鞋可能对阅读源码心生畏惧。其实读源码既不神秘也不复杂:写个 Demo,打断点,运行,而后细细揣摩。阅读源码就是观摩高手出招的过程。

  1. 确立目标,一般是理解某个模块的原理、设计或者为了解决实际问题;
  2. 写个 demo,可以将主流程运行起来;
  3. 找到框架运行的入口点,经过静态代码分析,大体了解整个实现流程;
  4. 在预估会通过的关键地方打断点,单步调试;
  5. 仔细查看主流程通过的主路径、每个主要对象及其成员变量的值及变化,细细揣摩其设计意图和方法技巧;
  6. 绘制总体流程框图和类的交互图;
  7. 学习和理解关键类及关键方法及实现(代码)。

阅读源码,要把握主要与扩展:

  • 首先把主流程及涉及到的主要类弄透彻;
  • 理解其扩展机制;
  • 理解主要扩展实现(须要的时候徐图之)。

阅读源码,经常要将“静态代码分析”和“单步调试”结合起来使用。

静态代码分析

静态代码分析,就是沿着方法调用链,“顺藤摸瓜”一路点击下去。一般可以对总体流程有一个大概的了解。

因为框架实现经常基于接口编程,有时会遇到有多个实现的情形。这时,能够根据直觉和经验,选择一个最有可能的默认实现继续跟下去,或者经过单步调试来弄清楚是哪一个具体实现。

单步调试

单步调试,是看似笨拙却很实用的源码阅读方法。单步调试在如下情形尤为有用:

  • 接口调用有多个实现,难以肯定是哪一个是具体实现时;
  • 查看某个比较复杂的具体类的成员时;
  • 理解实现细节时。

框架解析

框架的设计实现一般包括三层:

  • 问题域及解决方案构成的抽象层,解决问题的核心部分;
  • 封装和交互构成的设计层,确保灵活性、可扩展性和应用集成;
  • 各类细节构成的实现层,用于保证性能和容错等。

阅读顺序是:抽象层 -> 封装与交互层 -> 细节实现层 或者 抽象层 -> 细节实现层 -> 封装与交互层。抽象层比如匣中的宝珠,不能干买椟还珠的事情。

抽象层

抽象层便是问题求解层。技术面试中问到的原理或实现机制,一般都属于这一层。

因为封装和交互、实现细节的大量代码每每会将用于解决问题的核心代码“淹没”,所以,在探索抽象层时,要学会大胆过滤封装和细节,直接跳过大量的分支条件语句,暂时跳过使人疑惑的地方,始终聚焦和直击解决问题的核心部分。用于解决基本问题的核心代码一般是很少的。

好比,Bean 实例建立的核心代码是 ClassPathBeanDefinitionScanner.doScan(扫描资源路径,生成 beanDefinition 对象) 和AbstractAutowireCapableBeanFactory.doCreateBean 方法(根据 beanDefinition 建立 bean 实例)。

设计层

要弄明白设计层,就要先弄清楚框架的总体设计:

  • 有哪些子模块,子模块的设计意图是什么;
  • 子模块之间的关联是怎样的,如何串联成一个完整的设计意图。

框架的设计实现经常会用到设计模式。

  • 经常使用设计模式:工厂、单例、外观、策略、适配器、装饰、代理、模板、组合、观察者、迭代器;
  • 不一样问题域可能会用到的设计模式,好比 DB 驱动接口实现会用到生成器模式和桥接模式,web 请求处理用到职责链模式。

经常使用设计模式的使用场景:

  • 若是须要建立实例,则一般离不开工厂和单例模式;
  • 若是涉及较为复杂的算法流程,部分算法须要在子类实现,则会用到模板方法模式;
  • 若是须要多种实现,并依据特定场景来选取使用,则会用到策略模式;
  • 若是要将客户端接口及实现与框架的调用隔离,则会用到动态代理模式;
  • 若是要灵活叠加多种功能,则会用到装饰器模式;
  • 若是涉及到事件机制,则离不开观察者模式;
  • 若是须要在库实现的基础上提供简洁接口,则一般用到外观模式;
  • 若是要将多种实现与多种接口定义进行链接,则会用到桥接模式;
  • 若是须要涉及大量配置(规格)并生成实例,则一般用到生成器模式;
  • 若是涉及容器元素访问,则离不开迭代器模式;
  • 若是须要以统一接口访问总体与部分的行为,且总体由部分组成,则一般用到组合模式。

理解基本设计模式的特征和适用场景,识别设计模式的使用,能够更自如地在框架源码之间穿梭。

细节层

细节是最考验源码阅读的心性了。细节藏魔鬼。关键细节考虑不周全,可能会致使整个设计的失败。所以,细节层也是值得仔细推敲的。技术面试中也经常考察实现细节。若是可以回答上来,大几率会让面试官眼前一亮。

有时,一些实现细节可能让人摸不到头脑。此时,能够上网搜索一下,每每会“茅塞顿开”。

克服障碍

阅读成熟框架源码,遇到的一大挑战就是对象之间的错综复杂的交互关系。使人生畏。这实际上考验着开发者的抽象和建模能力。

原理流程图

原理流程图很是重要,就像地图同样,指引人更容易地在“代码迷宫”中穿行而不迷失方向。

在阅读源码以前,设法弄到并理解框架的原理流程图,每每能起到事半功倍的效果。就如行兵打仗,先弄清楚天时与地形。不可不重视之。

概念图景

优秀的软件设计,每每是先创建一个比较完整的概念图景。概念图景,就是关于某个问题域的概念及其关联关系的总体图。

譬如盖房子吧。有的人盖房子就是:砌砖!砌砖!!砌砖!!!要安装窗户怎么办?把其中一大块砖墙锤空了再安。

有的人,则会“设计先行”:

  • 原材料 => 子部件 => 组合与集成。
  • 原材料:砖、石、木、铝、铜、玻璃等;
  • 子部件:墙、窗框、窗户、门、地板、楼梯、锁、通道等;
  • 房子:由子部件进行组合和集成而成;
  • 机制:子部件的组合与集成的原理支撑,好比形状的组合与契合、承压计算等。

如何理清其中的复杂交互关系,从而理解其中蕴含的设计思想呢?须要先理清楚框架的概念图景。

有两种技巧能够结合使用:

  • 因为接口定义了具体类的行为规范,能够经过阅读接口定义及文档来了解其设计思路和骨架;
  • 查看具体类的实例成员(暂不涉及方法),根据经验揣摩其设计意图。

核心类成员

要深刻到具体实现,则没法避免核心类的阅读。核心类每每拥有十几个甚至几十个成员及方法,展现出了十足的源码阅读劝退诚意。面对这种状况怎么办呢? 有三个技巧能够结合使用:

  • 按快捷键 Alt+7,能够查看该类的全部成员及方法,概览一下,大体猜想其意图;
  • 首先只关注那些对核心问题求解有重要影响的成员,暂时忽略那些用来提高性能、可扩展性等方面的成员;
  • 单步调试,仔细看看运行时的成员对象如何,这样会更加直观具体一些。好比 DefaultListableBeanFactory 这个类,单步调试后获得以下图示:

技术难点

技术难点也是理解源码实现的一个主要障碍。技术难点主要有三类:

  • 数据结构与算法:好比 HashMap 用到了哈希表和红黑树,须要先阅读文献(好比《算法导论》)理解其结构与算法;
  • 原理机制:好比 IO 读写、内存管理、文件系统、编译原理、网络协议,先学习相关的原理机制,夯实基础;
  • 编程模型:特别的编程手法和技巧,好比读 hystrix 源码,就要先熟悉函数式编程和响应式编程。

如何找到论述原理机制的相关文献呢?有一些基本方法可循:

  • 经典书籍:好比数据结构与算法,就有《算法导论》、《算法》、《计算机程序设计艺术》(排序与查找)等;
  • 经典论文:一些还没来得及写入书籍的论文解读,好比 Raft 算法等;
  • 技术书籍:好比 Linux 操做系统内核实现,TCP 协议详解等;
  • 官方文档:优秀项目主页的文档部分,每每有相关原理机制的介绍,好比 ES 的官方文档;
  • JavaDoc:优秀源码的 Java Doc 每每会引用相关出处,好比 AQS 的源码;
  • 优秀博文:优秀博文每每有一些文献引用,能够阅读相关文献引用;
  • 百科与搜索:在维基百科上搜索出处和引用来源;或者使用搜索引擎。

越到后面,就会发现,真正须要仔细阅读和钻研的书籍和论文,其实并很少。花费不少时间阅读网络文章,这些偷懒和捷径,反而是走了弯路。


耐心与意志

阅读框架源码须要很大的耐心和意志。有点像蚕宝宝吃桑叶,须要一点一点地啃。各个击破。在这个过程当中,须要克服很多障碍,才能“修得正果”。

可使用多种辅助手段:

  • 边听音乐边阅读代码;
  • 拉取代码分支,边读边作标记并提交;
  • 阅读原理、架构及源码分析文章。

小结

源码阅读技能,能够说是程序员的“内功心法”之一。如果能读通优秀源码,则应对平常编程工做游刃有余,而应对难题则有路可循。

路漫漫其修远兮,吾将上下而求索。

相关文章
相关标签/搜索