關於閱讀開源項目的源碼,有哪些經驗值得分享?
希望能夠分享一下讀開源代碼時遇到的問題,好用的工具,幫助你理解的文檔,收穫的知識等等這些經驗。
我先說下自己的經歷吧。
我比較完整的讀過的有wordpress,ttserver(tokyotyrant+tokyocabinet),memcached,redis的源碼,最近正在系統地讀nginx的源碼。
相信重複的力量
讀源碼過程中最深的一點體會就是,重複重複再重複。當年讀ttserver源碼是剛入行,本來對C就不熟,ttserver里有很多比較奇怪的代碼習慣,於是硬著頭皮vim一行一行一遍一遍地看,有些不懂的地方,看十來遍,還真就懂了,可能算是量變引發質變吧。
從數據結構入手,先想後讀
然後後來我學聰明了一點,不再從main函數一行一行的開始,學著先讀 .h 文件,了解數據結構,結合能找著的文檔,腦補作者的思路,在這個基礎上再去讀源碼,看自己和作者想的有什麼不一樣,經常能發現自己錯誤或者不周全的地方,偶爾也能竊喜和大師們的不謀而合。
善用工具
再後來發現原來有ctags這類東西,如有神助,加上這時對C語言的基本寫法有了一些了解,讀碼效率頓時提升。今年下半年在lua和nginx中選了先讀nginx代碼,目前結合一些工具做記錄,也頗有成效。
理論與實踐結合
當然,我讀過的所有源碼,都不是為了讀而讀,基本都是我工作中經常會用到,會面對的一些東西。所以這裡有一點也很重要,建議最好先了解你這個東西的功用,再去讀源碼,否則真的很難讀懂。最最好的當然是經常用到的東西,自己對其本身的功能已經很熟悉了,甚至說內部實現機理都能略說一二,這時候讀起來肯定更省力。
教就是學
讀碼之後寫文章也不錯,有時候你以為自己讀懂了,真提筆要寫一個模塊分析的時候,突然發現還有東西是模糊的,或者前後邏輯串起來是矛盾的,這種事也常有。於是我一直認為,能真正寫出一篇讓人看得懂,沒有問題的分析文章,才真的算是讀懂了一個模塊。
謝謝 iammutex 的邀請, 看過一些 c 和 java 的項目,說說經驗, 想到哪裡說到哪裡.
一般開源項目, 如果這個項目你很熟悉經常用, 那麼你直接從 main 入手沒問題.
如果你不熟悉或者代碼量很大, 最好從代碼的 example 代碼 或者 client 的代碼入手比較容易. 這些代碼直接 gdb 進去就可以調試運行了, 客戶端的功能搞清楚了,會用了, 恐懼感就降下去了, 再看服務端就容易了.
看 c 代碼要 關注主體核心 struct , 整個server, client 可能都是圍繞整個 struct 運行起來的, 這個struct 的指針 傳來傳去. 而看 java 代碼 就需要關注最主要的 interface, 通過 interface
看繼承類.
有的人喜歡微觀從main開始看,有的喜歡宏觀先看數據結構(介面), 這個都可以.
另外看網路代碼,最要關注的就是 何時 listen, 何時 accept, epoll_ctl 注入的幾個函數是誰,對應java 里就是 selector 之後的幾個讀寫函數. 找到這幾個函數,基本網路框架就理解了, 網路模型就那麼幾種.
看網路協議包處理函數可以讓你更好的理解乾巴巴的協議.
看 help 函數 可以更快的知道 如何使用.
如果代碼不好調試, 根據程序運行的日誌輸出, 在代碼里找日誌輸出點也是一種方法.
自己寫一個 cscope.sh 腳本, 放到path里, 代碼下載下來在主目錄運行一下就可以開看了.
find . -name "*.h" -o -name "*.c" -o -name "*.py" -o -name "*.lua" -o -name "*.erl" -o -name "*.hrl" -name "*.java" &>&> cscope.files
cscope -bkq -i cscope.files
ctags -R
ctags -I __THROW --langmap=c:+.h --languages=c --c-kinds=+p --if0=yes --exclude=java --file-scope=yes -f systemtags /usr/include/* /usr/include/sys/*
用 gdb 看代碼, 反覆進去退出, 寫個 macro file 也是不錯的
b main
r --user=mysql --gdb --basedir=/usr/local/mysql --datadir=/usr/local/mysql/var --log-error=/usr/local/mysql/var/hoterran-laptop.err --pid-file=/usr/local/mysql/var/hoterran-laptop.pid
set scheduler-locking off
gdb -x macro-file libexec/mysqld
gdb --args 挺好用的, 很多人喜歡在 gdb 里輸入參數多麻煩阿.
vim 的 ctags, cscop只能利用symbol 來查找還是弱了一點, 用 eclipse 看 java 就更加方便了, 看 擴展類, 看調用關係都是一個右鍵可以搞定的. java 項目可以利用 maven 把依賴的jar的 source 都下下來.
我一般習慣左邊顯示器開代碼(vim, eclipse). 右邊我一般會開個 xmind, dia 或者 luchidchart, griffly 畫畫uml流程時序圖什麼的.尤其多線程的程序, 各種 lock 和 queue 調用, 你沒有圖比較難理清楚思路. 紙筆有的時候更加效果.
個人覺得代碼比看書更應該有產出, 我們可以看閑書一掃而過, 看代碼最好還是記點筆記, 畫個圖. 方便回憶,方便continued.
另外@李創@劉煒 同學都是看代碼的高手,大家可以多關注他們的博客.建議如下:
1:基本的語言語法知識。如果C語言的指針都雲里霧裡,那讀開源的C項目沒有任何意義。
2:數據結構和演算法。程序的靈魂,可以通過讀開源項目慢慢領會。
3:項目框架。先從整體了解開源項目,那樣讀的時候會事半功倍。
4:模塊分解。分模塊分層次的仔細閱讀。。
閱讀 jQuery 源碼的18個驚喜
很多時候程序的運行時行為可以側面印證自己對程序靜態語義的理解,使得代碼閱讀更加高效和有針對性。 所以需要動態追蹤工具啦! agentzh有一篇非常好的中文介紹, 在這裡動態追蹤技術漫談。 英文材料首推Netflix的Brendan Gregg的博客。
很多動態追蹤的工具可以生成call graph或者flame graph, 這些圖經常可以當作程序的流程圖來看(雖然並不準確)。閱讀別人的代碼作為開發人員是一件經常要做的事情。一個是學習新的編程語言的時候通過閱讀別人的代碼是一個最好的學習方法,另外是積累編程經驗。如果你有機會閱讀一些操作系統的代碼會幫助你理解一些基本的原理。還有就是在你作為一個質量保證人員或一個小領導的時候如果你要做白盒測試的時候沒有閱讀代碼的能力是不能完成相應的任務。最後一個就是如果你中途接手一個項目的時候或給一個項目做售後服務的時候是要有閱讀代碼的能力的。
收集所有可能收集的材料
閱讀代碼要做的第一件事情是收集所有和項目相關的資料。比如你要做一個項目的售後服務,那麼你首先要搞明白項目做什麼用的,那麼調研文檔、概要設計文檔、詳細設計文檔、測試文檔、使用手冊都是你要最先搞到手的。如果你是為了學習那麼盡量收集和你的學習有關的資料,比如你想學習linux的文件系統的代碼,那最好要找到linux的使用手冊、以及文件系統設計的方法、數據結構的說明。(這些資料在書店裡都可以找到)。
材料的種類分為幾種類型
1.基礎資料。
比如你閱讀turbo c2的源代碼你要有turbo c2的函數手冊,使用手冊等專業書籍,msc 6.0或者java 的話不但要有函數手冊,還要有類庫函數手冊。這些資料都是你的基礎資料。另外你要有一些關於uml的資料可以作為查詢手冊也是一個不錯的選擇
2.和程序相關的專業資料。
每一個程序都是和相關行業相關的。比如我閱讀過一個關於氣象分析方面的代碼,因為裡邊用到了一個複雜的數據轉換公式,所以不得不把自己的大學時候課本 找出來來複習一下高等數學的內容。如果你想閱讀linux的文件管理的代碼,那麼找一本講解linux文件系統的書對你的幫助會很大。
3.相關項目的文檔資料
這一部分的資料分為兩種,一個相關行業的資料,比如你要閱讀一個稅務系統的代碼那麼有一些財務/稅務系統的專業資料和國家的相關的法律、法規的資料是 必不可少的。此外就是關於這個項目的需求分析報告、概要設計報告、詳細設計報告,使用手冊、測試報告等,盡量多收集對你以後的代碼閱讀是很重要的
知識準備
了解基礎知識,不要上來就閱讀代碼,打好基礎可以做到事半功倍的效果
留備份,構造可運行的環境
代碼拿到手之後的第一件事情是先做備份,最好是刻在一個光碟上,在代碼閱讀的時候一點不動代碼是很困難的一件事情,特別是你要做一些修改性或增強性維護的時候。而一旦做修改就可能發生問題,到時候要恢復是經常發生的事情,如果你不能很好的使用版本控制軟體那麼先留一個備份是一個最起碼的要求了。
在做完備份之後最好給自己構造一個可運行的環境,當然可能會很麻煩,但可運行代碼和不可運行的代碼閱讀起來難度會差很多的。所以多用一點時間搭建一個環境是很值得的,而且我們閱讀代碼主要是為了修改其中的問題或做移植操作。不能運行的代碼除了可以學到一些技術以外,用處有限。
找開始的地方
做什麼事情都要知道從那裡開始,讀程序也不例外。在c語言里,首先要找到main()函數,然後逐層去閱讀,其他的程序無論是vb、delphi都要首先找到程序頭,否則你是很難分析清楚程序的層次關係。
分層次閱讀
在閱讀代碼的時候不要一頭就紮下去,這樣往往容易只見樹木不見森林,閱讀代碼比較好的方法有一點象二叉樹的廣度優先的遍歷。在程序主體一般會比較簡 單,調用的函數會比較少,根據函數的名字以及層次關係一般可以確定每一個函數的大致用途,將你的理解作為註解寫在這些函數的邊上。當然很難一次就將全部注 解都寫正確,有時候甚至可能是你猜測的結果,不過沒有關係這些註解在閱讀過程是不斷修正的,直到你全部理解了代碼為止。一般來說採用逐層閱讀的方法可以是 你系統的理解保持在一個正確的方向上。避免一下子扎入到細節的問題上。在分層次閱讀的時候要注意一個問題,就是將系統的函數和開發人員編寫代碼區分開。在 c, c++,java ,delphi中都有自己的系統函數,不要去閱讀這些系統函數,除非你要學習他們的編程方法,否則只會浪費你的時間。將系統函數表示出來,註明它們的作用 即可,區分系統函數和自編函數有幾個方法,一個是系統函數的編程風格一般會比較好,而自編的函數的編程風格一般比較會比較差。從變數名、行之間的縮進、注 解等方面一般可以分辨出來,另外一個是象ms c6++會在你編程的時候給你生成一大堆文件出來,其中有很多文件是你用不到了,可以根據文件名來區分一下時候是系統函數,最後如果你實在確定不了,那就 用開發系統的幫助系統去查一下函數名,對一下參數等來確定即可。
寫註解
寫註解是在閱讀代碼中最重要的一個步驟,在我們閱讀的源代碼一般來說是我們不熟悉的系統,閱讀別人的代碼一般會有幾個問題,1搞明白別人的編程思想不 是一件很容易的事情,即使你知道這段程序的思路的時候也是一樣。2閱讀代碼的時候代碼量一般會比較大,如果不及時寫註解往往會造成讀明白了後邊忘了前邊的 現象。3閱讀代碼的時候難免會出現理解錯誤,如果沒有及時的寫註解很難及時的發現這些錯誤。4不寫註解有時候你發生你很難確定一個函數你時候閱讀過,它的功能是什麼,經常會發生重複閱讀、理解的現象。
好了,說一些寫註解的基本方法:1猜測的去寫,剛開始閱讀一個代碼的時候,你很難一下子就確定所有的函數的功能,不妨採用採用猜測的方法去寫註解,根 據函數的名字、位置寫一個大致的註解,當然一般會有錯誤,但你的註解實際是不但調整的,直到最後你理解了全部代碼。2按功能去寫,別把註解寫成語法說明 書,千萬別看到fopen就寫打開文件,看到fread就寫讀數據,這樣的註解一點用處都沒有,而應該寫在此處開發參數配置文件(****。dat)讀出 系統初始化參數。。。。。,這樣才是有用的註解。3在寫註解的使用另外要注意的一個問題是分清楚系統自動生成的代碼和用戶自 己開發的代碼,一般來說沒有必要寫系統自動生成的代碼。象delphi的代碼,我們往往要自己編寫一些自己的代碼段,還要對一些系統自動生成的代碼段進行 修改,這些代碼在閱讀過程是要寫註解的,但有一些沒有修改過的自動生成的代碼就沒有必要寫註解了。4在主要代碼段要寫較為詳細的註解。有一些函數或類在程 序中起關鍵的作用,那麼要寫比較詳細的註解。這樣對你理解代碼有很大的幫助。5對你理解起來比較困難的地方要寫詳細的註解,在這些地方往往會有一些編程的技巧。不理解這些編程技巧對你以後的理解或移植會有問題。6寫中文註解。如果你的英文足夠的好,不用看這條了,但很多的人英文實在不怎麼樣,那就寫中文註解吧,我們寫註解是為了加快自己的理解速度。中文在大多數的時候比英文更適應中國人。與其寫一些誰也看不懂的英文註解還不如不寫。
重複閱讀
一次就可以將所有的代碼都閱讀明白的人是沒有的。至少我還沒有遇到過。反覆的去閱讀同一段代碼有助於得代碼的理解。一般來說,在第一次閱讀代碼的時候 你可以跳過很多一時不明白的代碼段,只寫一些簡單的註解,在以後的重複閱讀過程用,你對代碼的理解會比上一次理解的更深刻,這樣你可以修改那些註解錯誤的 地方和上一次沒有理解的對方。一般來說,對代碼閱讀3,4次基本可以理解代碼的含義和作用。
運行並修改代碼
如果你的代碼是可運行的,那麼先讓它運行起來,用單步跟蹤的方法來閱讀代碼,會提高你的代碼速度。代碼通過看中間變數了解代碼的含義,而且對 以後的修改會提供很大的幫助
用自己的代碼代替原有代碼,看效果,但在之前要保留源代碼
600行的一個函數,閱讀起來很困難,編程的人不是一個好的習慣。在閱讀這個代碼的時候將代碼進行修改,變成了14個函數。每一個大約是40-50 行左右.
———————————————源碼下載網站—————————————————————
跟你說幾個我常用的源碼下載網站
csdn(中文IT社區)它是集新聞、論壇、群組、Blog、文檔、下載、讀書、Tag、網摘、搜索、.NET、Java、遊戲、視頻、人才、外包、第二書店、《程序員》等多種項目於一體的大型綜合性IT門戶網站,源碼只是其中的一項,但是很實用 裡邊有很多大牛。
DevStore(源碼下載_DevStore)主要是開發者服務平台,彙集國內外眾多第三方開發者服務,為開發者提供從設計開發到運營推廣一站式的解決方案,源碼和服務評測也是亮點,很專業,很實用,這裡邊聚集的都是開發者和PM,可以看看。
站長之家(網站源碼)針對個人站長,企業網管提供的資訊和源碼,包含的語言和類型也比較多。
終於回應了 @David Chang的邀請。
高效源代碼閱讀指南謝謝 @David Chang
贊同 @iammutex 的回答,我能想到的和 @iammutex 很類似。
補充兩點:
一,善用版本控制系統。
大型項目代碼複雜,一個文件幾千行,一個模塊幾萬行,遇到複雜的問題常常需要從多個模塊中找答案,如果藉助版本控制系統,有時候可以節省不少時間。
我用得最多的版本控制系統是git,以git為例,以下兩種用法對我幫助很大:
1. $ git log --follow path/to/source_file.c
上面這行命令可以追蹤source_file.c的變更歷史,根據需要,可以專門閱讀該文件相關的某個特定的commit,很多時候項目第一版的代碼會比最新版的代碼簡單很多,閱讀舊版的代碼可能會比較容易。如果是為了修復bug而讀代碼,這樣的變更歷史有時候可以提示我們哪個commit可能引入了bug。
2. $ git blame path/to/source_file.c
blame是指責的意思,這個命令非常有意思,如果你在閱讀代碼的過程中覺得哪一行比較可疑,可以通過git blame查到該行代碼的作者和commit id,然後單獨閱讀相關的補丁,如果這個補丁確實有bug,那你就可以詛咒作者吃速食麵沒有調料包。反過來,既然知道世界上有git blame這種東西,我們給開源項目提交補丁的時候就尤其要注意double check再double check,如果哪天吃速食麵找不到調料包,記得回去檢查代碼。。。
使用git blame,可以在閱讀代碼的時候,以git commit為單位,而不是以源代碼的行、函數或文件為單位,這樣的好處是,如果一個commit同時改變了多個代碼文件,可以同時注意到,不會漏掉邏輯上相關卻不在同一個文件的代碼。
二,善用grep
1. 假設有一個內部函數叫做internal_func(), 沒有文檔,如何知道這個函數怎麼用?除了閱讀內部函數的實現和閱讀實例,基本上沒有其他方法。對於這種情況,我習慣嘗試下面的做法:
$ git grep -w internal_func -A100 -B100
上面的命令行可以批量找到internal_func的用例,在vim里從上往下先掃一遍,找到儘可能簡單的用例,然後再返回到源文件中閱讀這個用例的上下文,這樣做經常可以提高效率。
推薦你閱讀《Code Reading》這本書,你想要的經驗這裡面都有:http://www.spinellis.gr/codereading/
有中文的翻譯版,但是我不知道質量如何。
閱讀別人的代碼作為開發人員是一件經常要做的事情。一個是學習新的編程語言的時候通過閱讀別人的代碼是一個最好的學習方法,另外是積累編程經驗。如果你有機會閱讀一些操作系統的代碼會幫助你理解一些基本的原理。還有就是在你作為一個質量保證人員或一個小領導的時候如果你要做白盒測試的時候沒有閱讀代碼的能力是不能完成相應的任務。最後一個就是如果你中途接手一個項目的時候或給一個項目做售後服務的時候是要有閱讀代碼的能力的。
知識準備
了解基礎知識,不要上來就閱讀代碼,打好基礎可以做到事半功倍的效果
留備份,構造可運行的環境
代碼拿到手之後的第一件事情是先做備份,最好是刻在一個光碟上,在代碼閱讀的時候一點不動代碼是很困難的一件事情,特別是你要做一些修改性或增強性維護的時候。而一旦做修改就可能發生問題,到時候要恢復是經常發生的事情,如果你不能很好的使用版本控制軟體那麼先留一個備份是一個最起碼的要求了。
在做完備份之後最好給自己構造一個可運行的環境,當然可能會很麻煩,但可運行代碼和不可運行的代碼閱讀起來難度會差很多的。所以多用一點時間搭建一個環境是很值得的,而且我們閱讀代碼主要是為了修改其中的問題或做移植操作。不能運行的代碼除了可以學到一些技術以外,用處有限。
找開始的地方
做什麼事情都要知道從那裡開始,讀程序也不例外。在c語言里,首先要找到main()函數,然後逐層去閱讀,其他的程序無論是vb、delphi都要首先找到程序頭,否則你是很難分析清楚程序的層次關係。
分層次閱讀
在閱讀代碼的時候不要一頭就紮下去,這樣往往容易只見樹木不見森林,閱讀代碼比較好的方法有一點象二叉樹的廣度優先的遍歷。在程序主體一般會比較簡 單,調用的函數會比較少,根據函數的名字以及層次關係一般可以確定每一個函數的大致用途,將你的理解作為註解寫在這些函數的邊上。當然很難一次就將全部注 解都寫正確,有時候甚至可能是你猜測的結果,不過沒有關係這些註解在閱讀過程是不斷修正的,直到你全部理解了代碼為止。一般來說採用逐層閱讀的方法可以是 你系統的理解保持在一個正確的方向上。避免一下子扎入到細節的問題上。在分層次閱讀的時候要注意一個問題,就是將系統的函數和開發人員編寫代碼區分開。在 c, c++,java ,delphi中都有自己的系統函數,不要去閱讀這些系統函數,除非你要學習他們的編程方法,否則只會浪費你的時間。將系統函數表示出來,註明它們的作用 即可,區分系統函數和自編函數有幾個方法,一個是系統函數的編程風格一般會比較好,而自編的函數的編程風格一般比較會比較差。從變數名、行之間的縮進、注 解等方面一般可以分辨出來,另外一個是象ms c6++會在你編程的時候給你生成一大堆文件出來,其中有很多文件是你用不到了,可以根據文件名來區分一下時候是系統函數,最後如果你實在確定不了,那就 用開發系統的幫助系統去查一下函數名,對一下參數等來確定即可。
寫註解
寫註解是在閱讀代碼中最重要的一個步驟,在我們閱讀的源代碼一般來說是我們不熟悉的系統,閱讀別人的代碼一般會有幾個問題,1搞明白別人的編程思想不 是一件很容易的事情,即使你知道這段程序的思路的時候也是一樣。2閱讀代碼的時候代碼量一般會比較大,如果不及時寫註解往往會造成讀明白了後邊忘了前邊的 現象。3閱讀代碼的時候難免會出現理解錯誤,如果沒有及時的寫註解很難及時的發現這些錯誤。4不寫註解有時候你發生你很難確定一個函數你時候閱讀過,它的功能是什麼,經常會發生重複閱讀、理解的現象。
好了,說一些寫註解的基本方法:
1.猜測的去寫,剛開始閱讀一個代碼的時候,你很難一下子就確定所有的函數的功能,不妨採用採用猜測的方法去寫註解,根 據函數的名字、位置寫一個大致的註解,當然一般會有錯誤,但你的註解實際是不但調整的,直到最後你理解了全部代碼。
2.按功能去寫,別把註解寫成語法說明 書,千萬別看到fopen就寫打開文件,看到fread就寫讀數據,這樣的註解一點用處都沒有,而應該寫在此處開發參數配置文件(****。dat)讀出 系統初始化參數。。。。。,這樣才是有用的註解。
3.在寫註解的使用另外要注意的一個問題是分清楚系統自動生成的代碼和用戶自 己開發的代碼,一般來說沒有必要寫系統自動生成的代碼。象delphi的代碼,我們往往要自己編寫一些自己的代碼段,還要對一些系統自動生成的代碼段進行 修改,這些代碼在閱讀過程是要寫註解的,但有一些沒有修改過的自動生成的代碼就沒有必要寫註解了。
4.在主要代碼段要寫較為詳細的註解。有一些函數或類在程序中起關鍵的作用,那麼要寫比較詳細的註解。這樣對你理解代碼有很大的幫助。
5.對你理解起來比較困難的地方要寫詳細的註解,在這些地方往往會有一些編程的技巧。不理解這些編程技巧對你以後的理解或移植會有問題。
6.寫中文註解。如果你的英文足夠的好,不用看這條了,但很多的人英文實在不怎麼樣,那就寫中文註解吧,我們寫註解是為了加快自己的理解速度。中文在大多數的時候比英文更適應中國人。與其寫一些誰也看不懂的英文註解還不如不寫。
重複閱讀
一次就可以將所有的代碼都閱讀明白的人是沒有的。至少我還沒有遇到過。反覆的去閱讀同一段代碼有助於得代碼的理解。一般來說,在第一次閱讀代碼的時候 你可以跳過很多一時不明白的代碼段,只寫一些簡單的註解,在以後的重複閱讀過程用,你對代碼的理解會比上一次理解的更深刻,這樣你可以修改那些註解錯誤的 地方和上一次沒有理解的對方。一般來說,對代碼閱讀3,4次基本可以理解代碼的含義和作用。
運行並修改代碼
如果你的代碼是可運行的,那麼先讓它運行起來,用單步跟蹤的方法來閱讀代碼,會提高你的代碼速度。代碼通過看中間變數了解代碼的含義,而且對 以後的修改會提供很大的幫助
用自己的代碼代替原有代碼,看效果,但在之前要保留源代碼
600行的一個函數,閱讀起來很困難,編程的人不是一個好的習慣。在閱讀這個代碼的時候將代碼進行修改,變成了14個函數。每一個大約是40-50 行左右.
———————————————源碼下載網站—————————————————————
跟你說幾個我常用的源碼下載網站
csdn(中文IT社區)它是集新聞、論壇、群組、Blog、文檔、下載、讀書、Tag、網摘、搜索、.NET、Java、遊戲、視頻、人才、外包、第二書店、《程序員》等多種項目於一體的大型綜合性IT門戶網站,源碼只是其中的一項,但是很實用 裡邊有很多大牛。
DevStore(源碼下載)主要是開發者服務平台,彙集國內外眾多第三方開發者服務,為開發者提供從設計開發到運營推廣一站式的解決方案,源碼和服務評測也是亮點,很專業,很實用,這裡邊聚集的都是開發者和PM,可以看看。
站長之家(網站源碼)針對個人站長,企業網管提供的資訊和源碼,包含的語言和類型也比較多。
我讀源碼時主要有幾個步驟
1. 先使用(雖然是廢話,不過這一步是必須得,都不知道有什麼功能還怎麼讀)
2. 找到切入點
也就是找到程序的入口點,從哪裡開始,會調用哪些生命周期等。
3. 記錄調用過程、依賴關係
這個就必須要畫圖了,不論使用比畫還是使用軟體畫,都是必須的。一個稍微大點的項目依賴關係錯綜複雜,不記錄下來根本不可能讀懂。
4. 猜
在閱讀的過程中要猜各個函數的作用,然後輸出log驗證。然後繼續猜猜猜,直到猜到自己想要的結果。
之前的時候也回到過類似的問題,這次借著這個機會,重新梳理總結下:
目前為止,個人完整看過了Nginx/Libevent/Redis/Leveldb/Rocksdb/Qdb/RebornDB,涉及很多實現細節;看過了MongoDB/Memcached/SSDB的部分代碼,主要是架構,還有其它一些小組件,類似json-cpp/Lua這種。
現在回想下,覺的c開源項目首推Nginx,代碼簡潔、風格不錯,內存使用極致,作者功力深厚;
c++ 的話可從Leveldb入手,單機版代碼精簡,光靠jeff dean就是招牌,而且google的c++代碼風格十分好,還有個c++ style,值的學習~~
閱讀開源代碼最關鍵的一點還是,你閱讀開源代碼的目的是什麼?學習代碼技巧or架構設計or數據結構or演算法等等。。。只有帶著一定的目的性去看開源代碼,才能快速成長與汲取有價值的東西,不過閑的沒事做,想折騰下看看開源框架,也能收穫到不少東西,至少能學習到它如何通過框架設計來解決一個領域的某一問題~~而且有時還會有意外收穫~~
接下來是如何讀,
1.首先有一定的目的,選擇一個經典開源項目,了解該項目是想解決什麼問題,大概通過什麼方式來解決這個問題,學習官方document,基本就是了解這個一個什麼東西?
2.了解整體模塊架構設計,熟悉底層數據結構設計,梳理主流程,這也是有先後順序的
3. 從主流程輻射到整體架構模塊中的其它模塊,重要性從高到低梳理每一個模塊,是否涵蓋細節根據情況決定,一直到最後系統能run起來~~到這一步,系統框架基本已在腦海里搭建起來~~
總結來說的話,就是先有骨架,然後逐步豐富骨架中的每一個點,最終形成一個有血有肉的系統~~
其實吧,只要系統架構圖及類圖清晰了,其它都是水到渠成,很自然的事~~在閱讀代碼的同時,可以同時參考官方document與前人的分析文章,一邊看源碼,一邊對照著分析,剛開始的時候可能會比較慢,一但你閱讀的開源代碼行數在30W行左右,以後再閱讀開源代碼就會如履平地了~~
閱讀開源代碼的過程,也是一個探索未知的過程,這個過程需要自己積累方法,自己體會,去沉澱修鍊內功,慢慢你就會明顯感覺到自己內功在逐步提高,類似武俠小說中修鍊秘籍一樣~~最終實現量變到質變~~
之前本人一直做的架構工程,最近是在往深度學習方向轉,也正想通過開源深度學習框架切入,要是成功了,應該也算是個正面例子~~
最近完整的把nginx讀了一下分享一下心得。
1、在閱讀源碼之前最好能把程序跑起來,改一改配置文件再跑一跑。
2、搞清楚從main開始的程序流程
3、從parse配置文件角度深看一下配置文件對執行流的影響
4、回調搞不懂?打開gdb step一下就知道了
5、多擼一下3基本程序就明白了
6、嘗試寫一下插件復用一下代碼,哈哈還是很有成就感
如何快速學習一個開源項目源碼?
你有個任務,需要用到某個開源項目;或者老大交代你一個事情,讓你去了解某個東西。怎麼下手呢?如何開始呢?我的習慣是這樣:
1.首先,查找和閱讀該項目的博客和資料,通過google你能找到某個項目大體介紹的博客,快速閱讀一下就能對項目的目的、功能、基本使用有個大概的了解。
2.閱讀項目的文檔,重點關注類似Getting started、Example之類的文檔,從中學習如何下載、安裝、甚至基本使用該項目所需要的知識。
3.如果該項目有提供現成的example工程,首先嘗試按照開始文檔的介紹運行example,如果運行順利,那麼恭喜你順利開了個好頭;如果遇到問題,首先嘗試在項目的FAQ等文檔里查找答案,再次,可以將問題(例如異常信息)當成關鍵詞去搜索,查找相關的解決辦法,你遇到了,別人一般也會遇到,熱心的朋友會記錄下解決的過程;最後,可以將問題提交到項目的郵件列表,請大家幫你看看。在沒有成功運行example之前,不要嘗試修改example。
4.運行了第一個example之後,嘗試根據你的理解和需要修改example,測試高級功能等。
5. 在了解基本使用後,需要開始深入的了解該項目。例如項目的配置管理、高級功能以及最佳實踐。通常一個運作良好的項目會提供一份從淺到深的用戶指南,你並不 需要從頭到尾閱讀這份指南,根據時間和興趣,特別是你自己任務的需要,重點閱讀部分章節並做筆記(推薦evernote)。
6.如果時間允許,嘗試從源碼構建該項目。通常開源項目都會提供一份構建指南,指導你如何搭建一個用於開發、調試和構建的環境。嘗試構建一個版本。
7.如果時間允許並且有興趣,可以嘗試閱讀源碼:
(1)閱讀源碼之前,查看該項目是否提供架構和設計文檔,閱讀這些文檔可以了解該項目的大體設計和結構,讀源碼的時候不會無從下手。
(2)閱讀源碼之前,一定要能構建並運行該項目,有個直觀感受。
(3)閱讀源碼的第一步是抓主幹,嘗試理清一次正常運行的代碼調用路徑,這可以通過debug來觀察運行時的變數和行為。修改源碼加入日誌和列印可以幫助你更好的理解源碼。
(4)適當畫圖來幫助你理解源碼,在理清主幹後,可以將整個流程畫成一張流程圖或者標準的UML圖,幫助記憶和下一步的閱讀。
(5)挑選感興趣的「枝幹」代碼來閱讀,比如你對網路通訊感興趣,就閱讀網路層的代碼,深入到實現細節,如它用了什麼庫,採用了什麼設計模式,為什麼這樣做等。如果可以,debug細節代碼。
(6)閱讀源碼的時候,重視單元測試,嘗試去運行單元測試,基本上一個好的單元測試會將該代碼的功能和邊界描述清楚。
(7)在熟悉源碼後,發現有可以改進的地方,有精力、有意願可以向該項目的開發者提出改進的意見或者issue,甚至幫他修復和實現,參與該項目的發展。
8.通常在閱讀文檔和源碼之後,你能對該項目有比較深入的了解了,但是該項目所在領域,你可能還想搜索相關的項目和資料,看看有沒有其他的更好的項目或者解決方案。在廣度和深度之間權衡。
以上是我個人的一些習慣,我自己也並沒有完全按照這個來,但是按照這個順序,基本上能讓你比較高效地學習和使用某個開源項目。
原文:如何熟悉一個開源項目?
說點JS相關的吧。
讀過(不是通讀)PhantomJS, jQuery, DataTables的源碼。
心得:- 相對於編譯型語言,JS太靈活。Closure, scope chain的代價就是讓代碼閱讀起來更困難,經常幾個call, apply之後就搞不清作用域了。因此「讀」代碼不能光「讀」,稍微掌握點思路之後就加點console.log,或者結合Chrome的devtool單步調試。
- 要搞清楚自己為什麼要讀源碼,我是想改造這個Lib?我是想借鑒他好的地方?不管怎麼樣,好的Lib太多了,要讀完是不可能的。我個人學習最快的方法就是自己設計一個當前Lib無法實現場景,然後自己修改代碼去實現他。
- 不管讀什麼語言的代碼,編程的基礎一定要掌握,設計模式(Design Pattern)非常非常重要。對於比較大型的Lib,經常會有幾個Pattern混用,因此如果設計模式了解太少,讀起來會很困難。當然在讀代碼的同時學習設計模式也是個不錯的方向。
- 對於Open Source的項目(貌似這個是廢話,不open source哪兒來代碼給你看...),建議有精力的情況下,去follow一下整個commit的歷史,看看代碼是怎麼擴展,怎麼健壯起來的。同樣,issues和fix也可以去關注。
大概這幾個,想到再補充
我覺得讀源代碼最大的動力來自你要用這個東西。
我以前也想讀啊,但是怎麼也堅持不下來,隨便看看就不管了。工作了,用到的東西,文檔又少或者不清楚,還是自己看了源代碼才覺得比較保險。
我突然想起來,很多程序員都討厭閱讀代碼。來吧,承認吧! 每個人都喜歡編寫代碼,編代碼是件趣事。 另一方面,閱讀代碼也不容易。 不僅不容易(編註:參見《微軟資深軟體工程師:閱讀代碼不容易》),而且還非常枯燥,咱們要面對這一事實。任何不是你的代碼都不怎樣。(雖然我們沒有說出來,但我們都是這樣想的。)
即便是你自己幾個小時之前寫的代碼,也會看起來很爛。時間越久,看起來越爛。 所以,為什麼你要浪費時間去看其他人的糟糕代碼,而你完全可以利用這段時間編寫你自己的優秀代碼。 其實我們可以一試,幾個小時之後回頭再看,看看你的代碼是否還依舊優秀。 如果你不能吸收前輩大師的經驗知識,那你永遠都無法成為一位大師。
成為大師的方法之一是,找到一位大師,讓其傾囊傳授其所知。 有這種可能么?當然了,有這可能,雖然機會不大,但你必須極其走運。 不過你不必十分走運,因為我們幸運地處於這樣一個職業,一個充滿著大師知識和技能的職業,等待我們去汲取吸收,這些東西就在他們所編寫的代碼中。 你要做的就是去閱讀代碼,當然了,這或許耗時不少,畢竟沒有人坐在那裡給你講解,但這種方法的成效還很高。 打個比方,要想成為一名卓越的木匠,得觀察大量結構優良的傢具。
我喜愛閱讀代碼,我的直覺告訴我,你也會從中獲益頗豐。雖然閱讀過程惱人並煩人,但其回報是非常值得你為之努力的。 說到這個,如果你想成為一名卓越的作家,你會專註於寫作么? 你或許已經嘗試,但你並沒有走得很遠。 大多數的偉大作家也是如饑似渴的讀者,這是一個普遍事實。 在你能寫出任何拿得出手的東西之前,你需要品讀其他偉大作家,吸收不同的風格,看看前輩已嘗試過的東西,從中吸取精華。 你的知識會慢慢增長,你自己的作品最終會透露出些許成熟,你也會找到一種「感覺」。 編寫代碼和寫作沒什麼不同,如果你都沒有閱讀過任何卓越的代碼,你為什麼期望自己能寫出像樣的代碼呢? 你顯然不應該那樣。對於程序員來說,閱讀卓越代碼就如同作家閱讀優秀書籍一樣重要(這話可不是我說的,這是Peter Norvig(Google研究院總監)說的,他非常優秀,大家也要向他學習了)。
即便所有這些都無法讓你信服,那這裡有一個不可置否的事實。 對你作為一名專業開發人員的生存來說,善於閱讀代碼至關重要。 如今,任何有一定規模的項目,都是團隊的成果。所以,你通常要處理、修改和擴展大量不是你寫的代碼。 因此,閱讀代碼可能是你能掌握的最常用並最有用的技能。挺過這個難關,好好掌握。
如何閱讀代碼?像某些人一樣……
我已經記不清有多少次看到程序員(用滑鼠)滾上滾下地看著不熟悉的代碼,幾分鐘過後,他們的臉上浮現出不悅的表情。 他們不久後會宣告說,那代碼不值一讀,為什麼要浪費時間呢?我們只能用其他方法解決問題。 我不確定(他們)在期待什麼,是通過潛移默化來吸收代碼的含義,還是集中精神盯著代碼來得到啟發? 你不能只靠長時間盯著代碼來閱讀代碼,你要理解它並化為己用。 這裡有一些我喜歡用的技巧,雖然這不是一份詳盡的列表,但我發現其中有些特別有用。
1.儘力構建並運行代碼。 這通常是一個簡單的步驟,就像你在看可運行的代碼(這和隨機代碼相反)。 不過,並非總是如此。通過構建和執行代碼,你能從中學到很多上層代碼結構。 說到工作代碼,你是否非常熟悉如何構建你的當前項目? 雖然構建通常非常複雜,但通過構建並生成可執行的代碼,你能學到很多。
2. 不要只注重細節。 你要做的第一件事是,在你正閱讀的代碼中,找到代碼結構和風格的。 首先瀏覽一下代碼,儘力理解不同代碼段要做什麼。這會讓你熟整個代碼的上層結構,你也能領會到你正處理的代碼的一些構思(良好架構和義大利麵條等)。 這時候,你可以找到切入點(不管它是什麼,主函數、servlet或控制器等),並查看代碼如何在那裡分支。 不要在這上面花過多的時間,隨著你愈加熟悉代碼,你可以隨時回來查看。
3. 確信自己理解所有結構。 除非你碰巧是所用編程語言的首席專家,否則該語言有些它能做的事你可能還不知道。當你在瀏覽代碼時,記下所有你或許不熟悉的結構。 如果有很多不熟悉的結構,你要做的下一步非常明顯。 如果你不知道代碼要做什麼,那你就走不了很遠。 即便只有幾個你不熟悉的結構,你應當深入查看。 你現在是在探索你所用編程語言中你以前不知道的東西,為此花幾個小時來閱讀代碼,我也非常樂意。
4. 既然你對大多數結構已有很好了解,那現在是該做些隨機深入研究了。 就像步驟2,開始瀏覽代碼,當這次要挑選一些隨機函數或類,並開始逐行詳細查看。 這是硬仗開始的地方,但也是你要取得主要成功的地方。 這裡的構想,會形成你正在查看的代碼庫的思維模式。 也不要在這上面花過長的時間,但在繼續前行之前,你要儘力並極大吸收一些有內容的代碼塊。 這個步驟,你也可以隨時反覆回過頭來,每次你都會了解更多的背景,並收穫更多。
5. 毫無疑問,在前面這些步驟中,肯定有你困惑的地方,所以這是你做些測試的最佳時間。在測試的時候,你的麻煩可能會更少,同時你也能理解代碼。 我一直感到奇怪,開發人員忽略一套寫得很好很全面的測試代碼,而儘力去閱讀並理解某些代碼。 當然了,有時候並沒有測試。
6. 如果你說沒有測試,那這聽起來是編寫測試的時候了。 (編寫測試)有很多益處,有助於你自己的理解,有助於你提升代碼庫,閱讀代碼時也能編寫代碼,這是該你出手做些事的時候。 即便已經有了測試,通常你也可以編寫一些測試,你總能受益的。 測試代碼通常需要換種方式思考問題,那些你以前不太明了的概念也會變得更清晰。
7. 提取奇特的代碼,使其成為單獨的程序。我發現閱讀代碼是個非常有趣的練習,即便只為節奏變化。 即便你不了解代碼的底層細節,你或許能知道一些代碼在上層結構上要做什麼。 什麼不提取一些特定的函數,單獨列為獨立的程序。 當你在執行小段程序時,調試也會更簡單。反過來說,可能還需要一些額外的步驟,才能理解你正查看的代碼。
8. 代碼不幹凈?有異味? 為什麼不重構它? 我並不建議你重寫整個代碼庫,但重構部分代碼,真的有助於你理解層次上升一層。 把你理解的函數拿出來,改成獨立的函數。 在你知道之前,原來的大函數看起來易管理,你可以在腦海中修改它。 重構允許你把代碼變成自己的,無需完成重寫代碼。 如果有好的測試,有助於重構,但即便你沒有好的測試,抽取你確定的函數並做測試。 即便測試看起來完全不充分,但作為一個開發人員,你得學著相信你的技能,有時候你只需努力去做(重構)。(如果你必須重構,你通常都可以把代碼恢復原狀。)
9. 如果沒什麼能幫上忙,那你就找個閱讀代碼的同伴。或許並非只有你一個人能從這代碼中獲益,所以去找一個人,一起閱讀代碼吧。 但你別找專家,他們會從上層結構上,向你解釋所有東西,你會錯失那些你自己詳細查看代碼時所能學到的細微差別。 然而,如果不見效的話,你也不能理解,有時候,你能做的最好的事就是去問。 向你的同事請教,如果你正在閱讀開源代碼,可以在互聯網上找人問問。 但是你要記住,這是最後一步,而不是第一步。
如果我時間緊迫,需要快速合理地理解某些代碼,並且我只能挑選上述步驟的其中一個,那我會選擇「重構」(即:第8個步驟)。 雖然你能理解的東西不會很多,但那些你領會的東西,你會牢牢記住的。 總之,有件事你需要記在心裡。 如果你新接觸一個重要的代碼庫,你不可能立即能理解它。 這需要數天、數周和數月的潛心努力,接受這個事實。 即便有一位專家和你在一起,也不能明顯地縮短時間(。 然而,當涉及到代碼庫時,如果你能耐心並有條不紊地閱讀(和編寫)代碼,你最終能熟悉項目的方方面面,你能成為大牛。 你或者是逃避閱讀代碼,經常尋求某人幫你講解某事。 我知道我會成為哪一種人。
尋找閱讀代碼的機遇 – 不要錯失
我們喜歡編寫新代碼,是因為我們這次能正確處理問題。 好吧,也許不是這次,但一定是下次。 事實上是,你經常改進你的技術,但你從沒有恰當地處理問題。 這就是編寫新代碼的價值所在,你可以歷練並磨練你的技能,但閱讀和把玩其他人編寫的代碼,(如果沒有更多的價值,)也是有同樣多的價值。 你不僅能從中獲得一些有價值的技術知識,也能收穫領域知識,領域知識通常仍具更多價值(畢竟,代碼是文檔的最終形式)。
即便代碼寫得很神秘,無任何慣例可言,但還是有價值。 你知道我在說的代碼,它幾乎看起來晦澀難懂,但不是有意而為之(因某些原因,Perl語言代碼通常是這樣的)。 不管什麼時候我看到那樣的代碼,我都會這樣想: 把它想像成只有你破譯它後才能學到的東西。 是的,這是主要的痛楚之處,但要接受它,有時候你自己也會因瑣碎的原因而寫出那種使人困惑的代碼(否認沒有用,你知道這是真的)。 好了,如果你花些時間來閱讀那樣的代碼,你更有可能最終寫出同樣的代碼。並不說你將會寫出那樣的代碼,但你有能力寫出那樣的代碼。 最後,態度通常是最重要的(編註:態度決定一切)。 如果你視閱讀代碼為日常繁瑣的工作,那它就是(繁瑣的工作),並且你會逃避,但如果你視其為一個機遇,那好事終將到來。
@碼來網 好多可供閱讀的源碼
分享一下個人經驗:
(1)閱讀開源的代碼未必非要找一個完整的開源工程,從頭到尾地去讀。從日常工作需要的SDK源碼入手,積少成多。每個程序員,不管他是用什麼語言來編寫程序,一般來說都要依賴某個語言的SDK,而且通常它們都是開源的。比如Java的JDK,比如C++的STL,再比如Android SDK。一定要把你的開發環境設置成一點擊某個調用的方法就能跳轉進源碼實現。只有這樣,你才能把平常開發的時間利用起來,隨時隨刻都點過去看源碼。有時候我發現某些工程師用的IDE很高級,但就是點擊不到源代碼,很不理解。在這種情況下編程,我會感覺自己像是被蒙上了眼睛一樣。當然有些程序員面對的是一個閉源的系統,比如iOS程序員,似乎這個方法不太適用。不過閉源的SDK通常注釋寫得比較詳細,至少每個調用都把注釋仔細讀懂,千萬杜絕從網上拷貝一段代碼直接貼在項目工程中,其實自己根本不知道這段代碼實現思路是怎樣的。這樣不僅給項目帶來隱患,也不利於自己提高。況且,現在iOS上的Swift的SDK不是也開源了嘛。
(2)多閱讀自己項目組裡他人寫的代碼。這一點似乎跟閱讀開源代碼沒有關係。但是,我看過太多的程序員,在項目中只了解自己負責的那一小塊,對項目其餘部分幾乎一無所知。這樣的程序員,只了解孤立的技術點,即使再去閱讀一個開源工程,也只不過是頭腦中另一個孤立的東西而已,產生不了整體的價值。實際上,一般的業務項目組,會用到各種開源工程,閱讀這些項目代碼,有利於在使用層面了解各個開源工程,體會它們的區別和抽象層次。開源工程一般都是對具體問題抽象之後的解決方案。這裡再說點題外話,在項目組裡從不願意看別人代碼的人,一般也不會去讀開源代碼,甚至連自己之前寫的代碼都不願意重新去讀。
(3)挑自己熟悉的、平常經常使用的開源工程入手,這樣你才能有的放矢。比如,Redis在我們公司內部的存儲和緩存架構中佔有很重要的地位,既然經常使用,就必然會對內部實現產生興趣,所以我就決定讀一讀它的源碼,這樣也能對平常的運維和使用加深理解。
(4)不要讀完就完了。要把體會記下來,總結下來,比如寫成博客。在自己總結的過程中,你會發現理解又加深了一層,而且不容易遺忘,如果讀得仔細甚至還能發現源碼的bug。
(5)如果發現你讀的源碼有bug,或者有不夠優化的地方,不要遲疑,馬上給開源工程提issue或者pull request。比如我在讀Redis源碼的過程中,就給Redis主工程代碼和文檔工程代碼提交過pull request,這是個很自然的過程。
寫起來突然有點剎不住車,最後做個小廣告:
我最近在閱讀Redis的源碼,閱讀的關注點主要在於內部數據結構的實現(包括dict, sds, robj, ziplist, quicklist, skiplist)。最近兩個月在寫一些源碼閱讀的總結,還沒寫完,正在持續的更新中。如果題主也想選擇Redis源碼讀一讀的話,可以參考我的文章。這裡是入口:
Redis內部數據結構詳解(2)——sds
Redis內部數據結構詳解(3)——robj
Redis內部數據結構詳解(4)——ziplist
Redis內部數據結構詳解(5)——quicklist
讀過一點點 NTP 和 Linux 的代碼,真的只是一點點嗯...
最大的感覺是,看這些不比我年輕的代碼真的很長見識,書上能學到語法,真正的實踐還是要看優秀工程實現。比如人家是怎麼組織程序結構的、人家是怎麼寫注釋的、人家是怎麼對付不同數據結構的、以及人家是怎麼小心翼翼優化性能的,等等。
基本上,看CC++的源代碼,如果在linux下,我選擇VIM + Ctag 作為代碼閱讀工具;在windows下,選擇visual studio。
在閱讀代碼之前,會先仔細閱讀開源項目文檔,並且按照文檔配置編譯環境,編譯工程,跑通工程中給予的demo。
在跑通一些demo之後,選擇功能最為單一,或者最能體現開源工程特性的demo作為切入口,一點一點的單步調試代碼。看懂第一個demo是最為費力的,之後會越來越輕鬆。
遇到不理解的地方,首先查找項目文檔,google些相應的資料,逛該工程的論壇(如果有的話),加一些跟該項目有關的QQ群啊什麼的,找些同時在關注項目的志同道合之士,相互交流,這樣基本上就能解決問題了。
最後,盡全力把開源的東西用到自己的個人項目或者日常練習中,不經過實踐,看了多少代碼都是然並卵!!!推薦閱讀: