如何阅读源代码(建议收藏)

读源码是大多数程序员进阶的重要途径,最近了解到很多朋友反馈读了一些源码但是收获不是很大,看了一些源码总是半途而废,有很多困惑。

主要表现为:

整体概览:

在我看来,大多数人读源码收获不大的主要原因如下:

做事要“以终为始”,只有搞清楚读源码我们究竟想得到什么,我们才能避免“走马观花”最终将收获无多的尴尬场景。

读目的:该框架是为了解决什么问题?比同类框架相比的优劣是什么?这对理解框架非常重要。

读注释:很多人读源码会忽略注释。建议大家读源码时一定要重视注释。因为优秀的开源项目,通常某个类、某个函数的目的、核心逻辑、核心参数的解释,异常的发生场景等都会写到注释中,这对我们学习源码,分析问题有极大的帮助。

读逻辑:这里所谓的逻辑是指语句或者子函数的顺序问题。我们要重视作者编码的顺序,了解为什么先写A再写B,背后的原因是什么。

读思想:所谓思想是指源码背后体现出了哪些设计原则,比如是不是和设计模式的六大原则相符?是不是符合高内聚低耦合?是不是体现某种性能优化思想?

读原理:读核心实现步骤,而不是记忆每行代码。核心原理和步骤最重要。

读编码风格:一般来说优秀的源码的代码风格都比较优雅。我们可以通过源码来学习编码规范。

读编程技巧:作者是否采用了某种设计模式,某种编程技巧实现了意料之外的效果。

读设计方案:读源码不仅包含具体的代码,更重要的是设计方案。比如我们下载一个秒杀系统/商城系统的代码,我们可以学习密码加密的方案,学习分布式事务处理的方案,学习幂等的设计方案,超卖问题的解决方案等。因为掌握这些方案之后对提升我们自己的工作经验非常有帮助,我们工作中做技术方案时可以参考这些优秀项目的方案。

很多人读源码不顺利,效果不好,通常都会有些共性。

那么读源码通常会有哪些误区呢?

经常打游戏的朋友都知道,开局直接打Boss无异于送人头。

一般开局先打野,练就了经验再去挑战Boss。

如果开始尝试学习源码就直接拿大型开源框架入手容易自信心受挫,导致放弃。

经常打游戏的朋友也都知道,打游戏要讲究策略,随便瞎打很容易失败。

有些朋友决定读源码,但又缺乏规划,随心所欲,往往效果不太好。

我们知道很多小学生、初高中生,甚至很多大学生学习会出现眼高手低的情况。

有些人做题时并不是先思考,而是先看答案,然后对着答案的思路来理解题目。在这种模式下,大多数题目都理所当然地这么做,会误认为自己真正懂了。但是即使是原题,也会做错,想不出思路。

同样地,很多人读源码也会走到这个误区中。直接看源码的解析,直接看源码的写法,缺乏关键的前置步骤,即先自己思考再对照源码。

学习某个源码之前一定要对源码的基本用法有一个初步了解。

如果对框架没有基本的了解就直接读源码,效果通常不会太好。

一般优秀的开源项目,都会给出一些简单的官方示例代码,大家可以将官方示例代码跑起来,了解基本用法。

大家也可以去GitHub上搜索并拉取某个技术的Demo,某个技术的helloworld项目,快速用起来。

如Dubbo官方文档就给出了快速上手示例代码;轻量级的分布式服务框架jupiterREADME.md就给出了简单的调用示例。一些开源项目给出了多个框架的示例代码,如tutorials。

循序渐进是学习的一大规律。

一方面,可以先尝试阅读较为简单的开源项目源码,比如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的核心函数refresh为例:

比如再去了解第7步的具体编码实现。

从设计者的角度读源码是一条极其重要的思想。体现了“先猜想后验证”的思想。

这样就可以走出“对着答案做题”的误区。

学习源码时不管是框架的整体架构、某个具体的类还是某个函数都要设想如果自己是作者,该怎么设计框架、如何编写某个类、某个函数的代码。

