解析IntelliJ IDEA內部設計

d433 9年前發布 | 70K 次閱讀 IntelliJ IDEA

IntelliJ IDEA第一版發布于2001年1月,這是第一款集成了高級代碼導航和代碼重構功能的Java IDE。

2009年,JetBrains開源了其社區版。從那時開始,就新出現了許多基于其社區版的IDE,如Google的Android Studio。

本文使用JArchitect作為工具,深入了解Intellij IDEA社區版,探索其中使用的一些內部設計決策。

1、模塊化

Intellij IDEA以模塊化的方式組織,使用了多個項目。最主要的項目是“idea”,“util”項目中實現了工具類,而“openapid”這個項目的jar含有開發Intellij IDEA插件所需的類型。

下面列出了Intellij IDEA的項目,以及相關類型的統計信息。

 解析IntelliJ IDEA內部設計

每個項目都以模塊化的方式組織代碼,使用多個包并將相同特性的包組織在一起。

基于特性劃分包(package),將代碼根據其特性放在不同的包里。即將所有與某個單一特性相關的內容都放在一個單個目錄或包中。這樣就使得所有的包都具有高聚合性和高模塊化,把包與包之間的耦合性降到最低,互相依賴非常緊密的內容都放置在一起。

下面是idea項目中一些包的例子,可以看到其中的類型是根據特性分類的。

 解析IntelliJ IDEA內部設計

2、Intellij IDEA開發者廣泛使用了GoF設計模式

設計模式是一個軟件工程概念,描述了在軟件設計中許多常見問題的解決方案。GoF模式是其中最著名的一個。

Intellij IDEA開發者使用了擴展的GOF模式,下面是一些在代碼中使用的設計模式。

2.1、工廠模式

使用工廠模式可以分離邏輯并加強聚合性,下面是在代碼中定義的一些工廠類。

 解析IntelliJ IDEA內部設計

在Intellij中實現了許多工廠類,下面就是一些繼承自TextEditorHighlihtingPassFactory的工廠類。

圖:http://www.codergears.com/Blog/wp-content/uploads/intellij2.png

2.2、適配器模式

適配器模式是兩個不兼容接口之間的橋梁。這種類型的設計模式來自于結構模式,結構模式將兩個互相獨立的接口組合起來。

在Intellij IDEA的代碼中實現了許多適配器:

 解析IntelliJ IDEA內部設計

2.3、裝飾器模式

裝飾器模式可以用來擴展(裝飾)某個對象的功能,而且無需改變其結構。在Intellij IDEA中實現了許多裝飾器。

 解析IntelliJ IDEA內部設計

2.4 代理模式

代理模式最普通的形式就是一個類,該類作為其他類型的接口。

下面是一個例子,FieldBreakpoint和FrameVariablesTree這兩個類使用了兩個代 理:VirtualMachineProxy和StackFrameProxy。VirtualMachineProxy接口用來替代 VirtualMachineProxyImpl這個實現。然而,與FrameVariablesTree相關的StackFrameProxyImpl 卻不是這樣。也許這里需要重構一下,移除互相的依賴比較好。

 解析IntelliJ IDEA內部設計

2.5、Facade(外觀)模式

Facade模式隱藏了系統的復雜性,向客戶端提供了一套用于訪問系統的接口。下面是Intellij IDEA中實現的CodeStyle Facade。

 解析IntelliJ IDEA內部設計

2.6 訪問者模式

訪問者模式是用來將對象的結構本身和算法分離。

下面的代碼高亮特性就是用訪問者模式實現的。

 解析IntelliJ IDEA內部設計

2.7、策略模式

在許多情況下,某些類之間區別只在于他們的行為不同。這種情況下,最好的辦法是將不同類中的算法與類本身分離出來,在運行時讓類選擇不同的算法。

在Intellij IDEA的源碼中,許多類都實現了策略設計模式:

 解析IntelliJ IDEA內部設計

2.8、Builder模式

這種設計模式允許客戶對象構建一個復雜的對象。Intellij IDEA的源碼中,實現的ConrtolFlowBuilder就是這樣一個builder。

下面列出了一組會被ControlFlowBuilder.build調用的方法:

 解析IntelliJ IDEA內部設計

3、耦合

