1. 程式人生 > >如何高效地檢視開源專案原始碼?

如何高效地檢視開源專案原始碼?

標籤: 原始碼學習方法


我們為什麼要看原始碼?

這個小標題好像有點扯淡,不過我感覺還是有必要聊一聊。
最近搞 Blazor,手邊常備 AspNetCore 原始碼,遇到問題了就翻原始碼。
然後有同樣關注 Blazor 的同學會一起討論一些問題,我知道的問題會直接分享,我不知道的問題,我就,甩一句,“看原始碼”
然後有的同學炸了,說,“不是每個人都可以像你一樣看原始碼,原始碼不是每個人都能看的,不是每個人都想看原始碼”
當然原話不是這樣,後兩句是我添油加醋的,不過這兩句想必是大部分同學的心聲,心裡害怕看原始碼,覺得看原始碼都是大神才會看的。

看原始碼的正確姿勢

st=>start: 遇到問題
search=>operation: 百度谷歌
isOk=>condition: 能解決嗎?
donotusecode=>end: 好了你可以不用繼續看了
usecode=>end: 分析原始碼解決
st->search->isOk
isOk(yes)->donotusecode
isOk(no)->usecode

看原始碼的錯誤姿勢

st=>start: 聽說看原始碼能學到很多東西,能通過面試,能XXXXX
usecode=>end: 看原始碼
st->usecode

感覺第二個流程影象是在扯犢子,其實第二個圖也並非完全錯誤,只是在強調你不能為了看而看,你要帶著問題去看,但如果這樣的話,那其實又變成了第一張流程圖了

看原始碼的方法

很多同學在滿足第一個流程圖的條件之後,可能看原始碼仍然感覺累,沒有頭緒,這是沒掌握看原始碼的技巧,可能存在以下幾種情況

  • 拿著 github 線上看原始碼,這和拿記事本看原始碼沒好到哪去
  • 真的就拿著記事本看原始碼,或者拿著 VSCode 看原始碼(我不是黑它)
  • 不擅於使用 VS 的相關功能

看原始碼一定先編譯原始碼

不用編譯直接開啟 VS 一把梭,不是很好嗎?
不好,確實是可以開啟的,但是這個時候往往因為沒有編譯過導致 VS 的很多功能不可用,例如轉到定義、查詢引用。
對我而言,編譯原始碼是個艱難的過程,看原始碼反而簡單。特別是 AspNetCore 的原始碼,編譯起來要了半條命。
關於怎麼編譯,每個開源專案都不一樣,有的專案簡單,開啟 VS 直接就能編譯,但是像 AspNetCore 這種,必須嚴格按照官方給的編譯步驟文件,還得保證網路暢通,編譯過程中很容易被勸退。

五大板斧不可少

有很多同學看原始碼的過程讓我比較著急,咋看的呢?
CTRL+F,輸入搜尋詞,全解決方案搜尋,不管是怎樣的情況全是這樣搞,解決方案比較大的話一搜一大堆出來。

也許只是我周圍的同學是這樣的吧。
我看原始碼的過程中常使用這些方法

  • 轉到定義,F12
  • 轉到實現,CTRL + F12
  • 查詢引用
  • 呼叫堆疊
  • 解決方案管理器搜尋

這五大板斧中,除了呼叫堆疊,其他的幾乎都是靜態分析程式碼的必備方法,呼叫堆疊一般屬於動態分析程式碼的辦法。
這五大板斧用好了,只要原始碼的變數命名不要太坑爹,基本上是不需要看註釋的,我看開源專案的原始碼從來不看註釋,開源專案的原始碼的命名一般都是比較好的。

下面我將舉例來分享我看原始碼的過程,把上面這幾個方法用上

問題例項:怎麼根據路由獲取對應的元件?

這是我在編寫 BlazAdmin 時遇到的問題,我需要根據當前路由得到這個路由對應的那個元件,然後做一些操作,我甚至不知道如何搜尋,因為關鍵詞不好搞,隨便搜了幾個也沒找到答案。

下面開始分享我針對這個問題的解決思路,下載編譯 AspNetCore 原始碼的步驟我就不寫了

第一個問題,從哪兒開始?