然后再和最终的源码进行对比,发现自己的设想和对方的差异,这样对源码的印象更加深刻,对作者的意图领会的会更加到位。

比如我们封装HTTP请求工具,获取响应后根据响应码判断是否成功,我们可能会这么写:

/***Returnstrueifthecodeisin[200..300),whichmeanstherequestwassuccessfullyreceived,*understood,andaccepted.*/valisSuccessful:Booleanget()=codein200..299发现和自己设想的不同,响应码的范围是[200..300)。

通过这个简单的例子,我们发现自己对HTTP响应码的理解不够全面。

另外通过这个源码我们也了解到了源码注释的重要性,通过源码注释可以清楚明白的理解该函数的意图。

很多优秀的开源项目都会用到各种设计模式,尤其是学习Spring源码。

因此,强烈建议要了解常见的设计模式。

了解常见设计模式的目的、核心场景、优势和劣势等。

要理解设计模式的六大原则:单一职责原则、开闭原则、依赖倒置原则、接口隔离原则、迪米特法则等。

在读源码时注意体会设计模式的六大原则在源码中的体现。

如jupiter1.3.1版本的org.jupiter.serialization.SerializerFactory类就体现了工厂模式。该类通过在静态代码块中使用SPI机制加载序列化方式并存储到serializersmap中,获取时从该map中直接取,实现了已有对象的重用。

大家可以通过《设计模式之禅》、《Java设计模式及实践》、《Headfirst设计模式》等来学习设计模式。

从设计模式角度阅读源码,可以加深对设计模式应用场景的理解,自己编码时更容易选择适合的设计模式来应对项目中的变化。

很多开源项目代码行数非常多,几十万甚至上百万行,想都读完并且都能记下来不太现实。

前面也讲到读源码读什么的问题,个人建议大家读核心的原理,关键特性的实现,高抽象层的几个关键步骤。

不要追求读每一行代码,甚至“背诵”代码,因为工作之后学习的目的更多地是为了运用,而不是为了考试。

我们以Guava源码commitid为5a8f19bd3556的提交版的CacheBuilder源码为例。

如果我们想了解expireAfterWrite函数的的用法。

可以通过读其注释了解该函数的功能,每个参数的含义,异常发生的原因等。对我们学习源码和实际工作中的使用帮助极大。

/***Specifiesthateachentryshouldbeautomaticallyremovedfromthecacheonceafixedduration*haselapsedaftertheentry'screation,orthemostrecentreplacementofitsvalue.*//省略其他**@paramdurationthelengthoftimeafteranentryiscreatedthatitshouldbeautomatically*removed*@paramunittheunitthat{@codeduration}isexpressedin*@returnthis{@codeCacheBuilder}instance(forchaining)*@throwsIllegalArgumentExceptionif{@codeduration}isnegative*@throwsIllegalStateExceptionifthetimetoliveortimetoidlewasalreadyset*/@SuppressWarnings("GoodTime")//shouldacceptajava.time.DurationpublicCacheBuilderexpireAfterWrite(longduration,TimeUnitunit){checkState(expireAfterWriteNanos==UNSET_INT,"expireAfterWritewasalreadysetto%sns",expireAfterWriteNanos);checkArgument(duration>=0,"durationcannotbenegative:%s%s",duration,unit);this.expireAfterWriteNanos=unit.toNanos(duration);returnthis;}通过单元测试学源码同样以学习6.1的函数为例,可以通过findusages找到对应的单元测试。

com.google.common.cache.CacheExpirationTest#testExpiration_expireAfterWrite可以执行在源码中断点,然后执行单元测试,了解源码细节。

publicvoidtestExpiration_expireAfterWrite(){FakeTickerticker=newFakeTicker();CountingRemovalListenerremovalListener=countingRemovalListener();WatchedCreatorLoaderloader=newWatchedCreatorLoader();LoadingCachecache=CacheBuilder.newBuilder().expireAfterWrite(EXPIRING_TIME,MILLISECONDS).removalListener(removalListener).ticker(ticker).build(loader);checkExpiration(cache,loader,ticker,removalListener);}从入口开始学源码如下面是常见的springboot的应用启动主函数:

