前言
阅读源代码是大多数程序员进步的重要途径。 最近了解到很多同学反映看了一些源码,并没有太大收获。 看了一些源码后,总是半途而废,遇到很多麻烦。
主要表现有:
网上也会有一些关于特定开源代码的系列文章,一般比较繁琐,授“鱼”而不是“钓鱼”。 古语云:授人以鱼,不如授人以渔。
相信大部分朋友都希望得到方法论级别的文章,更系统地介绍如何更好地阅读源码。 因此,在这里我将我的源码阅读经验传授给大家。 相信很多人都会明白问题的根源,并给出一些“意想不到”的实用建议,让那些为阅读源码而困扰的朋友找到出路。
文章重点阐述了以下内容:
总体概述:
为什么很多人阅读源码却收效甚微?
在我看来,大多数人阅读源码并没有多大收获的主要原因如下:
读源码要注意什么?
我们做事一定要“以终为始”。 只有读完源码才知道自己想要得到什么,才能避免出现“到处乱飞”却收效甚微的尴尬场面。
你这样读源码干什么? 我们应该关注什么?
阅读目的:该框架解决了什么问题? 与同类框架相比有何优缺点? 这对于理解框架非常重要。
阅读注释:很多人在阅读源代码时会忽略注释。 建议大家在阅读源码时注意注释。 由于优秀的开源项目,在注释中都会提到某个类的用途、某个功能、核心逻辑、核心参数解释、异常发生场景等,这对于我们学习源码和学习有很大帮助。分析问题。
阅读逻辑:这里所谓的逻辑是指句子或子功能的顺序。 我们一定要注意作者编码的顺序,明白为什么先写A再写B,背后的原因是什么。
阅读思路:所谓的思路是指源码背后的设计原则,比如是否符合设计模式的六大原则? 是否符合高内聚低耦合? 它是否体现了某种性能优化的想法?
阅读原则:阅读核心实现步骤,而不是死记硬背每一行代码。 核心原则和步骤是最重要的。
阅读编码风格:一般来说,优秀源码的代码风格是比较高贵的。 我们可以通过源代码来学习编码标准。
阅读编程方法:作者是否采用了某种设计模式,某种编程方法取得了意想不到的疗效。
阅读设计方案:除了具体的代码之外,更重要的是阅读源码时的设计方案。 比如我们下载一个秒杀系统/商城系统的代码,我们可以学习密码加密方案、分布式事务处理方案、幂等设计方案、超买问题的解决方案等等。因为掌握这种方案对于提高自己的工作经验,我们在工作中制定技术方案时可以参考那些优秀的项目方案。
阅读源码时的错误
很多人读源码不顺利,疗效不好,而且普遍存在一些共性。
这样看源码有哪些误区呢?
开始玩老大
经常玩游戏的同学都知道,一开始就直接打boss,无异于送人头。
通常先从中路开始,积累经验,然后挑战boss。
如果你开始尝试学习源码,从一个小型的开源框架入手,很容易失去信心,甚至放弃。
佛教青年
经常玩游戏的同学也知道,玩游戏需要策略,随便玩很容易失败。
有的同事决定去读源码,但缺乏规划,为所欲为,疗效往往不是很好。
回答提问
我们知道,很多高中生、小学生,甚至很多大学生在学习上都会显得不如前辈。
有些人做题的时候不会先思考,而是先看答案,然后根据答案的思路来理解问题。 在这些模式下,大部分问题都被认为是理所当然的,你会误以为你真的理解了。 而且即使是原题,也会出错,想不出思路。
同样,很多人在阅读源码时也会陷入这样的误区。 直接看源码的分析,直接看源码的写法,缺少关键的后期步骤,即先自己思考再对比源码。
会采用先读源码再读源码的思路
在学习某个源码之前,必须对源码的基本用法有一个初步的了解。
如果对框架没有基本了解就直接阅读源码,效果一般不会很好。
通常优秀的开源项目都会给出一些简单的官方示例代码,你可以运行官方示例代码来了解基本用法。
还可以去GitHub上搜索并拉取某项技术的demo、某项技术的helloworld项目,快速使用。
例如Dubbo官方文档给出了快速入门示例代码; 轻量级分布式服务框架jupiterREADME.md给出了简单的调用示例。 一些开源项目提供了多个框架的示例代码,例如教程。
先易后难
循序渐进是学习的大法则。
一方面,可以先尝试阅读比较简单的开源项目的源码反编译到源码,比如commons-lang、commons-collection、guava、mapstruct等工具类源码。
另外,你也可以尝试寻找某个框架的简单版本。 先从简单的版本入手,看完之后学习小型开源项目会容易很多。
很多人可能会说很难找,虽然大多数著名的开源项目都会有简易版本反编译到源码,但只要认真搜索,大部分都能找到,比如 Spring 的简易版、Dubbo 的简易版。
先整体,后部分
先整体后部分是一个非常重要的认知规则,它凸显了“整体思维”。
如果缺乏对框架的整体了解,很容易陷入局部细节。
先整体后局部包含多重含义,下面介绍几个核心含义。
先看结构再看源码
你可以通过官方的框架文档了解它的整体结构和核心原理,然后去具体的源码。
然而很多人总是忽视这一步。
比如轻量级分布式服务框架jupiter框架的README.md给出了该框架的整体框架:
(图片来自:jupiter项目README.md文档)
对框架有了整体的了解之后,再看具体的实现就会容易很多。
先看项目结构再看源码
先整体后局部,包括先看项目的总体封装,再详细看源码。
(图片来自:jupiter项目结构)
通过项目的应用,如monitor、registry、serialization、example、common等,可以了解包下的代码意图。
先看类的函数列表再看源码
通过IDEA的函数列表功能,可以快速了解某个类所包含的函数,初步了解该类的核心功能。
这些方法在阅读单个源代码时非常有效。
更重要的是,如果能养成查看函数列表的习惯,就能发现很多重要的、被忽视的函数,这些函数在以后的项目开发中很可能会用到。
右图是commons-lang3 3.9版本中StringUtils类的函数列表示意图:
先看整体逻辑再看某个步骤
例如,一个大函数可以分为多个步骤。 我们首先要明白某个步骤的意图,明白为什么先执行子函数1,然后执行子函数2。
然后再去观察一个子功能的细节。
以spring-context 5.1.0.RELEASE版本的IOC容器的核心org.springframework.context.support.AbstractApplicationContext的核心功能刷新为例:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
// 1 初始化前的预处理
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
// 2 告诉子类去 refresh 内部的 bean Factory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
// 3 BeanFactory 的预处理配置
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
// 4 准备 BeanFactory 完成后进行后置处理
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
// 5 执行 BeanFactory 创建后的后置处理器
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
// 6 注册 Bean 的后置处理器
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
// 7 初始化 MessageSource
initMessageSource();
// Initialize event multicaster for this context.
// 8 初始化事件派发器
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
// 9 子类的多态 onRefresh
onRefresh();
// Check for listener beans and register them.
// 10 检查监听器并注册
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
// 11 实例化所有剩下的单例 Bean (非懒初始化)
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
// 12 最后一步,完成容器的创建
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
// 销毁已经常见的单例 bean
destroyBeans();
// Reset 'active' flag.
// 重置 active 标志
cancelRefresh(ex);
// Propagate exception to caller.
// 将异常丢给调用者
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
// 重置缓存
resetCommonCaches();
}
}
}
我们可以高度关注每一步的意义,思考为什么要这样设计,然后单步进入一个子函数来了解具体的实现。
比如去了解第7步的具体编码实现。
/**
* Initialize the MessageSource.
* Use parent's if none defined in this context.
*/
protected void initMessageSource() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
// Make MessageSource aware of parent MessageSource.
if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
if (hms.getParentMessageSource() == null) {
// Only set parent context as parent MessageSource if no parent MessageSource
// registered already.
hms.setParentMessageSource(getInternalParentMessageSource());
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using MessageSource [" + this.messageSource + "]");
}
}
else {
// Use empty MessageSource to be able to accept getMessage calls.
DelegatingMessageSource dms = new DelegatingMessageSource();
dms.setParentMessageSource(getInternalParentMessageSource());
this.messageSource = dms;
beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
if (logger.isTraceEnabled()) {
logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");
}
}
}
从这个子函数来看,“整体”是if和else两个代码块,“部分”是if和else代码块的具体步骤。
从设计师的角度学习源代码
从设计者的角度去阅读源代码是一个非常重要的思想。 凸显了“先推测,后验证”的思想。
这样,你就可以走出“做题去回答”的误区。
在学习源码的时候,无论是框架的整体结构,还是某个具体的类或者某个函数,你都要设想如果你是作者,如何设计框架,如何编译某个类或者某个函数的代码一定的功能。
然后和最终的源码进行对比,找出自己的想法和对方的差异。 这样,你会对源代码印象更深刻,更好地理解作者的意图。