應用程序的耦合度越低越好,這樣當應用的一部分發生改變時,另一部分受到的影響就不大。從長遠來看,在更改應用的需求時,低耦合可以節省大量的時間、精力和資金。

下面列出了使用接口帶來的3個主要優點:

  • 接口就是定義了一套契約,用來方便重用。如果某個對象實現了一個接口,就表明這個對象遵循這個接口的標準。使用這個對象的對象成為消費者,接口就是這個對象和消費者之間的契約。
  • 接口還提供了某種層次的抽象,讓程序更易理解。接口允許開發者以更一般的方式討論代碼的行為,而不是一頭扎進代碼的細節中。
  • 接口讓組件之間的耦合更低,使得接口的消費者不會接觸實現這些接口的類中的內部改動。

Intellij IDEA中定義的許多接口和抽象類都是為了降低耦合。

 解析IntelliJ IDEA內部設計

下面的Metric View中,藍色區域就是使用了接口的代碼。

 解析IntelliJ IDEA內部設計

在Metric View中,代碼是使用Treemap表示的。Treemap是一種表示以嵌套矩形,按樹的方式組織的數據。樹結構一般用來表示代碼層次。

  • 項目中含有多個包。
  • 項目中含有多個類型。
  • 類型中含有多個方法和字段。

Treemap視圖可以很好的表示CQLinq查詢的結果:藍色矩形表示的查詢結果,所以能以可視的方式了解查詢中關注的類型。

我們可以查看所有包中定義的接口和抽象類,方便地表示包中提供的特性。

4、聚合

單一功能原則(Single responsibility principle)規定每個類都應該只有一個單一的功能,即類應該是聚合的。一般LCOM值越高,表示類聚合越低。LCOM有好幾種表示方式。LCOM 表示的是0-1之間的值,而LCOM HS(HS表示的是Henderson-Sellers)表示的值范圍是0-2。LCOM HS值大于1是很危險的事。下面是LCOM的計算方式:

LCOM = 1 – (sum(MF)/M*F)
LCOM HS = (M – sum(MF)/F)(M-1)

其中:

  • M是類中方法的數量(包括靜態方法和實例方法,其中還包括構造器、屬性設置和獲取方法、添加和移除事件的方法)。
  • F是類中實例字段的數量。
  • MF是類中訪問特定實例字段的方法的數量。
  • Sum(MF)是類中所有實例字段的MF的總和。

這些公式中的基本思想為:完全耦合的類中,所有方法會使用其所有的示例字段,這意味著sum(MF)=M*F,即LCOM = 0且LCOMHS = 0。

如果LCOMHS的值大于1,就要小心了。

 解析IntelliJ IDEA內部設計

只有少數類型不是聚合的。

5、多線程和并發

為了讓Intellij IDEA具有更好的響應能力,其中創建了許多線程來提高用戶體驗。

首先,來搜索所有以直接和非直接線程開始的方法:

 解析IntelliJ IDEA內部設計

其中并發邏輯分離出來,位于下列的包中:

 解析IntelliJ IDEA內部設計

為了使用并發開發,這里使用了JSR166

下面列出了所有jsr166 jar使用的所有類型:

 解析IntelliJ IDEA內部設計

6、抽象度與不穩定性圖

這幅圖的思想是,程序中使用的代碼元素越多,其抽象度就應該越高。換句話說,不應該過多依賴實現,而應該依賴抽象。這里的代碼元素是指一個項目(也可以是包或者類型),該項目被程序中被其他項目大量使用。

在代碼中,不應該大量使用具體類型。這會在程序中帶來“痛苦區(Zones of Pains)”,當改變某個具體實現時,會潛在的影響程序的其他部分。而使用實現比抽象影響的范圍更大。

下圖中的主線(虛線)表示的是應該如何維持抽象和穩定性的平衡。穩定的組件應該位于左邊。如果檢查主線,可以看出,該線附近的組件抽象度都很高。如果抽象度非常低,則會位于“痛苦區”。

 解析IntelliJ IDEA內部設計

只有工具項目位于“痛苦區”,但這并不是大問題。實際上,工具庫提供的是一些工具類,而不像接口提供功能。

7、Open API和插件系統

可以用插件擴展Intellij IDEA。“openapi”就是用來完成這個目標的jar。

openapi這個jar提供了許多接口,這些接口表示可以通過插件擴展的特性。

 解析IntelliJ IDEA內部設計