@SpringBootApplicationpublicclassDemoApplication{publicstaticvoidmain(String[]args){SpringApplication.run(DemoApplication.class,args);}}我们可以从SpringApplication的run函数一直跟下去。

有些朋友可能会说,跟着跟丢了怎么办?

大家可以在源码中打断点,然后通过左下角的调用栈实现源码的跳转,可以通过“dropframe”实现。

可以使用IDEA自带的类图了解核心类的源码的关系。

如下图为fastjson的核心类的类图:

可以使用StacktracetoUMLIDEA插件绘制错误堆栈的时序图,了解源码的执行流程。

推荐大家安装SequenceDiagramIDEA插件,读源码时可以查看调用的时序图,对理解源码调用关系帮助很大。

强烈推荐大家安装codota插件(支持Eclipse、IDEA、AndroidStudio)通过该插件或对应的Java代码搜索网站。

如下图所示,我们安装好codota插件后,想了解org.springframework.beans.factory.support.BeanDefinitionRegistry的registerBeanDefinition函数用法。

这对我们了解该源码的功能和用法有极大的帮助,我们实际开发中也可以多用codota来快速学习如何使用一个函数。

比如我们想研究某段源码的变动,可以拉取源代码,查看Git提交记录。

比如我们想研究某个感兴趣类的演进,直接选取该类,查看提交记录即可。

下图为commons-lang项目的,StringUtils工具类的一个变更记录:

通过变更记录我们可以学习到早期版本有哪些问题,如何进行优化。

issues是学习源码的重要途径,是我们提高开发经验的一个重要途径。

如果我们想深入学习某个开源项目,可以翻阅历史issues。

针对具体的issue中涉及的具体的问题入手了解大家对该问题的看法,学习问题的原因和解决办法。

着重了解有多种方案时作者进行了何种考量,做出了什么取舍。

如AddImmutable*Array.reverse()#3965:

当我们对某些源码设计感到困惑时,可以在Google或者StackOverflow上搜索问题的原因,往往会有些意外收获。

我们在读源码时经常会遇到类似下面的这种写法:

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#startWebServer

看似很小的细节,隐含着一个优化思想。这就需要借助反编译大法,在字节码层面去分析。

详细解读参见《为什么要推荐大家学习字节码?》。

总之,读源码要着重思考,思考为什么这么设计?可能的原因是什么?然后去验证。

THE END
1.VS如何运行测试代码VS如何运行测试代码 简介 现在我们来看看,VS如何运行测试代码。工具/原料 联想2020 Win11 VS 2017 方法/步骤 1 我们可以使用最简单的办法直接点击启动按钮。2 或者我们点击菜单栏中的测试按钮。3 然后我们点击运行二级菜单。4 接下来我们点击所以测试按钮。5 也可以使用它的快捷键。6 同理,还可以使用它的调试命令https://jingyan.baidu.com/article/67662997a06a7d54d51b84e7.html
2.Python中如何实现代码测试问答在Python中,通常可以使用以下几种方法来实现代码测试:1. 单元测试(Unit testing):使用Python内置的unittest模块或第三方库如pytest来编写和运行单元测试,以验证https://www.yisu.com/ask/27942242.html
3.Golang中如何编写test测试代码这个输出结果是否是我们期望的!很多是否都要碰都这种情况,特别是在写一些项目的时候,文件很多代码也多,以前在不知道这样方法的时候就很恼火,以前在遇到这种情况的时候处理方法就是用IDE新建一个环境,然后把要测试的函数直接复制过去,但是这样不仅麻烦很多时候还没有办法做。https://www.jianshu.com/p/3e9aa5d20782
4.MSDN—探索如何创建测试和理解代码MicrosoftLearn创建代码 了解如何创建代码,获取工具,并立即开始学习。 测试代码 阅读有关测试代码的基础知识,获取工具,并立即了解更多信息。 理解代码 学习理解代码的过程,获取工具,并深入了解! 所有开发人员中心 展开表 任务 展开表 理解代码 理解代码涉及多个任务。工具可帮助您以直观形式查看和理解代码的组织、关系和行为。 https://msdn.microsoft.com/zh-cn/gg192976
5.如何创建临时查询,测试运行代码临时查询用于在本地测试代码的实际情况与期望值是否相符或排查代码错误。 背景信息 若您仅需在数据开发(DataStudio),即开发环境,查询数据及相关SQL代码、测试代码的实际运行情况与期望值是否相符,或验证代码的正确性,而无需将数据或SQL代码发布至生产环境并操作生产环境引擎,则可通过 新建临时查询文件实现。https://help.aliyun.com/zh/dataworks/user-guide/create-an-ad-hoc-query
6.四种常见的代码覆盖率测试当您添加新的功能和测试时,增加代码覆盖率百分比可以让您更加确信您的应用程序已经经过了彻底的测试。然而,还有更多的问题有待发现。 四种常见的代码覆盖类型 有四种常见的方法来收集和计算代码覆盖率:函数、行、分支和语句覆盖率。要查看每种类型的代码覆盖率如何计算其百分比,请思考以下计算咖啡成分的代码示例: https://blog.csdn.net/2301_81967508/article/details/144409765
7.谷歌指南:编写可测试的代码谷歌的软件工程师为了保持代码的最佳状态, 写了以下提醒. 缺陷: 构造器做了真正的工作 构造器中的工作包括: 创建/初始化协同类, 和其他service通信, 和设置自身状态的逻辑, 失去了测试的必要, 迫使subclass/mocks去继承不必要的行为. 构造器中太多的工作阻止了测试中的实例化或协同类的修改. https://zhuanlan.zhihu.com/p/146951456
8.JavaIDE如何使用代码覆盖率工具?在软件开发中,代码覆盖率是一个重要的质量指标,它用于衡量测试代码对实际代码的覆盖程度。Java 开发者常常使用 IntelliJ IDEA 这样的 IDE 来进行项目开发和代码测试。然而,有时在使用“Run with Coverage”功能时,会遇到一些问题,导致代码覆盖率无法正常工作。本文将介绍如何在 IntelliJ IDEA 中使用代码覆盖率工具,并https://blog.51cto.com/u_16175520/11996918
9.如何在树莓派4B上通过GoogleMediapipe解决方案实现手势检测6. 编写代码测试 接下来就可以尝试编写代码进行测试了,其中可以参考官方提供Python代码示例来进行学习: import cv2 import mediapipe as mp mp_drawing = mp.solutions.drawing_utils mp_drawing_styles = mp.solutions.drawing_styles mp_hands = mp.solutions.hands https://www.eet-china.com/mp/a148964.html
10.如何在Simulink中实现MIL的自动化测试?汽车测试技术自动化测试代码主要包括3个指令:xlsread、sim、xlswrite。 先用xlsread把测试用例的输入和期望输出读取进来;借助for循环,实现每一个测试用例的单独执行,这里会用到sim(‘XX.mdl’),把执行完的结果与期望的结果对比即可获得测试结果;最后把测试结果通过xlswrite写入excel。 https://www.auto-testing.net/news/show-107747.html
11.通过调试器如何在线测试S7SCL源代码?内容预览:描述: 调试器帮助对S7-SCL源代码进行一个简单调试,如果在编译S7-SCL 源代码过程没有错误发生,能够通过利用断点找出程序逻辑错误,并在运行时监视S7-SCL 代码的函数功能。过程如下面表格中描述: https://www.ad.siemens.com.cn/download/DocumentDetail_2719.html
12.静态代码分析测试静态分析如何保障软件质量和软件安全? 提高代码质量并降低缺陷成本 在开发过程的早期防止代码缺陷,以免它们在软件测试的后期阶段造成昂贵的成本。 满足行业功能安全标准 引入支持IS26262、DO-178C、IEC62304、IEC61508、EN50128等功能安全标准的静态分析解决方案。 https://www.parasoftchina.cn/solutions/static-code-analysis/