解鎖優秀源代碼的方法與技巧總結(上)

讀碼破萬卷,敲鍵若有神。html

概述

要成爲做家, 須要閱讀大量的文學做品;要成爲一流的開發者,須要閱讀大量的優秀代碼。程序設計與開發,可視之爲邏輯的武學;解鎖優秀代碼的能力,是開發者的內功心法之一。

java

優秀源代碼就像一座寶庫,裏面藏着關於邏輯的奇珍異寶,很是值得一探哦!web

  • 解讀設計思想,理解技術原理和實現,遇到相似問題時,可以應用到解決方案中;
  • 積累好的代碼,勝於從新造輪子。

怎樣纔算真正理解了源代碼呢? 通常作到以下幾點:算法

  • 預分析與反饋總結。本身來設計,思考了哪些要素;優秀的實現,考慮了哪些要素。做個對比,可以有更多領悟和收穫;
  • 設計思想。蘊含了哪些設計思想,記錄下來;
  • 核心技術。哪些是核心技術點,是不可替換的 ?
  • 細節與發現。有哪些值得關注的細節 ? 有什麼重要的發現 ?

寫一篇源碼閱讀筆記,記錄其中的設計思想、核心技術和細節,重要或出乎意料的發現。數據庫

本文以 Guava.Cache 爲例,探討如何解讀優秀源代碼。

編程

導圖


流程與步驟

STEP0. 瞭解核心使用場景及預分析json

能夠到官網上去了解所要解讀代碼的核心使用場景,有哪些優點和不足。設計模式

思考一下:若是是本身來設計,會如何來作,考慮哪些因素? 作必定的思考後,再比對別人的實現,對比中能夠有更多的收穫和領悟。緩存

示例: Guava.Cache 用於建立一個可靠的本地緩存。 若是我來設計,會考慮多線程訪問的併發安全性,好比使用 ConcurrentHashMap 。在 Guava 實現裏,還考慮了緩存相關的統計,好比命中率,這對於衡量緩存效果是很是重要的;還記錄了移除緩存的緣由和監聽器等。

安全

STEP1. 瞭解系統的總體設計及概念

與常見業務系統冗長混亂的方法迥異, 優秀的系統一般會有一個優雅的總體設計, 將一組小而簡潔的概念串聯起來。 先了解系統的總體框架和所基於的基本概念, 會對理解系統的構造與運行有一個整體的引導做用。 同時, 在設計本身的系統時, 也會從中得到很好的啓發。

示例: Guava.Cache ,基本概念以下:

  • KV : 緩存一般是 KV 結構,基本原理是 Hash 查找;
  • CacheBuilderSpec : 緩存的規格,用來設置緩存的一些基本參數,調節緩存的行爲;
  • CacheLoader : 從 Key 值計算出 Value 值的計算函數;
  • CacheBuilder : 根據 CacheBuilderSpec 及 CacheLoader 建立具體的 LocalCache;
  • Cache : 緩存對象,存儲必定時間期限的KV值;
  • ObjectPool : 對象池,緩存一般要使用到池,避免過分膨脹;
  • 併發 : 能夠經過 Hash 分段表來增大併發吞吐量。

STEP2. 熟悉項目代碼組織結構

優秀的開源項目一般會有清晰而有層次化的項目組織結構。

拿到項目源代碼, 第一件事:概覽項目組織結構, 弄清楚哪些主要模塊(包), 各個模塊(包) 的主要做用;各個模塊(包) 下面有哪些主要類, 其做用如何。 這一步能夠採用大膽猜想加API文檔說明的方法來完成。

一般, 最頂層包每每包含與客戶端使用直接相關的類和方法, 其下層的子包完成某個子功能或特殊模塊。 此外, 優秀的項目代碼一般會對系統所涉及的每一個點都有一個較好的抽象, 使用一個小而簡潔的類或接口來表達, 而不是混雜在大的類中。 哪怕只是一些簡單的顏色字符串, 也會盡力用枚舉來實現它。不少 Javaweb 項目默認採用 Controller - Service - (Dao, RPC) - Model - Utils 或 API - Service - Dependency - Biz - Domain - Common - Config - Deploy 的總體組織模式, 這進一步下降了閱讀 Java 項目的難度。

示例: 因爲這只是個小的功能,所以都組織在包 com.google.guava.common.cache 下。 若是要看更復雜層次的包結構,能夠看 com.google.code.json , junit, Spring 等。

