最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

如何閱讀一份源代碼?

2022-04-20 16:50 作者:Databend  | 我要投稿

閱讀源代碼的能力算是程序員的一種底層基礎(chǔ)能力之一,這個(gè)能力之所以重要,原因在于:

  • 不可避免的需要閱讀或者接手他人的項(xiàng)目。比如調(diào)研一個(gè)開源項(xiàng)目,比如接手一個(gè)其他人的項(xiàng)目。

  • 閱讀優(yōu)秀的項(xiàng)目源碼是學(xué)習(xí)他人優(yōu)秀經(jīng)驗(yàn)的重要途徑之一,這一點(diǎn)我自己深有體會(huì)。
    讀代碼與寫代碼是兩個(gè)不太一樣的技能,原因在于“寫代碼是在表達(dá)自己,讀代碼是在理解別人”。因?yàn)槊鎸?duì)的項(xiàng)目多,項(xiàng)目的作者有各自的風(fēng)格,理解起來需要花費(fèi)不少的精力。

我從業(yè)這些年泛讀、精讀過的項(xiàng)目源碼不算少了,陸陸續(xù)續(xù)的也寫了一些代碼分析的文章,本文中就簡單總結(jié)一下我的方法。

先跑起來

開始閱讀一份項(xiàng)目源碼的第一步,是先讓這個(gè)項(xiàng)目能夠通過你自己編譯通過并且順利跑起來。這一點(diǎn)尤其重要。

有的項(xiàng)目比較復(fù)雜,依賴的組件多,搭建起一個(gè)調(diào)試環(huán)境并不容易,所以并不見得是所有項(xiàng)目都能順利的跑起來。如果能自己編譯跑起來,那么后面講到的情景分析、加上調(diào)試代碼、調(diào)試等等才有展開的基礎(chǔ)。

就我的經(jīng)驗(yàn)而言,一個(gè)項(xiàng)目代碼,是否能順利的搭建調(diào)試環(huán)境,效率大不一樣。

跑起來之后,又要盡量的精簡自己的環(huán)境,減少調(diào)試過程中的干擾信息。比如,Nginx 使用多進(jìn)程的方式處理請(qǐng)求,為了調(diào)試跟蹤 Nginx 的行為,我經(jīng)常把 worker 數(shù)量設(shè)置為1個(gè),這樣調(diào)試的時(shí)候就知道待跟蹤的是哪個(gè)進(jìn)程了。

再比如,很多項(xiàng)目默認(rèn)是會(huì)帶上編譯優(yōu)化選項(xiàng)或者去掉調(diào)試信息的,這樣在調(diào)試的時(shí)候可能會(huì)有困擾,這時(shí)候我會(huì)修改 makefile 編譯成 -O0 -g,即編譯生成帶上調(diào)試信息且不進(jìn)行優(yōu)化的版本。

總而言之,跑起來之后的調(diào)試效率能提升很多,而在跑起來的前提之下又要盡量精簡環(huán)境排除干擾的因素。

明確自己的目的

盡管閱讀項(xiàng)目源碼很重要,但是并不見得所有項(xiàng)目都需要從頭到尾看的清清楚楚。在開始展開閱讀之前,需要明確自己的目的:是需要了解其中一個(gè)模塊的實(shí)現(xiàn),還是需要了解這個(gè)框架的大體結(jié)構(gòu),還是需要具體熟悉其中的一個(gè)算法的實(shí)現(xiàn),等等。比如,很多人看 Nginx 的代碼,而這個(gè)項(xiàng)目有很多模塊,包括基礎(chǔ)的核心模塊( epoll 、網(wǎng)絡(luò)收發(fā)、內(nèi)存池等)和擴(kuò)展具體某個(gè)功能的模塊,并不是所有這些模塊都需要了解的非常清楚,我在閱讀 Nginx 代碼的過程中,主要涉及了以下方面:

  • 了解 Nginx 核心的基礎(chǔ)流程以及數(shù)據(jù)結(jié)構(gòu)。

  • 了解 Nginx 如何實(shí)現(xiàn)一個(gè)模塊。
    有了這些對(duì)這個(gè)項(xiàng)目大體的了解,剩下的就是遇到具體的問題查看具體的代碼實(shí)現(xiàn)了。

