个人总结 18 条心法奉上,手把手带你阅读开源项目的源码!
这篇文章我准备来聊一聊如何去阅读开源项目的源码。
在聊如何去阅读源码之前,先来简单说一下为什么要去阅读源码,大致可分为以下几点原因:
最直接的原因,就是面试需要,面试喜欢问源码,读完源码才可以跟面试官 battle
提升自己的编程水平,学习编程思想和和代码技巧
熟悉技术实现细节,提高设计能力
...
那么到底该如何去阅读源码呢?这里我总结了 18 条心法,助你修炼神功
学好 JDK
身为一个 Javaer,不论要不要阅读开源项目源码,都要学好 JDK 相关的技术。
所有的 Java 类开源项目,本质上其实就是利用 JDK 已有的类库和关键字实现一种业务功能,所以学会了 JDK 相关的类库是看其它的源码基础。
如果你不懂 JDK,你去阅读源码会发现有太多看不懂的地方,会影响读源码的心情和信心。
学习 JDK 主要包括使用和原理两部分。内容大致包括以下几部分:
集合相关,比如常见的 Map,List,Queue 的实现,包括线程安全与不安全
并发相关,比如 synchronized、volatile、CAS、AQS、锁、线程池、原子类等等
io 相关,包括 bio 和 nio 等等
反射相关
网络编程相关
...
了解设计模式
在一个优秀的开源项目中,设计模式处处存在,所以在你开始阅读源码之前最好先了解一下常见的一些设计模式。当你了解了一些设计模式以后,在源码中遇到了相关的设计模式,你就可以快速明白代码结构的设计,从而以整体的视角去阅读相关代码。
同时,学习设计模式不仅可以帮助我们阅读源码,在日常开发中也可以帮助我们设计出更易于扩展的程序。
学习设计模式的话可以看看《大话设计模式》这本书,如果不想看书也可以找一些视频或者专栏。
之前我也写过一篇关于开源项目中常用的设计模式文章 两万字盘点那些被玩烂了的设计模式 ,有兴趣的小伙伴可以看看。
先从官网入手
官网是介绍开源项目的地方,同时也是学习一个开源项目最开始的地方,通过官网我们可以快速的了解项目,比如:
项目的定位
一些核心概念
功能
使用教程
整体的架构和设计
常见的问题及解答
...
RokcetMQ 官网
当你了解了项目的一些概念、功能等信息之后,如果你在读源码一旦发现了代码是实现这些概念或者功能的足迹,那么能够帮助你更好的理解代码。
熟悉源码模块结构
当你对项目有大致的了解之后,就可以从 Github 上把代码 clone 下来,官网有项目源码的 Github 地址。
当成功拉下来代码之后,就可以对项目源码模块进行简单的分析,熟悉模块结构,分析模块功能,混个眼熟。
如上是 RocketMQ 源码,如果前面阅读过官网相关的一些概念介绍,就大致可以知道这些模块有什么功能。
RocketMQ 概念介绍
比如说,源码中的 broker 模块,官网说 broker 主要是负责消息存储,那么 broker 模块代码块肯定就主要实现了消息存储的功能。
还有些模块可以根据单词的意思进行判断,比如 common 模块,一看就是存储一些公共类的模块,example 模块,就是 RocketMQ 使用代码示例的模块等等。
顺着 demo 开始读
有的小伙伴在读源码的时候不知道从哪里开始读比较合适,最后随便从源码中的某个模块就开始读,读读越来越发现读不下去。
读源码正确的姿势应该是从 demo 开始读。
比如说,现在我想要阅读一下 RocketMQ 生产者是如何发送消息的,整个过程是什么样的,那么我首先至少得写个发送消息的 demo,看看代码是如何写的。
demo 一般可以从官网中查看
RocketMQ 官网发送消息代码示例
除了官网,一般开源项目在源码中也会有相应的 demo,代码放在示例模块,就比如上面提到的 RocketMQ 的 example 模块。
最后还可以通过谷歌搜索一下 demo。
如上是 RocketMQ 生产者发送消息的一个 demo,消息发送源码阅读就从这块代码开始入手,一步一步进入源码中,这就算开始阅读源码了。
带着目的去读
带着目的去读其实很好理解,就拿上面生产者发送消息流程源码来说,读源码的第一个目的其实就是弄懂生产者发送消息的流程。
除了弄懂生产者发送消息,你还可以带着其它目的去读。
比如说,消息发送的核心逻辑是 send 方法实现的,那么除了消息发送,是不是可以去弄懂生产者在启动的过程做了哪些事,也就是 start 方法的作用。
再比如生产者发送消息肯定涉及到网络通信相关的内容,那么了解 RocketMQ 底层网络通信模型是不是也可以算一个目的。
当你带着这些目的,你读源码就有很强的目的性,读完印象会很深刻。当然如果你最开始想不到这些目的,也没有什么关系,你可以先往下读,在读的过程中再去尝试发现一些其它的目的。
先抓主线,再抓分支
有的小伙伴在读源码的时候,每个方法都使劲一直往下点,最后都不知道代码进入到哪了,这其实是非常不可取的。
正确的方法应该是先抓住主线流程,分支流程先大致看看,知道大概是什么作用,等读完主线之后,再回过头仔细读一下分支代码。
举个例子来说,在 Spring 中,ApplicationContext 在使用之前需要调用一下 refresh 方法,而 refresh 方法就定义了整个容器刷新的执行流程代码。
refresh 方法部分截图
当在读这段代码,你可以先读一读 refresh 中各个方法大致都做了什么,等读完之后,你可以具体的去读每个代码的具体实现,比如说 prepareRefresh 干了什么,obtainFreshBeanFactory 是如何获取到 BeanFactory 的,prepareBeanFactory 又在对 BeanFactory 做了什么事等等。
不要过度抠实现细节
有的小伙伴在阅读的时候特别喜欢深究,想要弄清每行代码是如何实现的,这不仅非常难而且也是不可取的。
就比如说,我们都知道,在 Spring Bean 的生命周期中,当存在基于 xml 的方式来声明 Bean 的方式,Spring 会去解析 xml,生成 BeanDefinition。当你想要了解 Bean 的生命周期过程的时候,其实是没有太大的必要去过度扣 Spring 是如何解析 xml 生成 BeanDefinition 的细节,这对你整体了解 Bean 的生命周期没有太大的意义,只需要知道最终会转换成 BeanDefinition 就可以了。
那什么时候去扣实现细节呢?
当你需要使用到的时候,比如说你遇到了一个 bug 或者是需要扩展
阻碍你理解功能实现的时候
大胆猜
读源码的时候也需要我们发挥一点想象力,去猜一猜功能是如何实现的。猜不是瞎猜,而是基于目前了解的一些知识、技术或者是思想合理地去猜。
就比如说,当你已经知道了 OpenFeign 最终会对每一个 FeignClient 接口生成动态代理对象,之后注入的对象都是代理对象,代理对象中实现了 RPC 的请求之后,那么当你在学习 dubbo 的时候,是不是就可以去猜测注入的 dubbo 接口最终也是一个动态代理对象,并且这个代理对象也实现了 RPC 的请求?
之后你在读代码的时候就需要着重注意发现是否有动态代理生成的代码,这就算是一个目的,一旦发现了动态代理相关的代码,那么这块代码很可能就是 dubbo RPC 实现的核心。
学会看类名
不要小看类名,优秀的代码命名都是见名知意的,所以从类名也可能窥探出这个类的一些蛛丝马迹。
如下列举了几个比较常用的命名习惯
以 Registry 结尾的一般都是存储功能,比如 Spring 中的 SingletonBeanRegistry 就是用来保存单例 Bean 的;Mybatis 中的 MapperRegistry 就是用来保存 Mapper 接口的
以 Support、Helper、s、Util(s)结尾的一般都是工具类
以 Filter,Interceptor 结尾的一般都是拦截作用,一般会配合责任链模式(Chain)使用
以 Event、Listener 结尾的一般都是基于观察者模式实现的事件发布订阅模型
...
除了一些比较通用的命名习惯,也有一些项目独有的一些命名习惯。
比如说 Spring 中常见的以 PostProcessor 结尾的都是扩展接口,实现这些接口可以拿到某个比较核心的组件,从而实现对 Spring 的扩展。
其实很多开源项目的命名都比较偏向 Spring 的命名风格,当你遇到了跟 Spring 的命名比较像的时候,那么可以大胆猜测类的作用。
学会看类结构
类结构也非常重要,他也能够帮助我们窥探类的大致功能。
ApplicationContext
如上图,是 Spring 中 ApplicationContext 的继承体系,当你需要了解 ApplicationContext 的时候,可以先去熟悉一下它的父接口的作用,当你大致弄明白了每个接口的作用,那么 ApplicationContext 有啥作用就大致就清楚了。
除了可以看类继承体系,还可以浏览一下类大致提供了哪些方法,了解对外提供的功能。
类方法通过快捷键 ctrl+F12(mac:fn+command+F12)查看,并且还支持模糊搜索方法名,我本人就非常喜欢这个快捷键
ApplicationContext
总结类的职责
当我们在读完一个类的代码的时候,一定要总结这个类的职责,明白这个类存在的意义。一般情况下一个类核心职责只有一个,遵循单一职责的设计原则。
举个例子,在 RocketMQ 中有一个类 MQClientAPIImpl
MQClientAPIImpl
其实从名字大概看不出这个类主要是有什么功能,但是当我读代码的时候发现每个方法最终都调用 RemotingClient 方法,而 RemotingClient 只有一个实现 NettyRemotingClient,所以从这个实现和类名可以猜出来 RemotingClient 是发送网络请求的客户端,所以当读完 MQClientAPIImpl 源码之后,我就知道了 MQClientAPIImpl 这个类的职责大致是封装参数,然后通过 RemotingClient 向 MQ 发送消息的。
当知道这个类的职责的时候,那么其它地方在调用这个类的方法的时候,就知道大概在做什么事了。
习惯阅读注释
当你在读源码的时候,如果有注释,最好能先读一下注释,这样能帮助你厘清类或者方法的功能,先知道功能,再去读源码就容易多了。
注释一般都是英文,如果看不懂,可以装个插件
相关视频解析:源码阅读技巧
写好注释
俗话说的好记性不如烂笔头,写好注释也是阅读源码中很重要的一个环节,好的注释可以帮助快速回忆起实现细节和功能。
注释并不需要对每行代码都注释,当然如果你愿意也没多大问题,但是注释应包括以下几点内容:
核心类和方法实现的核心功能
核心功能大致的实现逻辑
核心的成员变量的作用
方法中不易读懂的代码实现细节
DefaultMessageStore
如图,是我读 RocketMQ 中对于 DefaultMessageStore 类阅读的注释,这个类是 RocketMQ 中一个非常核心的类,从名字可以看出来跟消息的存储有关。这个类的功能非常多,所以我写了很多注释,列举了这个类主要有哪些功能和这些功能实现的一些细节。
总结思想,及时输出
当你读完某个功能模块的时候,就可以尝试对这块功能实现逻辑或者思想进行总结。
比如说,当你了解了 CAS 思想的时候,你会发现,原来保证线程安全不仅仅可以通过加锁的方式,还可以基于乐观锁的方式来实现。
在总结之后可以输出成一个文档,又或者是流程图。我个人比较喜欢画图,这里推荐两个在线画图工具:
processon
draw.io
processon 我平时就在用,功能多,但是需要收费;draw.io 的话免费,图标和颜色感觉比 processon 好看,平时文章中的贴图就是用 draw.io 画的。
这里多说一句,总结思想还是非常重要的,在我阅读了很多源码之后,我发现很多技术或者功能的实现原理最终都是殊途同归。
提前了解依赖的技术
一般一个开源项目不是所有的技术都是自己实现的,它也会依赖一些其它的框架或者是思想,提前了解这些框架或者是思想,可以帮助你更好地阅读和理清代码。
比如说,RocketMQ 底层是基于 Netty 框架实现网络通信的,当你对 Netty 有所了解,知道 Netty 在启动的时候需要注册一堆 ChannelHandler 用来处理网络请求,那么在读 RocketMQ 底层网络通信功能的时候你就可以去找一下 Netty 启动的代码,看看都注册了哪些 ChannelHandler,然后就知道 RocketMQ 是如何处理和发送请求的。
查阅相关资料
当在阅读源码的时候,对某一块代码功能实现不太清楚的时候,可以通过查阅相关资料来辅助阅读,包括但不限于以下几种通道:
官网
书籍
Github
文章
视频
坚持
最后一点也是最核心的一点就是坚持。只有你长期坚持读源码,不停地思考,总结,不断提升自身技术的广度和深度,找到适合自己的阅读方式,阅读源码才会是越来越容易的一件事。
原文:https://mp.weixin.qq.com/s/kYmZYyaKG_4EJ8ya_0qbIw
如果感觉本文对你有帮助,点赞关注支持一下,想要了解更多 Java 后端,大数据,算法领域最新资讯可以关注我公众号【架构师老毕】私信 666 还可获取更多 Java 后端,大数据,算法 PDF+大厂最新面试题整理+视频精讲
评论