前言
前不久公眾號後臺有人給我留言,請教如何體系地學習 Java 知識,我當時心想:這話題太大,Java 技術棧也太深,不是一篇文章能說清楚的,但要說學習技巧,卻的確是有規律所尋,秉持著「授人以魚不如授人以漁」的想法,這篇文章便分享下我個人的一些學習技巧。
王國維在《人間詞話》中提到了古今之成大事業、大學問者,必經的三種境界: “昨夜西風凋碧樹,獨上高樓,望盡天涯路。” 此第一境也。 “ 衣帶漸寬終不悔,為伊消得人憔悴。” 此第二境也。 “ 眾裡尋他千百度,驀然迴首,那人卻在,燈火闌珊處。”此第三境也。我也按照我的理解,將精進技術分成了三個境界。
第一境:從 overview 和 guides 獲取新知識的直觀感受
善用搜索,推薦 google。google 第一頁搜尋出來權威內容的機率要大於 baidu,人很容易有先入為主的觀念,前五篇文章對一個知識點的直觀感受,就會形成固化映像。(技術精進的第一層關卡:你可能缺少一個梯子)
以一個可能大多數讀者都覺得陌生的知識點:圖形資料庫 Neo4j 作為引子,來介紹。
搜尋 neo4j 關鍵詞時大機率會搜尋到官網,但作為一門技術的初學者,我更習慣於閱讀下中文部落格,教程,以對這個陌生的技術有一個宏觀的瞭解。雖然大家都很贊同一個觀點:英文檔案更加權威,但是我個人還是覺得,中文教程更加直觀,實際上我讀中文檔案的速度是英文檔案的 2-3 倍。
不需要細讀每個語法,我一般習慣看下概覽,再看下目錄對整體的知識點有個宏觀的掌握。這種入門級的網站有很多,大多出現在搜尋引擎的第一頁。而官方的 overview 通常也是很適合入門的手段。
英文閱讀能力強的讀者可以直接上手官方的 guides,overview,get started 等入門教程,它們比中文教程強在很多方面(技術精進的第二層關卡:你可能需要一定的英語閱讀能力):
- 避免了語言轉換帶來的翻譯失真。很多中文教程讀起來總覺得拗口,太過於書面化,陳述句也不符合中國人閱讀的習慣。翻譯爛的原因不需要我總結,事實上我對翻譯這個事有過比較深的真物體驗,我曾經和 spring4all 的小夥伴們一起對 spring 檔案進行過翻譯,大家積極性都很高,一週之類幾乎所有的 guides 檔案全部翻譯完成了,但質量真的慘不忍睹,參與校對時,竟然有人將 「jar」翻譯成「蜜罐」,而且不在少數。
- 官方檔案維護著最新的版本。對於一些熱門的軟體技術,比如我最近接觸的 docker,之前研究過得 lucene,它們都一些共同點:版本差異非常大,版本更迭速度快。翻看 docker 在國內零散的部落格,大多數比最新的 docker 版本落後 3 個大版本以上;而像 lucene 這種大版本演進非常迅速的技術(大版本演進意味著不相容老版本),api 幾乎是翻天覆地的變化。
- 權威性。
英語能力要求並不是很高,大學英語 4 級足矣,畢竟有個東西叫谷歌翻譯。
再比如 spring 體系的知識點,有一個通用的學習路線。還是以 neo4j 為例,得知 spring 對 neo4j 有二次封裝之後,便熟練地來到了 spring 的 guides 專欄(https://spring.io/guides),spring 對所有的知識點提供了兩個維度的學習檔案,其中 https://spring.io/guides 一般都是一個 15 分鐘上手的 hello world,讓你快速上手一門新的技術;另一個是較為完善的檔案:https://spring.io/docs/reference,成體系地介紹技術細節。第一階段主要關註前者,一般它長這樣:
敲完 hello world,一般就可以對應簡歷上「瞭解 XXXX」的描述了(斜眼笑。
第二境:官方檔案與社群
大多數時候,一個停留在 hello world 認知級別的知識點對於我們的幫助不會很大,你甚至連在群裡裝逼的機會都沒有!恐怖如斯!
瞭解完畢這門技術是用來解決什麼問題的,它大概是怎麼使用的,有了這些直觀體驗之後再來看檔案,會直觀不少。下麵主要介紹個人閱讀官方檔案的一些心路歷程。
首先來看看宇宙級開源專案 spring 的官方檔案,它長這樣:
琳琅滿目的專案,所有與 spring 相關的技術都可以在這兒獲取最權威的解讀,怎麼學習 springboot,springcloud 還需要問嗎?平時經常接觸 spring 的同學如果這個頁面都沒見過,私以為在學習認知上是有所欠缺的。
檔案除了主體知識內容之外還有整體介紹,新舊版本迭代的改動,新特性,依賴分析,效能測試等,檔案內容一般都是非常多的,可以擷取其中核心的幾節,將主要用法和註意事項掌握,至於不常用的特性,可以在碰到時再翻閱。
曾經一個專案需求中涉及到 spring security 的改造,而相關的檔案又比較少,我至少通讀過 5 遍 spring security 完整的檔案,從一開始的大概瞭解,到最後的基本掌握,很多細節點一開始無法 get 到,讀多了之後很多常用詞彙和語感都會提升,細節會被消化,整體閱讀速度也會隨著檔案閱讀量提高而提升(比如 out-of-box 這個高頻詞一開始是不知道什麼意思,谷歌翻譯也翻不出來,後來得知是「開箱即用」)。
開源社群也是精進一門技術的重要途徑,比如 spring4all,k8s,netty,elk 社群匯聚了不少文章和問答,初中高階的使用者都在社群中扮演著各自的角色(部分中文社群甚至樣式都差不多,估計是用同一套開原始碼搭建的[捂臉]),社群活躍度也是一門技術火熱的衡量指標。經常被大家調侃的面向 github 程式設計背後的 github,以及 Stack Overflow,都是質量比較硬的社群。
第三境:原始碼閱讀
(技術精進的第三層關卡:對原始碼的恐懼阻止了一個人探索的步伐)如果你覺得看完前面的文字是在說廢話,那麼這一節可能稍微能勾起你的興趣,權當之前是一個鋪墊,照顧下一些初學者。
在交流群裡面發現的一個現象,很多人對原始碼有一種天生的畏懼感,諸如:“我才剛畢業/我才工作三年,還沒到看原始碼的階段”,“原始碼不是架構師看的嗎”,“原始碼看不懂“,”看看別人的原始碼分析不就行了“…這一節主要聊聊原始碼閱讀技巧,以及一些個人對閱讀原始碼的感悟。
首先澄清幾點:閱讀原始碼絕對和工作年限無關;閱讀原始碼絕對和工作職位無關;大多數原始碼並不是很難;debug+原始碼分析絕對比看文章來的直觀。
1 原始碼中的測試用例
還是以 neo4j 為例,我們在 github 找到 spring-data-neo4j 的原始碼,然後 git clone 到本地,在本地 idea 中開啟。
和原始碼相關的第一點介紹的便是原始碼中的測試用例,對於大多數的開源專案而言,測試改寫率是一個質量衡量的指標。大部分 Java 相關開源專案會包含測試用例,專案的一些功能特性可能在檔案中無法一一介紹,通常可以在 src/test/java
中找到對應的用法,比如 neo4j 是怎麼支援事務的,怎麼維護邊和邊的關係的,在測試用例中都可以看到官方是怎麼使用的。再舉個例子,之前在使用 orika 這個複製工具時,一開始不知道怎麼實現泛型的複製(泛型的執行時擦除特性),谷歌搜尋和 Stack Overflow 提問無果之後,終於在原始碼的諸多測試用例中找到了我需要的程式碼。
2 透過核心介面/包結構分析框架層次結構
我猜測有人拒絕閱讀原始碼的一個原因:原始碼註釋量不夠,壓根不知道一段程式碼是幹嘛用的。的確,我在閱讀有些原始碼時也會出現這樣的情況:這兒會什麼要加鎖?為什麼要用 AtomicReference 這個類?為什麼這個方法放在父類,而另外看似功能差不多的程式碼放在子類實現?框架編寫者不會像培訓班的老師一樣跟你講解他為什麼要這麼寫,也不是所有的原始碼都能像 HashMap 的原始碼那樣被大家泛濫地解讀,是的,可能大多數情況下你的境地是「雖然不懂,還沒法問!」氣不氣,尷不尷尬?沒辦法,因為這已經是第三境了,曲高和寡,但還是有些方法規避這樣的情況的,那就是:主要關心核心介面,透過介面暴露的方法,猜測出作者的意圖。據我不多的原始碼閱讀經驗,一個實現類的註釋可能不多,但介面的註釋通常會很多,畢竟一個原則是面向介面程式設計。
上圖是我分析 spring security 原始碼時,根據介面間的關係整理出來的 UML 類圖,對於綠色實現類的細節我可能並不是特別關註,淺藍色代表的接口才是我們理解整個架構體系的切入點,配合 idea 這些優秀的整合開發環境,可以很方便的整理出 UML 類圖。介面是全域性架構,實現類是原始碼細節。
順帶一提:熟練使用 IDE 很重要。無論你是 eclipse 玩家還是 intellij idea 玩家,你都應該熟練掌握快捷鍵和一些常用操作,方便你閱讀原始碼。比如 idea 右鍵可以自動生成類的繼承關係圖(聚合關係的體現不夠智慧),方便分析層次關係;顯示方法 outline 快速檢視一個類的方法概覽,在成片的原始碼中非常有用;快速定位一個介面的實現類,一個類的子類等等。
再比如我在閱讀 motan 這款 rpc 框架原始碼時遵循的順序是其包結構的層次關係。
寫原始碼分析文章時基本就是按照一個包一篇文章來分析,從而化繁為簡。無論是模組結構,包結構,還是程式碼層面的介面結構,重點都是在強調:我們需要從宏觀掌握一個框架,再去扣細節,否則我個人感覺學習狀態就是很迷,不知道學到哪兒了。
3 帶著問題閱讀實現類原始碼
具體的實現類原始碼真的沒什麼技巧可講,一定是看一個人的寫程式碼功底,以及程式碼敏感度了,非要說技巧的話,可能就是多寫程式碼,培養程式碼敏感度了。此外,帶著問題去讀原始碼個人體驗下來感覺不錯,在閱讀 spring security 原始碼時,我帶著的問題是,怎麼結合 zuul 實現動態的許可權控制,一步步地 debug,看它原來的實現,之後是改原始碼,debug 看改變的效果。
具體的原始碼的閱讀難度也是參差不齊的,個人學習經歷中發現 motan 的原始碼就很容易閱讀,spring 的原始碼因為檔案比較齊全,閱讀體驗也很好,但 lucene 的原始碼和 hibernate 的原始碼,我也嘗試閱讀過,簡直是天書,又比如說 netty,單單熟練使用它就已很難,何論原始碼。一方面跟個人閱歷有關,一方面跟框架實現難度有關,很難蓋棺定論得出方法論。個人建議是,明確自己需要解決什麼問題去閱讀原始碼,不必為了裝逼而讀原始碼。
貼下之前幾個系列的原始碼解讀連結:
【RPC 系列】 https://www.cnkirito.moe/categories/RPC/
【Spring Security 系列】https://www.cnkirito.moe/categories/Spring-Security/
【OAuth2 系列】https://www.cnkirito.moe/categories/Spring-Security-OAuth2/
三境之外
除了學習技術的三種境界,還有一些其他個人的感悟。比如類比學習法,一開始學習 spring-data-jpa 時效率比較慢,這對於我是一個比較新的技術,但當我後來再接觸 spring-data-redis,spring-data-neo4j 時,雖然同樣是第一次接觸這些資料訪問層,但有了之前 spring-data-jpa 的參考,可以說是事半功倍。關於影片,部落格,書,檔案可以說關係很微妙,從影片到檔案,越來越不直觀,但學習效率越來越高,這些沒有高低貴賤之分,私以為都是很好的學習方法。怎麼提升程式碼技巧?說真的方法論歸方法論,重點還是程式碼行數鍛煉出來的程式碼敏感度,這是看書,看程式碼,寫部落格,看方法論學不來的,不多說了,滾去寫程式碼了[抱拳]。
朋友會在“發現-看一看”看到你“在看”的內容