總而言之,并不建議毫無目的的就開始展開一個(gè)項(xiàng)目的代碼閱讀,無頭蒼蠅式的亂看只會(huì)消耗自己的時(shí)間和熱情。

區(qū)分主線和支線劇情

有了前面明確的閱讀目的,就能在閱讀過程中區(qū)分開主線和支線劇情了。比如:想了解一個(gè)業(yè)務(wù)邏輯的實(shí)現(xiàn)流程,在某個(gè)函數(shù)中使用一個(gè)字典來保存數(shù)據(jù),在這里,“字典這個(gè)數(shù)據(jù)結(jié)構(gòu)是如何實(shí)現(xiàn)的”就屬于支線劇情,并不需要深究其實(shí)現(xiàn)。


在這一原則的指導(dǎo)下,對(duì)于支線劇情的代碼,比如一個(gè)不需要了解其實(shí)現(xiàn)的類,讀者只需要了解其對(duì)外接口,了解這些接口的入口、出口參數(shù)以及作用,把這部分當(dāng)成一個(gè)“黑盒”即可。順便一提的是,早年間看到一種? C++ 的寫法,頭文件中只有一個(gè)類的對(duì)外接口聲明,將實(shí)現(xiàn)通過內(nèi)部的 impl 類轉(zhuǎn)移到? C++?文件中,比如:

頭文件:

// test.h
class Test {
public:
?void fun();

private:
?class Impl;
?Impl *impl_;
};

C++文件:

void Test::fun() {
?impl_->fun()
}

class Test::Impl {
public:
?void fun() {
? ?// 具體的實(shí)現(xiàn)
?}
}

這樣的寫法,讓頭文件清爽了很多:頭文件中沒有與實(shí)現(xiàn)相關(guān)的私有成員、私有函數(shù),只有對(duì)外暴露的接口,使用者一目了然就能知道這個(gè)類對(duì)外提供的功能。


“主線”和“支線”劇情在整個(gè)代碼閱讀的過程中經(jīng)常切換,需要閱讀者有一定的經(jīng)驗(yàn),清楚自己在這段代碼的閱讀中哪部分屬于主線劇情。

縱向和橫向

代碼閱讀過程中,分為兩個(gè)不同的方向:

  • 縱向:順著代碼的順序閱讀,在需要具體了解一個(gè)流程、算法的時(shí)候,經(jīng)常需要縱向閱讀。

  • 橫向:區(qū)分不同的模塊進(jìn)行閱讀,在需要首先弄清楚整體框架時(shí),經(jīng)常需要橫向閱讀。
    兩個(gè)方向的閱讀,應(yīng)該交替進(jìn)行,這需要代碼閱讀者有一定的經(jīng)驗(yàn),能夠把握當(dāng)前代碼閱讀的方向。我的建議是:過程中還是以整體為首,在不理解整體的前提之前,不要太過深入某個(gè)細(xì)節(jié)。把某個(gè)函數(shù)、數(shù)據(jù)結(jié)構(gòu)當(dāng)成一個(gè)黑盒,知道它們的輸入、輸出就好,只要不影響整體的理解就暫且放下接著往前看。

情景分析

假如有了前面的基礎(chǔ),已經(jīng)能夠讓項(xiàng)目順利在自己的調(diào)試環(huán)境跑起來了,也明確了自己想了解的功能,那么就可以對(duì)項(xiàng)目代碼進(jìn)行情景分析了。

所謂的“情景分析”,就是自己構(gòu)造一些情景,然后通過加斷點(diǎn)、調(diào)試語句等分析在這些場景下的行為。

以我自己為例,在寫?《 Lua 設(shè)計(jì)與實(shí)現(xiàn)》?時(shí),講解到Lua虛擬機(jī)指令的解釋和執(zhí)行過程中,需要針對(duì)每個(gè)指令做分析,此時(shí)用的就是情景分析的方法。我會(huì)模擬出來使用該指令的 Lua 腳本代碼,然后在程序里斷點(diǎn)調(diào)試這些場景下的行為。