每個Intellij IDEA插件含有一個或多個行為(Action)。下面這個CQLinq查詢結果中,顯示了代碼中實現的數千個行為。

 解析IntelliJ IDEA內部設計

研究已有的行為可以幫助開發者更容易開發自定義插件。

8、使用緩存提高性能

優化應用最常用的方式就是使用緩存。Intellij IDEA使用兩個緩存管理器:

 解析IntelliJ IDEA內部設計

FindInProjectTask使用CacheManager接口來搜索單詞。

下面列出了FindInProjectTask.getFilesForFastWordSearch方法調用的其他方法:

 解析IntelliJ IDEA內部設計

9、使用的外部庫

Intellij IDEA使用了許多外部jar,下面列出了所有用到的外部jar:

 解析IntelliJ IDEA內部設計

 解析IntelliJ IDEA內部設計

當使用了外部庫,最好檢查一下是否能方便的使用其他第三方庫而不對整個應用造成影響。在許多情況下都需要使用其他第三方庫。其他第三方庫會有以下特性:

  • 特性更多
  • 更強大
  • 更安全

現在來看看這些外部庫的耦合度是否很高。

Swing

Swing實現了許多GUI組件,為Java應用添加了許多GUI功能和交互功能。Swing組件完全用Java語言實現。其可替換的外觀和體驗可以在不同的平臺上擁有相同的GUI外觀,也能在當前的系統上擁有本地的外觀(如Windows、Solaris或Linux)。

現在來看看所有直接使用Swing組件的類型:

 解析IntelliJ IDEA內部設計

從下圖可以看出,藍色的類型都直接使用Swing組件。

 解析IntelliJ IDEA內部設計

用其他GUI框架來替代Swing很困難。雖然Swing的爭議很大,但Intellij IDEA這樣優秀的GUI應用證明了Swing作為GUI庫也是很好的選擇。

Netty

Netty是同步的時間驅動網絡應用框架,用于快速開發易維護的高性能服務端、客戶端協議。

下面列出了所有實用該庫的類型:

 解析IntelliJ IDEA內部設計

其中只有少數是直接使用了該庫,這樣如果想使用其他庫替換也非常容易。

ASM

ASM是一個非常小的Java字節碼操作框架。這個框架非常有名,許多工具都在使用。在JArchitect中也使用它來分析字節碼。

下面列出了所有直接使用ASM的類型:

 解析IntelliJ IDEA內部設計

如Netty一樣,對ASM的使用也僅限于幾個包中,這樣就可以方便的用其他庫替換。

除了Swing,其他庫都沒有在Intellij IDEA中緊耦合。

10、統計

10.1、使用最多的類型

了解項目中那些類型使用的最多是一件非常有趣的事。這些類型必須在設計上、實現上都非常優秀,而且經過完善的測試。這些類型中的一丁點改動都會影響整個項目。

可以使用TypesUsingMe衡量標準來找到這些類型:

 解析IntelliJ IDEA內部設計

然而,還有另一種有趣的衡量標準類搜索常用類型:TypeRank。

TypeRank值是使用Google PageRank算法在圖上計算類型的依賴關系。使用的位似中心(homothety of center)為0.15,所以平均TypeRank為1。

TypeRank值較高的類型應該仔細測試,因為這些類型中的bug會導致更大的問題。

下面是使用TypeRank衡量的所有常用類型:

 解析IntelliJ IDEA內部設計

在這種衡量方式中,PsiElement取代了Project接口,成為最常用的類型。

10.2、使用最多的方法:

 解析IntelliJ IDEA內部設計

10.3、過多調用其他方法的方法

了解某個方法調用了多少個其他方法也很有趣,如果一個方法過多調用其他方法可能會有設計問題。在某些情況下需要對這些方法進行重構,以提高可讀性和維護性。

 解析IntelliJ IDEA內部設計

總結

Intellij IDEA的設計和實現都非常好,其中使用了許多設計模式,并提供了許多優秀的實踐經驗。探索其中的代碼是一條實踐之路,從中可以學習如何設計和實現你的應用。這比只讀書和文檔來提高設計技能要好得多。

原文鏈接: codergears 翻譯: ImportNew.com - 孫 波翔
譯文鏈接: http://www.importnew.com/14260.html

 本文由用戶 d433 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!