這篇文章我準(zhǔn)備來聊一聊如何去閱讀開源項(xiàng)目的源碼。
在聊如何去閱讀源碼之前,先來簡單說一下為什么要去閱讀源碼,大致可分為以下幾點(diǎn)原因:
最直接的原因,就是面試需要,面試喜歡問源碼,讀完源碼才可以跟面試官battle
提升自己的編程水平,學(xué)習(xí)編程思想和和代碼技巧
熟悉技術(shù)實(shí)現(xiàn)細(xì)節(jié),提高設(shè)計(jì)能力
...
那么到底該如何去閱讀源碼呢?這里我總結(jié)了18條心法,助你修煉神功
學(xué)好JDK
身為一個Javaer,不論要不要閱讀開源項(xiàng)目源碼,都要學(xué)好JDK相關(guān)的技術(shù)。
所有的Java類開源項(xiàng)目,本質(zhì)上其實(shí)就是利用JDK已有的類庫和關(guān)鍵字實(shí)現(xiàn)一種業(yè)務(wù)功能,所以學(xué)會了JDK相關(guān)的類庫是看其它的源碼基礎(chǔ)。
如果你不懂JDK,你去閱讀源碼會發(fā)現(xiàn)有太多看不懂的地方,會影響讀源碼的心情和信心。
學(xué)習(xí)JDK主要包括使用和原理兩部分。內(nèi)容大致包括以下幾部分:
集合相關(guān),比如常見的Map,List,Queue的實(shí)現(xiàn),包括線程安全與不安全
并發(fā)相關(guān),比如synchronized、volatile、CAS、AQS、鎖、線程池、原子類等等
io相關(guān),包括bio和nio等等
反射相關(guān)
網(wǎng)絡(luò)編程相關(guān)
...
基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項(xiàng)目地址:https://github.com/YunaiV/ruoyi-vue-pro
視頻教程:https://doc.iocoder.cn/video/
了解設(shè)計(jì)模式
在一個優(yōu)秀的開源項(xiàng)目中,設(shè)計(jì)模式處處存在,所以在你開始閱讀源碼之前最好先了解一下常見的一些設(shè)計(jì)模式。當(dāng)你了解了一些設(shè)計(jì)模式以后,在源碼中遇到了相關(guān)的設(shè)計(jì)模式,你就可以快速明白代碼結(jié)構(gòu)的設(shè)計(jì),從而以整體的視角去閱讀相關(guān)代碼。
同時,學(xué)習(xí)設(shè)計(jì)模式不僅可以幫助我們閱讀源碼,在日常開發(fā)中也可以幫助我們設(shè)計(jì)出更易于擴(kuò)展的程序。
學(xué)習(xí)設(shè)計(jì)模式的話可以看看《大話設(shè)計(jì)模式》這本書,如果不想看書也可以找一些視頻或者專欄。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實(shí)現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項(xiàng)目地址:https://github.com/YunaiV/yudao-cloud
視頻教程:https://doc.iocoder.cn/video/
先從官網(wǎng)入手
官網(wǎng)是介紹開源項(xiàng)目的地方,同時也是學(xué)習(xí)一個開源項(xiàng)目最開始的地方,通過官網(wǎng)我們可以快速的了解項(xiàng)目,比如:
項(xiàng)目的定位
一些核心概念
功能
使用教程
整體的架構(gòu)和設(shè)計(jì)
常見的問題及解答
...
RokcetMQ官網(wǎng)
當(dāng)你了解了項(xiàng)目的一些概念、功能等信息之后,如果你在讀源碼一旦發(fā)現(xiàn)了代碼是實(shí)現(xiàn)這些概念或者功能的足跡,那么能夠幫助你更好的理解代碼。
熟悉源碼模塊結(jié)構(gòu)
當(dāng)你對項(xiàng)目有大致的了解之后,就可以從Github上把代碼clone下來,官網(wǎng)有項(xiàng)目源碼的Github地址。
當(dāng)成功拉下來代碼之后,就可以對項(xiàng)目源碼模塊進(jìn)行簡單的分析,熟悉模塊結(jié)構(gòu),分析模塊功能,混個眼熟。
如上是RocketMQ源碼,如果前面閱讀過官網(wǎng)相關(guān)的一些概念介紹,就大致可以知道這些模塊有什么功能。
RocketMQ概念介紹
比如說,源碼中的broker模塊,官網(wǎng)說broker主要是負(fù)責(zé)消息存儲,那么broker模塊代碼塊肯定就主要實(shí)現(xiàn)了消息存儲的功能。
還有些模塊可以根據(jù)單詞的意思進(jìn)行判斷,比如common模塊,一看就是存儲一些公共類的模塊,example模塊,就是RocketMQ使用代碼示例的模塊等等。
順著demo開始讀
有的小伙伴在讀源碼的時候不知道從哪里開始讀比較合適,最后隨便從源碼中的某個模塊就開始讀,讀讀越來越發(fā)現(xiàn)讀不下去。
讀源碼正確的姿勢應(yīng)該是從demo開始讀。
比如說,現(xiàn)在我想要閱讀一下RocketMQ生產(chǎn)者是如何發(fā)送消息的,整個過程是什么樣的,那么我首先至少得寫個發(fā)送消息的demo,看看代碼是如何寫的。
demo一般可以從官網(wǎng)中查看
RocketMQ官網(wǎng)發(fā)送消息代碼示例
除了官網(wǎng),一般開源項(xiàng)目在源碼中也會有相應(yīng)的demo,代碼放在示例模塊,就比如上面提到的RocketMQ的example模塊。
最后還可以通過谷歌搜索一下demo。
DefaultMQProducerproducer=newDefaultMQProducer("sanyouProducer"); //指定NameServer的地址 producer.setNamesrvAddr("localhost:9876"); //啟動生產(chǎn)者 producer.start(); //省略代碼。。 Messagemsg=newMessage("sanyouTopic","TagA","三友的java日記".getBytes(RemotingHelper.DEFAULT_CHARSET)); //發(fā)送消息并得到消息的發(fā)送結(jié)果,然后打印 SendResultsendResult=producer.send(msg);
如上是RocketMQ生產(chǎn)者發(fā)送消息的一個demo,消息發(fā)送源碼閱讀就從這塊代碼開始入手,一步一步進(jìn)入源碼中,這就算開始閱讀源碼了。
帶著目的去讀
帶著目的去讀其實(shí)很好理解,就拿上面生產(chǎn)者發(fā)送消息流程源碼來說,讀源碼的第一個目的其實(shí)就是弄懂生產(chǎn)者發(fā)送消息的流程。
除了弄懂生產(chǎn)者發(fā)送消息,你還可以帶著其它目的去讀。
比如說,消息發(fā)送的核心邏輯是send方法實(shí)現(xiàn)的,那么除了消息發(fā)送,是不是可以去弄懂生產(chǎn)者在啟動的過程做了哪些事,也就是start方法的作用。
再比如生產(chǎn)者發(fā)送消息肯定涉及到網(wǎng)絡(luò)通信相關(guān)的內(nèi)容,那么了解RocketMQ底層網(wǎng)絡(luò)通信模型是不是也可以算一個目的。
當(dāng)你帶著這些目的,你讀源碼就有很強(qiáng)的目的性,讀完印象會很深刻。當(dāng)然如果你最開始想不到這些目的,也沒有什么關(guān)系,你可以先往下讀,在讀的過程中再去嘗試發(fā)現(xiàn)一些其它的目的。
先抓主線,再抓分支
有的小伙伴在讀源碼的時候,每個方法都使勁一直往下點(diǎn),最后都不知道代碼進(jìn)入到哪了,這其實(shí)是非常不可取的。
正確的方法應(yīng)該是先抓住主線流程,分支流程先大致看看,知道大概是什么作用,等讀完主線之后,再回過頭仔細(xì)讀一下分支代碼。
舉個例子來說,在Spring中,ApplicationContext在使用之前需要調(diào)用一下refresh方法,而refresh方法就定義了整個容器刷新的執(zhí)行流程代碼。
refresh方法部分截圖
當(dāng)在讀這段代碼,你可以先讀一讀refresh中各個方法大致都做了什么,等讀完之后,你可以具體的去讀每個代碼的具體實(shí)現(xiàn),比如說prepareRefresh干了什么,obtainFreshBeanFactory是如何獲取到BeanFactory的,prepareBeanFactory又在對BeanFactory做了什么事等等。
不要過度摳實(shí)現(xiàn)細(xì)節(jié)
有的小伙伴在閱讀的時候特別喜歡深究,想要弄清每行代碼是如何實(shí)現(xiàn)的,這不僅非常難而且也是不可取的。
就比如說,我們都知道,在Spring Bean的生命周期中,當(dāng)存在基于xml的方式來聲明Bean的方式,Spring會去解析xml,生成BeanDefinition。當(dāng)你想要了解Bean的生命周期過程的時候,其實(shí)是沒有太大的必要去過度扣Spring是如何解析xml生成BeanDefinition的細(xì)節(jié),這對你整體了解Bean的生命周期沒有太大的意義,只需要知道最終會轉(zhuǎn)換成BeanDefinition就可以了。
那什么時候去扣實(shí)現(xiàn)細(xì)節(jié)呢?
當(dāng)你需要使用到的時候,比如說你遇到了一個bug或者是需要擴(kuò)展
阻礙你理解功能實(shí)現(xiàn)的時候
大膽猜
讀源碼的時候也需要我們發(fā)揮一點(diǎn)想象力,去猜一猜功能是如何實(shí)現(xiàn)的。猜不是瞎猜,而是基于目前了解的一些知識、技術(shù)或者是思想合理地去猜。
就比如說,當(dāng)你已經(jīng)知道了OpenFeign最終會對每一個FeignClient接口生成動態(tài)代理對象,之后注入的對象都是代理對象,代理對象中實(shí)現(xiàn)了RPC的請求之后,那么當(dāng)你在學(xué)習(xí)dubbo的時候,是不是就可以去猜測注入的dubbo接口最終也是一個動態(tài)代理對象,并且這個代理對象也實(shí)現(xiàn)了RPC的請求?
之后你在讀代碼的時候就需要著重注意發(fā)現(xiàn)是否有動態(tài)代理生成的代碼,這就算是一個目的,一旦發(fā)現(xiàn)了動態(tài)代理相關(guān)的代碼,那么這塊代碼很可能就是dubbo RPC實(shí)現(xiàn)的核心。
學(xué)會看類名
不要小看類名,優(yōu)秀的代碼命名都是見名知意的,所以從類名也可能窺探出這個類的一些蛛絲馬跡。
如下列舉了幾個比較常用的命名習(xí)慣
以Registry結(jié)尾的一般都是存儲功能,比如Spring中的SingletonBeanRegistry就是用來保存單例Bean的;Mybatis中的MapperRegistry就是用來保存Mapper接口的
以Support、Helper、s、Util(s)結(jié)尾的一般都是工具類
以Filter,Interceptor結(jié)尾的一般都是攔截作用,一般會配合責(zé)任鏈模式(Chain)使用
以Event、Listener結(jié)尾的一般都是基于觀察者模式實(shí)現(xiàn)的事件發(fā)布訂閱模型
...
除了一些比較通用的命名習(xí)慣,也有一些項(xiàng)目獨(dú)有的一些命名習(xí)慣。
比如說Spring中常見的以PostProcessor結(jié)尾的都是擴(kuò)展接口,實(shí)現(xiàn)這些接口可以拿到某個比較核心的組件,從而實(shí)現(xiàn)對Spring的擴(kuò)展。
其實(shí)很多開源項(xiàng)目的命名都比較偏向Spring的命名風(fēng)格,當(dāng)你遇到了跟Spring的命名比較像的時候,那么可以大膽猜測類的作用。
學(xué)會看類結(jié)構(gòu)
類結(jié)構(gòu)也非常重要,他也能夠幫助我們窺探類的大致功能。
ApplicationContext
如上圖,是Spring中ApplicationContext的繼承體系,當(dāng)你需要了解ApplicationContext的時候,可以先去熟悉一下它的父接口的作用,當(dāng)你大致弄明白了每個接口的作用,那么ApplicationContext有啥作用就大致就清楚了。
除了可以看類繼承體系,還可以瀏覽一下類大致提供了哪些方法,了解對外提供的功能。
類方法通過快捷鍵 ctrl+F12(mac:fn+command+F12)查看,并且還支持模糊搜索方法名,我本人就非常喜歡這個快捷鍵
ApplicationContext
總結(jié)類的職責(zé)
當(dāng)我們在讀完一個類的代碼的時候,一定要總結(jié)這個類的職責(zé),明白這個類存在的意義。一般情況下一個類核心職責(zé)只有一個,遵循單一職責(zé)的設(shè)計(jì)原則。
舉個例子,在RocketMQ中有一個類MQClientAPIImpl
MQClientAPIImpl
其實(shí)從名字大概看不出這個類主要是有什么功能,但是當(dāng)我讀代碼的時候發(fā)現(xiàn)每個方法最終都調(diào)用RemotingClient方法,而RemotingClient只有一個實(shí)現(xiàn)NettyRemotingClient,所以從這個實(shí)現(xiàn)和類名可以猜出來RemotingClient是發(fā)送網(wǎng)絡(luò)請求的客戶端,所以當(dāng)讀完MQClientAPIImpl源碼之后,我就知道了MQClientAPIImpl這個類的職責(zé)大致是封裝參數(shù),然后通過RemotingClient向MQ發(fā)送消息的。
當(dāng)知道這個類的職責(zé)的時候,那么其它地方在調(diào)用這個類的方法的時候,就知道大概在做什么事了。
習(xí)慣閱讀注釋
當(dāng)你在讀源碼的時候,如果有注釋,最好能先讀一下注釋,這樣能幫助你厘清類或者方法的功能,先知道功能,再去讀源碼就容易多了。
注釋一般都是英文,如果看不懂,可以裝個插件
寫好注釋
俗話說的好記性不如爛筆頭,寫好注釋也是閱讀源碼中很重要的一個環(huán)節(jié),好的注釋可以幫助快速回憶起實(shí)現(xiàn)細(xì)節(jié)和功能。
注釋并不需要對每行代碼都注釋,當(dāng)然如果你愿意也沒多大問題,但是注釋應(yīng)包括以下幾點(diǎn)內(nèi)容:
核心類和方法實(shí)現(xiàn)的核心功能
核心功能大致的實(shí)現(xiàn)邏輯
核心的成員變量的作用
方法中不易讀懂的代碼實(shí)現(xiàn)細(xì)節(jié)
DefaultMessageStore
如圖,是我讀RocketMQ中對于DefaultMessageStore類閱讀的注釋,這個類是RocketMQ中一個非常核心的類,從名字可以看出來跟消息的存儲有關(guān)。這個類的功能非常多,所以我寫了很多注釋,列舉了這個類主要有哪些功能和這些功能實(shí)現(xiàn)的一些細(xì)節(jié)。
總結(jié)思想,及時輸出
當(dāng)你讀完某個功能模塊的時候,就可以嘗試對這塊功能實(shí)現(xiàn)邏輯或者思想進(jìn)行總結(jié)。
比如說,當(dāng)你了解了CAS思想的時候,你會發(fā)現(xiàn),原來保證線程安全不僅僅可以通過加鎖的方式,還可以基于樂觀鎖的方式來實(shí)現(xiàn)。
在總結(jié)之后可以輸出成一個文檔,又或者是流程圖。我個人比較喜歡畫圖,這里推薦兩個在線畫圖工具:
processon
draw.io
processon我平時就在用,功能多,但是需要收費(fèi);draw.io的話免費(fèi),圖標(biāo)和顏色感覺比processon好看,平時文章中的貼圖就是用draw.io畫的。
這里多說一句,總結(jié)思想還是非常重要的,在我閱讀了很多源碼之后,我發(fā)現(xiàn)很多技術(shù)或者功能的實(shí)現(xiàn)原理最終都是殊途同歸。
提前了解依賴的技術(shù)
一般一個開源項(xiàng)目不是所有的技術(shù)都是自己實(shí)現(xiàn)的,它也會依賴一些其它的框架或者是思想,提前了解這些框架或者是思想,可以幫助你更好地閱讀和理清代碼。
比如說,RocketMQ底層是基于Netty框架實(shí)現(xiàn)網(wǎng)絡(luò)通信的,當(dāng)你對Netty有所了解,知道Netty在啟動的時候需要注冊一堆ChannelHandler用來處理網(wǎng)絡(luò)請求,那么在讀RocketMQ底層網(wǎng)絡(luò)通信功能的時候你就可以去找一下Netty啟動的代碼,看看都注冊了哪些ChannelHandler,然后就知道RocketMQ是如何處理和發(fā)送請求的。
查閱相關(guān)資料
當(dāng)在閱讀源碼的時候,對某一塊代碼功能實(shí)現(xiàn)不太清楚的時候,可以通過查閱相關(guān)資料來輔助閱讀,包括但不限于以下幾種通道:
官網(wǎng)
書籍
Github
文章
視頻
堅(jiān)持
最后一點(diǎn)也是最核心的一點(diǎn)就是堅(jiān)持。只有你長期堅(jiān)持讀源碼,不停地思考,總結(jié),不斷提升自身技術(shù)的廣度和深度,找到適合自己的閱讀方式,閱讀源碼才會是越來越容易的一件事。
-
模塊
+關(guān)注
關(guān)注
7文章
2670瀏覽量
47340 -
網(wǎng)絡(luò)通信
+關(guān)注
關(guān)注
4文章
792瀏覽量
29759 -
源碼
+關(guān)注
關(guān)注
8文章
633瀏覽量
29138
原文標(biāo)題:如何去閱讀源碼,我總結(jié)了18條心法
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論