我慣用的做法,是在某個(gè)重要的入口函數(shù)上面加上斷點(diǎn),然后構(gòu)造觸發(fā)場景的調(diào)試代碼,當(dāng)代碼在斷點(diǎn)處停下,通過查看堆棧、變量值等等來觀察代碼的行為。

例如,Lua 解釋器代碼中中,生成 Opcode 最終都會(huì)調(diào)用函數(shù) luaK_code,那么我就在這個(gè)函數(shù)上面加上斷點(diǎn),然后構(gòu)造我想要調(diào)試的場景,只要在斷點(diǎn)處中斷,我通過函數(shù)堆棧就能看到完整的調(diào)用流程:


(lldb) bt
* thread #1: tid = 0xb1dd2, 0x00000001000071b0 lua`luaK_code, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x00000001000071b0 lua`luaK_code
frame #1: 0x000000010000753e lua`discharge2reg + 238
frame #2: 0x000000010000588f lua`exp2reg + 31
frame #3: 0x000000010000f15b lua`statement + 3131
frame #4: 0x000000010000e0b6 lua`luaY_parser + 182
frame #5: 0x0000000100009de9 lua`f_parser + 89
frame #6: 0x0000000100008ba5 lua`luaD_rawrunprotected + 85
frame #7: 0x0000000100009bf4 lua`luaD_pcall + 68
frame #8: 0x0000000100009d65 lua`luaD_protectedparser + 69
frame #9: 0x00000001000047e1 lua`lua_load + 65
frame #10: 0x0000000100018071 lua`luaL_loadfile + 433
frame #11: 0x0000000100000eb9 lua`pmain + 1545
frame #12: 0x00000001000090cd lua`luaD_precall + 589
frame #13: 0x00000001000098c1 lua`luaD_call + 81
frame #14: 0x0000000100008ba5 lua`luaD_rawrunprotected + 85
frame #15: 0x0000000100009bf4 lua`luaD_pcall + 68
frame #16: 0x00000001000046fb lua`lua_cpcall + 43
frame #17: 0x00000001000007af lua`main + 63
frame #18: 0x00007fff6468708d libdyld.dylib`start + 1


情景分析的好處在于:不會(huì)在一個(gè)項(xiàng)目中大海撈針?biāo)频牟檎遥悄軌虬褑栴}縮小到一個(gè)范圍內(nèi)展開來理解。

“情景分析”這一概念不是我想出來的名詞,比如有這么幾本分析代碼的書籍,如:?《Linux內(nèi)核源代碼情景分析》,《Windows內(nèi)核情景分析》。

利用好測試用例

好的項(xiàng)目都會(huì)自帶不少用例,這類型的例子有:etcd、google 出品的幾個(gè)開源項(xiàng)目。

如果測試用例寫的很仔細(xì),那么很值得好好去研究一下。原因在于:測試用例往往是針對(duì)某個(gè)單一的場景,獨(dú)自構(gòu)造出一些數(shù)據(jù)來對(duì)程序的流程進(jìn)行驗(yàn)證。所以,其實(shí)跟前面的“情景分析”一樣,都是讓你從大的項(xiàng)目轉(zhuǎn)而關(guān)注具體某個(gè)場景的手段之一。

厘清核心數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系

雖然說“程序設(shè)計(jì)=算法+數(shù)據(jù)結(jié)構(gòu)”,然后我實(shí)際中的體會(huì),數(shù)據(jù)結(jié)構(gòu)更加重要。

因?yàn)榻Y(jié)構(gòu)定義了一個(gè)程序的架構(gòu),結(jié)構(gòu)定下來了才有具體的實(shí)現(xiàn)。好比蓋房子,數(shù)據(jù)結(jié)構(gòu)就是房子的框架結(jié)構(gòu),如果一間房子很大,而你并不清楚這個(gè)房子的結(jié)構(gòu),會(huì)在這里面迷路。而對(duì)于算法,如果屬于暫時(shí)不需要深究的細(xì)節(jié)部分,可以參考前面“區(qū)分主線和支線劇情”部分,先了解其入口、出口參數(shù)以及作用即可。

Linus 說:“爛程序員關(guān)心的是代碼。好程序員關(guān)心的是數(shù)據(jù)結(jié)構(gòu)和它們之間的關(guān)系?!?/p>

因此,在閱讀一份代碼時(shí),厘清核心的數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系尤其重要。這個(gè)時(shí)候,需要使用一些工具來畫一下這些結(jié)構(gòu)之間的關(guān)系,我的源碼分析類博客中有很多這樣的例子,比如?《Leveldb代碼閱讀筆記》、《Etcd存儲(chǔ)的實(shí)現(xiàn)》?等等。

需要說明的是,情景分析、厘清核心數(shù)據(jù)結(jié)構(gòu)這兩步并沒有嚴(yán)格的順序關(guān)系,不見得是先做某事再做某事,而是交互進(jìn)行的。

比如,你如果現(xiàn)在剛接手某個(gè)項(xiàng)目,需要簡單的了解一下項(xiàng)目,可以先閱讀代碼了解都有哪些核心數(shù)據(jù)結(jié)構(gòu)。理解了之后,如果不清楚某些情景下的流程,可以使用情景分析法??偠灾惶孢M(jìn)行直到解答你的疑問為止。

多問自己幾個(gè)問題

學(xué)習(xí)的過程中離不開交互。

如果閱讀代碼只是輸入 (Input),那么還需要有輸出 (Output)。只有簡單的輸入好比喂東西給你吃,而只有更好的消化才能變?yōu)樽约旱臓I養(yǎng),而輸出就是更好消化知識(shí)的重要手段。

其實(shí)這個(gè)思想很常見,比如學(xué)生上課 (Input) 了需要做練習(xí)作業(yè) (Output),比如學(xué)了算法 (Input) 需要自己編碼練習(xí) (Output),等等。簡而言之,輸出是學(xué)習(xí)過程中的一種及時(shí)反饋,質(zhì)量越高學(xué)習(xí)效率越高。

輸出的手段有很多,在閱讀代碼時(shí),比較建議的是自己能夠多問自己一些問題,比如:

  • 為什么選擇這個(gè)數(shù)據(jù)結(jié)構(gòu)來描述這個(gè)問題?類似的場景下,其他項(xiàng)目是怎么設(shè)計(jì)的?都有哪些數(shù)據(jù)結(jié)構(gòu)做這樣的事 ?

  • 如果由我來設(shè)計(jì)這樣的項(xiàng)目,我會(huì)怎么做?
    等等等等。越是主動(dòng)積極的思考,就越有更好的輸出,輸出質(zhì)量與學(xué)習(xí)質(zhì)量成正比關(guān)系。

寫自己的代碼閱讀筆記

我從開始寫博客,就是寫不少各種項(xiàng)目的代碼解讀類文章,網(wǎng)名? “codedump”? 也源于想把 code 內(nèi)部的實(shí)現(xiàn)原理? dump ?出來”之意。

前面提到學(xué)習(xí)質(zhì)量與輸出質(zhì)量成正比關(guān)系,這是我自己的深刻體會(huì)。也因?yàn)槿绱?,所以才要?jiān)持閱讀源碼之后寫自己的分析類筆記。

寫這類筆記,有以下幾個(gè)需要注意的地方。

雖然是筆記,但是要想象著在向一個(gè)不太熟悉這個(gè)項(xiàng)目的人講解原理,或者想象一下是幾個(gè)月甚至幾年后的自己回頭來看這個(gè)文章。在這種情況下,會(huì)盡量的把語言組織好,循循善誘的解釋。