萬事開頭難,剛開始看原始碼更難,無頭蒼蠅的感覺。
我們的需求是,怎麼根據路由獲取對應的元件,這裡面有兩個關鍵詞,一個是路由,一個是元件
我們換位思考一下,假如由我們來開發這個功能,我們會寫哪兩個類?
答案應當很明顯,RouterComponent,當然也許不是這麼命名的,都有可能。但好像知道這兩個名字了還是沒啥用?根本問題在於 AspNetCore 解決方案太多,我們壓根不知道應該開啟哪個解決方案,也就無從搜尋這兩個類名。
我的辦法是,也許這兩個類我們是可以直接使用的,那麼如果可以直接用的話,我們應該可以在程式碼的任意地方呼叫這兩個類,那麼我們就試試。
在我們自己專案的 Program.cs 檔案中,隨便找個地方,我們首先嚐試 Router 關鍵字。

好像有戲,我們已經看到名稱空間了,那麼根據這個名稱空間,我們嘗試找到對應的解決方案,這個名稱空間的關鍵詞是什麼呢?Microsoft?AspNetCore?這兩個關鍵詞太大眾化了,那麼只剩下兩個,Components 和 Routing,我們一個一個來


沒啥好說的,既然找到了,管它是不是,先開啟看看再說。

我們直接在解決方案管理器中搜索這 Router 這個類。

我們的入口點已經顯而易見了

第二個問題,這個類是如何找到 Component 的?

這個類中有這麼些方法

根據方法猜功能,沒錯,看原始碼全靠猜,猜錯了換條路繼續

OnAfterRenderAsync 這個應該不是
OnLocationChanged 這裡面都有些啥?

這裡面呼叫了 Refresh 方法,F12 跟進去

開始發暈了,咋這麼多呢,而且還不明顯,我們一行行看,一行行猜
看第五第六行,這兩行有點意思,跟當前路由有關,也許是我們想要的
F12 進 RouteContext,看看裡面有啥

好吧啥也沒有,我們繼續看第六行,F12 進 Route 方法

越來越像了,Match 方法裡面又是什麼呢?F12 進去

這個方法中看起來也不像那麼回事,不過還是有點有用資訊,最後一句程式碼將 Handler 賦值了一下,但問題是 Handler 是啥?看這命名有點像當前路由的處理器,那麼這個處理器可能就是我們要找的 Component,從構造方法中可以看出這個 Handler 是由構造方法傳過來的,構造方法可以看到還有一個引用,意味著有地方在顯示呼叫,那麼我們通過查詢引用跳過去看看

看來這就是我們要找的了,越來越近了,這裡應該是在分析路由模板,那麼路由模板應該是呼叫這個方法的地方傳過來的,再次查詢引用跳過去

兩個地方在調,咋辦?明顯可以看出第一個地方是單元測試調的,所以其實只有第二個地方在調

Template 就是路由的模板,而這個模板是由 RouteAttribute 特性來得到的,而這個特性是標記在每一個元件的 Type 上的,也就是說,只要獲取所有元件的 Type,就可以拿到所有元件對應的路由,這樣一來,我的問題解決了

總結

  • 大膽猜測,小心求證
  • 換位思考,如果是我,我會怎麼命名,這個常用於尋找分析入口點
  • 英語別太差,不過一般來說你堅持看原始碼英語會提升的
  • 不要 CTRL + F,不要 CTRL + F,不要 CTRL + F,多用 F12,查詢引用
  • 在某些特殊情況下,例如別人都是通過反射呼叫的,那麼就只能 CTRL + F 了

可以看到,解決這個問題的過程幾乎全是靠猜,猜錯了就回過頭去猜下一個,猜對了那就對了。所以如果說某個開源專案的命名太糟糕,那就確實不好找了。當你看原始碼看多了之後,你就不需要猜了,因為你已經知道命名是啥了。

在這個示例中,我們沒有使用轉到實現,因為其實這個示例所涉及到的程式碼還是簡單的,沒有涉及到太多的多型,因此不需要轉到實現。如果涉及到多型,那麼過程會複雜很多,因為要看的路徑太多,但總歸是能看完的,相比用谷歌去搜索半天壓根就沒有頭緒,花點時間猜原始碼還是值的。