如何高效、优雅、愉快地阅读项目源码?

共 4804字,需浏览 10分钟

 ·

2021-09-01 19:09


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

引子

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

温馨提示

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

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

预思考

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

  • 需求是什么?用一句话说清楚;

  • 设计目标是什么?用一句话说清楚;

  • 核心优势和适用场景是什么?分别用一句话说清楚;

  • 基本原理是怎样的?先自己思考怎么实现,然后阅读框架原理文章;

  • 整体设计是怎样的?先自己思考怎么设计,然后阅读架构设计的文章;

  • 技术难点是什么?先自己思考其中的难点及解决方案,然后阅读相关文章;

  • 数据结构及算法流程是如何设计的?阅读框架的源码解析文章。

比如 SpringBean 模块:

  • 需求:有一套通用机制去创建和装配应用所需要的完整的 Bean 实例,使得应用无需关注 Bean 实例的创建和管理,只要按需获取;

  • 设计目标:根据指定的配置文件或注解,生成和存储应用所需要的装配完整的 Bean 实例,并提供多种方式来获取 Bean 实例;

  • 核心优势:支持多种装配方式、自动装配、依赖关系自动注入;支持不同作用域的 Bean 实例创建和获取;稳定高效;

  • 适用场景:有大量的 Bean 需要创建,这些 Bean 存在复杂的依赖关系;

  • 基本原理:反射机制 + 缓存;

  • 算法流程:创建 bean 工厂对象 -> 扫描资源路径,获得 bean 的 class 文件 -> 生成 bean 定义的 beanDefinition 实例 -> 根据 beanDefinitioin 实例创建 bean 实例并缓存到 bean 工厂对象 -> 依赖自动注入 -> 执行钩子方法 -> 完整的 bean 实例准备就绪

  • 技术难点:依赖自动装配、循环引用;解决自动依赖注入和循环引用问题需要用到缓存机制。

需求与目标

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

  • 需求是宽泛的,目标是具体的;

  • 目标是需求的一种实现途径,往往是设计一个具备某些关键特性的系统或产品。

目标是功能与质量的结合体;除了功能部分,确定质量指标也是尤为关键的。

对于某个框架来说,需求、适用场景和核心优势,都是可以直接在官网或项目主页获取到的。如何还原框架的设计目标呢?可以从核心优势中获取基本说明,更多的就要从 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 的源码;

  • 优秀博文:优秀博文往往有一些文献引用,可以阅读相关文献引用;

  • 百科与搜索:在维基百科上搜索出处和引用来源;或者使用搜索引擎。

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

耐心与意志

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

可以使用多种辅助手段:

  • 边听音乐边阅读代码;

  • 拉取代码分支,边读边做标记并提交;

  • 阅读原理、架构及源码分析文章。

小结

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

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

(感谢阅读,希望对你所有帮助)
来源:cnblogs.com/lovesqcc/p/14403497.html

程序汪资料链接

程序汪接的7个私活都在这里,经验整理

Java项目分享  最新整理全集,找项目不累啦 04版

堪称神级的Spring Boot手册,从基础入门到实战进阶

卧槽!字节跳动《算法中文手册》火了,完整版 PDF 开放下载!

卧槽!阿里大佬总结的《图解Java》火了,完整版PDF开放下载!

字节跳动总结的设计模式 PDF 火了,完整版开放下载!

欢迎添加程序汪个人微信 itwang007  进粉丝群或围观朋友圈

浏览 10
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报