盡量避免大段的貼代碼。我認(rèn)為在這類文章中,大段貼上代碼有點(diǎn)自欺欺人:就是看上去自己懂了,其實(shí)并不見得。如果真要解釋某段代碼,可以使用偽代碼或者縮減代碼的方式。記?。翰灰云燮廴?,要真的懂了。如果真的想在代碼上加上自己的注釋,我有一個(gè)建議是 fork 出來一份該項(xiàng)目某個(gè)版本的代碼,提交到自己的github上,上面隨時(shí)可以加上自己的注釋并且保存提交。比如我自己注釋的 etcd 3.1.10 代碼:?etcd-3.1.10-codedump,類似的我閱讀的其他項(xiàng)目都會(huì)在 github 上 fork 出一個(gè)帶上 codedump 后綴的項(xiàng)目。

多畫圖,一圖勝千言,使用圖形展示代碼流程、數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系。我最近才發(fā)現(xiàn)畫圖能力也是很重要的能力,自己在從頭學(xué)習(xí)如何使用圖像來表達(dá)自己的想法。

寫作是很重要的基礎(chǔ)能力,我一個(gè)朋友最近教育我,大體的意思是說:如果你在某方面的能力很強(qiáng),如果再加上寫作好、英語好,那么將極大放大你在這方面的能力。而類似寫作、英語這樣的底層基礎(chǔ)能力,不是一撮而就的,需要長時(shí)間保持練習(xí)才可以。而寫博客,對(duì)于技術(shù)人員而言,就是一種很好的鍛煉寫作的手段。

PS:如果很多事情,你當(dāng)時(shí)做的時(shí)候能想到今后面對(duì)這個(gè)輸出的人是你自己,比如自己寫的代碼后面要自己維護(hù)、自己寫的文章后面給自己看,等等的,世界會(huì)美好很多。比如寫技術(shù)博客這些事情,因?yàn)槲以趯懙臅r(shí)候考慮到以后看這份文檔的人可能就是我本人,所以在寫的時(shí)候會(huì)盡量的清晰、易懂,力圖我自己一段時(shí)間后再看到自己的這份文檔時(shí),能夠馬上回憶起當(dāng)時(shí)的細(xì)節(jié),也正是因?yàn)檫@樣,我很少在博客里貼大段的代碼,盡可能的補(bǔ)充圖例。

總結(jié)

以上是我簡單總結(jié)的一些閱讀源碼時(shí)候的手段和注意方法,大體而言有那么幾點(diǎn)吧:

  • 只有更好的輸出才能更好的消化知識(shí),所謂的搭建調(diào)試環(huán)境、情景分析、多問自己問題、寫代碼閱讀筆記等都是圍繞輸出來展開的??偠灾荒芟褚粭l死魚一樣指望著光靠看代碼就能完全理解它的原理,需要想辦法跟它互動(dòng)起來。

  • 寫作是人的基礎(chǔ)硬實(shí)力之一,不僅鍛煉自己表達(dá)能力,還能幫助整理自己的思路。對(duì)程序員而言鍛煉寫作能力的手段之一就是寫博客,越早開始鍛煉越好。

最后,如同任何可以習(xí)得的技能一般,閱讀代碼這種能力也需要長時(shí)間、大量的反復(fù)練習(xí),下一次就從自己感興趣的項(xiàng)目開始鍛煉自己的這種技能吧。


作者:codedump
來源:https://www.codedump.info/post/20200605-how-to-read-code-v2020/


圖片
掃碼關(guān)注公眾號(hào)獲取更多精彩文章~


如何閱讀一份源代碼?的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國家法律
灵川县| 永城市| 元朗区| 绍兴县| 友谊县| 景德镇市| 盐山县| 屯昌县| 简阳市| 马龙县| 大城县| 桃源县| 壤塘县| 崇左市| 临潭县| 禄丰县| 高安市| 商都县| 韩城市| 聂荣县| 库伦旗| 武平县| 铜山县| 紫阳县| 万山特区| 佛坪县| 礼泉县| 库尔勒市| 乌审旗| 大庆市| 手游| 昌宁县| 广昌县| 乳山市| 荣昌县| 光山县| 石河子市| 长春市| 天等县| 泰宁县| 那曲县|