STEP3. 確立目標,縮小範圍

在開始閱讀源代碼以前, 內心要定一個目標去驅動閱讀。 好比說, 要探究 proxools 鏈接池有時獲取不到數據庫鏈接的問題, 或者弄清楚 extjs 分頁控件 PagingBar & java 線程池 ThreadPoolExecutor 的內部實現。 先攻取一個小分支, 將閱讀範圍縮小到容易完成的程度。物理學家一上來也不是能一會兒弄懂整個宇宙的。

示例:研究 Guava.Cache ,主要是想弄清楚裏面的原理和實現。

STEP4. 找到切入點

能夠從外部行爲切入。 閱讀 API 文檔, 理解其外部行爲,弄清楚設計所針對的需求目標,寫個 Demo 單步調試。

還能夠根據實際關注點切入。 好比:使用 Proxool 鏈接池, 我更關心是如何獲取數據庫鏈接的, 能夠從 ProxoolDataSource.getConnection 方法切入; 使用線程池, 能夠從 ThreadPoolExecutor 切入; 可使用 junit ,從 TestCase 切入。 通常來講,從所使用的客戶端類切入是一個不錯的選擇。

示例:能夠編寫一個 LocalCache 的 Demo ,而後單步調試進入。


STEP5. 鎖定主要和核心的類與方法

任何設計都會隱式或顯式地有「關鍵角色」 與 「支撐角色」 的分工。閱讀源代碼並非盲目漫無目的的行爲, 而是要先鎖定主要和核心的類與方法, 做爲閱讀的引路燈。在 Guava.Cache 裏,核心類就是 LocalCache ,而 CacheLoader, CacheBuilderSpec, CacheBuilder 都是支撐類。

STEP6: 標記主要流程, 繪製協做交互圖

跳過各類細節, 主要集中於弄清楚主要流程, 由哪些模塊、類以及哪些方法參與, 標記、繪製協做交互圖。

示例: 建立 CacheBuilderSpec -> 建立 CacheLoader -> 建立 Cache 實現 -> 使用 Cache

STEP7: 分解細節,各個擊破

完成 STEP6 以後, 一般對該框架已經有了一個總體的理解, 雖然還有不少細節不清楚。 不要緊! 優秀源碼爲了追求靈活性和可擴展性,具備更強的縝密度, 相對比業務系統代碼更難理解一些。 尤爲是細節盤根錯節,相互依賴影響。

一行行代碼去讀,是比較笨拙的;能夠將這些細節分離和提煉出多個關注點,理解關注點是如何實現的,在關注點的導引下去閱讀這些細節性的代碼,會更加高效一點。將多個關注點分解成多個小任務, 各個擊破。 這一步須要反覆屢次地進行, 可能須要查閱不少知識點和接觸一些「陰暗之處」, 才能逐漸抵達系統的「真相」, 最終做出對其優缺點的合理的綜合評定。

這一步比較困難,須要一些技巧和耐心。單獨再來討論這個話題。

STEP8: 實踐,再實踐!

由易入難,按部就班。

  • 不含複雜技術點的獨立類。好比 Integer , 只要編程基礎就能讀懂。
  • 不含複雜技術點的含有少許交互的框架。好比 JDK Collection,commons-collections, 須要數據結構與算法基礎。
  • 不含複雜技術點的交互度適中的簡易框架。好比 junit ,主要是類與交互的設計。
  • 含有併發但不含交互的獨立類。好比 AbstractQueuedSynchronizer , 併發工具的基礎抽象類。
  • 含有併發並只含有少許交互的簡單框架。好比 ThreadExecutor , Google.Cache 。
  • ???
  • 含有大量技術點和交互的實用性框架,好比 Spring , Struts 等。

模式識別與積累

代碼編寫和程序設計都有一些模式能夠遵循。 熟悉這些代碼與設計模式對理解源代碼也有很好的幫助。 好比, 面向對象系統中就一般有以下幾種基本模式。

獨立類

獨立類完成其獨立的功能,會引用到其它類的方法, 但交互不會複雜。 例如 java.util.Arrays , java.lang.Integer 類;

支撐類

支撐類用來表達一個小而簡潔的抽象, 它一般不直接拿來用, 而是被其它類引用來構造更復雜的功能, 好比 java.util.AbstractMap$SimpleEntry;

繼承型交互關係

