最新互聯網大廠面試真題、Java程序員面試策略(面試前的準備、面試中的技巧)請移步GitHubgit
咱們去閱讀別人的代碼,一般會帶有必定的目的性。完整把一個系統的代碼 「讀懂」 須要極大的精力。因此明確閱讀代碼的目標很重要,由於它決定了你最終可以爲這事付出多大的精力,或者說成本。程序員
大致來講,咱們能夠把目標分爲這樣幾種類型:github
爲何要把咱們的目標搞清楚?面試
由於讀懂源代碼真的很難,它實際上是架構的反向過程。它相似於反編譯,可是並非指令級的反編譯,而是須要根據指令反推更高維的思想。算法
咱們知道反編譯軟件可以將精確軟件反編譯爲彙編,由於這個過程信息是無損的,只是一種等價變換。可是要讓反編譯軟件可以精確還原出高級語言的代碼,這就比較難。由於編譯過程是有損的,大部分軟件實體的名字已經在編譯過程當中被去除了。固然,大部分編譯器在編譯時會同時生成符號文件。它主要用於 debug 用途。不然咱們在單步跟蹤時,debug 軟件就無法顯示變量的名字。數據庫
即便咱們可以拿到符號文件,精確還原出原始的高級語言的代碼仍然很是難。它須要帶必定的模型推理在裏面,經過識別出這裏面咱們熟悉的 「套路」,而後按照套路進行還原。咱們能夠想像一下,「一個精確還原的智能反編譯器」 是怎麼工做的。編程
第一步,它須要識別出所採用的編程語言和編譯器。這一般相對容易,一個很是粗陋的分類器就能夠完成。尤爲是不少編譯器都有 「署名」,也就是在編程出的軟件中帶上本身簽名的習慣。若是假設全部軟件都有署名,那麼這一步甚至不須要訓練與學習。數據結構
第二步,經過軟件的二進制,結合可選的符號文件(沒有符號文件的結果是不少軟件實體,好比類或函數的名字,會是一個隨機分配的符號),加上它對該編譯器的套路理解,就能夠進行反編譯了。架構
編譯器的套路,就如同一我的的行爲,持續進行觀察學習,是能夠造成總結的。這隻須要反編譯程序持續地學習足夠多的該編譯器所產生的樣本。編程語言
我之因此拿反編譯過程來類比,是但願咱們可以理解,閱讀源代碼過程一方面是很難的,另外一方面來講,也是須要有產出的。
有產出的學習過程,纔是最好的學習方式。
那麼閱讀源代碼的產出應該是什麼?答案是,構建這個程序的思路,也就是架構設計。
怎麼作到?
首先,有文檔,必定要先看文檔。若是本來就已經有寫過架構設計的文檔,咱們還要堅持本身經過代碼一步步去反向進行理解,那就太傻了。
可是,必定要記住文檔和代碼很容易發生脫節。因此咱們看到的極可能是上一版本的,甚至是最第一版本的設計。
就算已經發生過變化,閱讀過期的架構設計思想對咱們理解源代碼也會有極大的幫助做用。在這個基礎上,咱們再看源代碼,就能夠相互進行印證。固然若是發生了衝突,咱們須要及時修改文檔到與代碼一致的版本。
看源代碼,咱們首先要作到的是理解系統的概要設計。概要設計的關注點是各個軟件實體的業務範疇,以及它們之間的關係。有了這些,咱們就可以理解這個系統的架構設計的核心脈絡。
具體來講,看源碼的步驟應該是怎樣的呢?
首先,把公開的軟件實體(模塊、類、函數、常量、全局變量等)的規格整理出來。
這一步每每有一些現成的工具。例如,對 Go 語言來講,運行 go doc 就能夠幫忙整理出一個自動生成的版本。一些開源工具例如 doxygen 也可以作到相似的事情,並且它支持幾乎全部的主流語言。
固然這一步只能讓咱們找到有哪些軟件實體,以及它們的規格是什麼樣的。可是這些軟件實體各自的業務範疇是什麼,它們之間有什麼關係?須要進一步分析。
通常來講,下一步我會先看 example、unit test 等。這些屬於咱們研究對象的客戶,也就是使用方。它們可以輔助咱們理解各個軟件實體的語義。
經過軟件實體的規格、說明文檔、example、unit test 等信息,咱們根據這些已知信息,甚至包括軟件實體的名字自己背後隱含的語義理解,咱們能夠初步推測出各個軟件實體的業務範疇,以及它們之間的關係。
接下來,咱們須要進一步證明或證僞咱們的結論。若是證僞了,咱們須要從新梳理各個軟件實體之間的關係。怎麼去證明或證僞?咱們選重點的類或函數,經過看它們的源代碼來理解其業務流程,以此印證咱們的猜想。
固然,若是你可以找到以前作過這塊業務的人,不要猶豫,儘量找到他們而且爭取一個小時左右的交流機會,並提早準備好本身遇到迷惑的問題列表。這會大幅縮短你理解整個系統的過程。
最後,確保咱們正確理解了系統,就須要將結論寫下來,造成文檔。這樣,下一次有其餘同窗接手這個系統的時候,就不至於須要從新再來一次 「反編譯」。
業務系統的概要設計、接口理清楚後,一般來講,咱們對這個系統就初步有譜了。若是咱們是評估第三方模塊要不要採納等相對輕的目標,那麼到此基本就能夠告一段落了。
只有在必要的狀況下,咱們才研究實現機制。剛纔咱們談到系統架構梳理過程當中,咱們也部分涉及了源代碼理解。可是,須要明確的是,前面咱們研究部分核心代碼的實現,其目的仍是爲了確認咱們對業務劃分猜想的正確性,而不是爲了實現機制自己。
研究實現是很是費時的,畢竟系統的 UserStory 數量上就有不少。把一個個 UserStory 的具體業務流程都研究清楚寫下來,是很是耗時的。若是這個業務系統不是咱們接下來重點投入的方向,就不必在這方面去過分投入。
這時候目標就很重要。
若是咱們只是順帶解決一下遇到的 Bug,不管是用第三方代碼遇到的,仍是上級隨手安排的臨時任務,咱們天然把關注點放在要解決的 Bug 自己相關的業務流程上。
若是咱們是接手一個新的業務系統,咱們也沒有精力馬上把全部細節都搞清楚。這時候咱們須要梳理的是關鍵業務流程。
怎麼搞清楚業務流程?
程序 = 數據結構 + 算法
仍是這個基礎的公式。要搞清楚業務流程,接下來要作的事情是,把這些業務流程相關的數據結構先理清楚。
數據結構是容易梳理的,類的成員變量、數據庫的表結構,一般都有快速提取的方式。除了MongoDB 可能會難一些,由於弱 schema 的緣由,咱們須要經過閱讀代碼的方式去理解schema。更麻煩的是,咱們不肯定歷史上經歷過多少輪的 schema 變動,這經過最新版本的源代碼極可能看不出來。一個不當心,咱們就可能會處理到非預期 schema 的數據。理清楚數據結構,事情就解決了大半。
剩下來就是理各個 UserStory 的業務流程,並給這些業務流程畫出它的 UML 時序圖。這個過程隨時能夠補充。因此咱們挑選對咱們當前工做最爲相關的來作就行了。
最後,仍是一樣地,咱們要及時把咱們整理的結論寫下來,變成架構文檔的一部分。這樣隨着愈來愈多人去補充完整架構設計文檔,纔有可能把咱們的項目從混沌狀態解脫出來。
對於任何一個項目團隊來講,閱讀代碼的能力都極其重要。哪怕你以爲你的團隊共識管理很好,團隊很默契,你們的工程習慣也很好,也都很樂意寫文檔,但這些都替代不了閱讀代碼這個基礎活動。
閱讀代碼是不可或缺的能力。
爲何這麼說?由於:代碼即文檔,代碼是理解一致性更強的文檔。
另外,做爲一個小補充,咱們須要指出的一點是:閱讀代碼的結果,有時不必定僅僅是架構設計文檔的補充與完善。咱們有時也會順手修改幾行代碼。
這是正常現象,並且應該被鼓勵。爲何鼓勵改代碼?是由於咱們鼓勵隨時隨地消除臭味。改幾行明顯風格不太好的代碼,是很是好的一件事情。
可是咱們也要有原則。