繼承型的交互關係遵循"接口-抽象類-具體類" 的模式: 接口規定行爲規範, 抽象類完成用於定製的骨架實現, 具體類則實現具體完整的功能,能夠直接拿來用。 例如經典「三段式」 : Map -> AbstractMap -> HashMap 。

繼承性交互關係,要體會接口是怎麼設計的,爲何須要定義這些方法;要體會抽象類是怎麼設計的,如何將通用流程和鉤子方法提煉出來。

委託型交互關係

委託型交互遵循「封裝或代理」模式,將一部分或所有的功能實現委託轉發給其它類的實現, 可能會作一點封裝或代理操做。好比 ForwardingLoadingCache , 將緩存操做所有轉發給另外一個緩存。

組合與混合

實際應用中, 一般是多種模式混合而成。好比 :

  • class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> 是「接口-抽象類-具體實現」的繼承性交互關係;
  • static class LocalLoadingCache<K, V> extends LocalManualCache<K, V> implements LoadingCache<K, V> 是「接口-實現類-子類」的繼承性交互關係;
  • public interface LoadingCache<K, V> extends Cache<K, V>, Function<K, V> 是 「接口-接口」的繼承關係;
  • LocalLoadingCache 的緩存操做實現,是委託給 LocalCache 來實現的,是「實現類-實現類」的代理關係;
  • ReentrantLock 委託 Sync 實現,而 Sync 繼承自 AbstractQueuedSynchronizer。

在解決具體設計問題時, 會應用到一些常見的設計模式, 好比單例模式、觀察者模式、裝飾器模式、代理模式、責任鏈模式等, 熟悉這些設計模式也是必要的, 詳見《設計模式-可複用面向對象軟件的基礎》 一書,在 「軟件設計要素初探:基礎設計模式概覽」一文中亦有簡短的總結。

技巧與手段

  • 邊閱讀邊註釋。對讀過的地方作些必要的註釋, 主要突出其用途,必要的實現細節; 能夠邊讀邊作些筆記。

  • 搭建環境, 運行, 調試。搭建好源碼環境,寫個單測,運行起來, 而後設置斷點調試, 觀察結果, 很好的跟蹤方式;

  • 從簡單着手, 善於分解小任務。對 Spring , Tomcat 的源代碼無從下手? 從 JDK, Junit 看起; 看不懂 Extjs Grid 的代碼? 從 Label, ComboBox 着手; 一個龐大的類看的吃力? 不妨將其分解成多個小任務, 各個擊破。

  • 找點其它的事情作作。閱讀源代碼可不是這世界最美妙的事情。 若是起初不是很適應的話, 能夠先讀若干函數, 而後作點其它事, 好比活動活動, 聽聽歌看看視頻, 而後再回來閱讀, 反覆如此, 來逐漸加強這種耐心和適應感。

心理鍛鍊

神祕有難度

一般會認爲優秀源代碼很牛逼,反而敬而遠之,不敢入寶山而探之。 其實,優秀源代碼很日常,與平常所寫的代碼,是一樣的原材料和材質。不一樣的是: 1. 組織條理性更強; 2. 邏輯更縝密。 這不正是所須要學習的地方嗎?

此外,總會遇到看不懂,想不通的地方; 這時, 可能須要彌補下基礎知識(數據結構與算法、設計模式、併發、網絡協議、系統原理等), 也可能學習到某種高級技巧(函數式、位運算、字節碼等), 必定不要放過這種學習機會。

耐心與毅力

閱讀源碼很好滴鍛鍊耐心和毅力,而耐心和毅力均是難得的品質。 閱讀源碼,起初是有些艱難,可是,一旦攻下,就爲後面的前進打下很好的鋪墊了。

強化練習

有兩種模式:

  • 初期,能夠固定時段,好比天天早上或晚上半小時閱讀源代碼,創建反射弧;
  • 中期,能夠集中時段,強化閱讀大量源代碼。 有時候 「自虐」一下,會有更大的成長。


實際障礙

英語能力

emmm... 忘了說關鍵的一點了,閱讀源代碼須要必定的英語能力。 怎麼辦呢 ? 學呀,備個英文詞典唄!

沒有時間

想作必定會擠出時間。慾望要足夠強烈。

小結

就像任何一門技藝同樣, 閱讀源代碼的技能也要從基礎一點一點的訓練, 直到嫺熟、爐火純青的境界。

相關文章
相關標籤/搜索