Java代碼質量檢查工具及使用案例
在現在的軟件開發中,由於軟件的複雜度越來越高,業務也覆蓋很廣,各個業務模塊業務錯綜複雜。這樣就需要我們需要團隊開發,在我們團隊中開發人員的經驗、代碼風格樣式都不一致,以及缺乏統一的標準,從而導致我們的整個項目的的代碼難以閱讀,不便於後期維護。這幾天在研究代碼質量管理,根據在網上搜集的資料及跟前輩學的一點經驗整理一下,有需要的同學們可以查看,也便於以後自己回顧。
主要對下面的七塊進行分析
編碼格式規範
代碼重複
代碼覆蓋率
依賴項分析
複雜度監控
Java模擬技術
代碼評審和重構
接下來的使用Eclipse 插件來揭示這些分析領域:
編碼格式規範:codeStyle和CheckStyle
代碼重複:PMD 的 CPD
代碼覆蓋率:Coverlipse或者Emma
依賴項分析:JDepend
複雜度監控:Metrics
Java模擬技術:EasyMock、PowerMock
代碼評審和重構:Jupiter
編碼格式規範
codestyle介紹
統一的代碼規範能提高代碼的可讀性、可維護性。
一般規則和格式規範:如代碼縮進、程序塊規範、每行最大代碼長度;
命名規範:如包名、類名、接口名、枚舉、屬性名、方法名、參數名等命名規則;
文檔規範:如類文件頭註釋、變量註釋、方法註釋等;
編程規範:如異常、併發、多線程等;
其它規範:如日誌格式等。
圖1
可以導入代碼格式,實現統一。
checkstyle介紹
安裝checkstyle 的Eclipse插件
1. 下載地址:
http://pan.baidu.com/s/1o6LOSwM
2. 解壓net.sf.eclipsecs-updatesite_5.6.1.201306282206-bin.zip文件,到系統路徑下。如:D:\geyouchao\eclipse-plugins\cs(注:一定不用起名爲checkstyle,不知道爲什麼此名就是安裝不成功),此文件夾下有兩個文件夾features、plugins。
3. 我們使用link的方式安裝。在Eclipse的dropins文件夾下新建checkstyle.link文件,內容爲:
path=D:\\geyouchao\\eclipse-plugins\\cs
4. 關閉Eclipse,重啓。然後在Eclipse的window》Preferences下就可以看到checkstyle菜單,安裝成功,如下圖
圖2
使用checkstyle
自定義CheckStyle規則,下面是我定義的CheckStyle模板,然後導入
圖3
把新導入的,設置爲默認
圖4
可以修改其中的值,點擊“Configure…”按鈕。
圖5
下面是我自定義的CheckStyle.xml文件,供參考。
<?xml version="1.0"encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//PuppyCrawl//DTD Check Configuration 1.2//EN""http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
<module name="Checker">
<propertyname="severity" value="warning"/>
<property name="charset" value="UTF-8"/>
<!-- 長度方面的檢查 -->
<!-- 文件長度不超過1500行 -->
<module name="FileLength">
<property name="max" value="1500" />
</module>
<module name="TreeWalker">
<!-- javadoc的檢查 -->
<!-- 檢查所有的interface和class -->
<module name="JavadocType" />
<!-- 命名方面的檢查 -->
<!-- 局部的final變量,包括catch中的參數的檢查 -->
<module name="LocalFinalVariableName" />
<!-- 局部的非final型的變量,包括catch中的參數的檢查 -->
<module name="LocalVariableName" />
<!-- 包名的檢查(只允許小寫字母) -->
<module name="PackageName">
<property name="format"value="^[a-z]+(\.[a-z][a-z0-9]*)*$" />
</module>
<!--僅僅是static型的變量(不包括static final型)的檢查 -->
<module name="StaticVariableName" />
<!-- 類型(Class或Interface)名的檢查 -->
<module name="TypeName" />
<!-- 非static型變量的檢查 -->
<module name="MemberName" />
<!-- 方法名的檢查 -->
<module name="MethodName" />
<!-- 方法的參數名 -->
<modulename="ParameterName " />
<!-- 常量名的檢查 -->
<module name="ConstantName" />
<!-- 沒用的import檢查,比如:1.沒有被用到2.重複的3.import java.lang的4.import 與該類在同一個package的 -->
<module name="UnusedImports" />
<!-- 每行不超過150個字-->
<module name="LineLength">
<property name="max" value="150" />
</module>
<!-- 方法不超過150行 -->
<module name="MethodLength">
<property name="tokens" value="METHOD_DEF"/>
<property name="max"value="150" />
</module>
<!-- 方法的參數個數不超過5個。並且不對構造方法進行檢查-->
<module name="ParameterNumber">
<property name="max" value="5" />
<property name="tokens" value="METHOD_DEF"/>
</module>
<!-- 空格檢查 -->
<!-- 允許方法名後緊跟左邊圓括號"(" -->
<module name="MethodParamPad" />
<!-- 在類型轉換時,不允許左圓括號右邊有空格,也不允許與右圓括號左邊有空格 -->
<module name="TypecastParenPad" />
<!-- 關鍵字 -->
<!--
每個關鍵字都有正確的出現順序。比如 public static final XXX 是對一個常量的聲明。如果使用 static
public final 就是錯誤的
-->
<module name="ModifierOrder" />
<!-- 多餘的關鍵字 -->
<module name="RedundantModifier" />
<!-- 對區域的檢查 -->
<!-- 不能出現空白區域 -->
<module name="EmptyBlock" />
<!-- 所有區域都要使用大括號 -->
<module name="NeedBraces" />
<!-- 多餘的括號 -->
<module name="AvoidNestedBlocks">
<property name="allowInSwitchCase" value="true"/>
</module>
<!-- 編碼方面的檢查 -->
<!-- 不許出現空語句 -->
<module name="EmptyStatement" />
<!-- 不允許魔法數 -->
<module name="MagicNumber">
<property name="tokens"value="NUM_DOUBLE, NUM_INT" />
</module>
<!-- 多餘的throw -->
<module name="RedundantThrows" />
<!-- String的比較不能用!= 和 == -->
<module name="StringLiteralEquality" />
<!-- if最多嵌套3層 -->
<module name="NestedIfDepth">
<property name="max" value="3" />
</module>
<!-- try最多被嵌套2層 -->
<module name="NestedTryDepth">
<property name="max" value="2" />
</module>
<!-- clone方法必須調用了super.clone() -->
<module name="SuperClone" />
<!-- finalize 必須調用了super.finalize() -->
<module name="SuperFinalize" />
<!-- 不能catch java.lang.Exception -->
<module name="IllegalCatch">
<property name="illegalClassNames"value="java.lang.Exception" />
</module>
<!-- 確保一個類有package聲明 -->
<module name="PackageDeclaration" />
<!-- 一個方法中最多有3個return -->
<modulename="ReturnCount">
<property name="max" value="3" />
<property name="format" value="^$" />
</module>
<!--
根據 Sun 編碼規範, class 或 interface 中的順序如下: 1.class 聲明。首先是 public,
然後是protected , 然後是 package level (不包括access modifier )最後是private .
(多個class放在一個java文件中的情況) 2.變量聲明。首先是 public, 然後是protected然後是 package
level (不包括access modifier )最後是private . (多個class放在一個java文件中的情況)
3.構造函數 4.方法
-->
<module name="DeclarationOrder" />
<!-- 同一行不能有多個聲明 -->
<module name="MultipleVariableDeclarations" />
<!-- 不必要的圓括號 -->
<module name="UnnecessaryParentheses" />
<!-- 檢查並確保所有的常量中的L都是大寫的。因爲小寫的字母l跟數字1太象了 -->
<module name="UpperEll" />
<!-- 檢查數組類型的定義是String[] args,而不是String args[] -->
<module name="ArrayTypeStyle" />
<!-- 檢查java代碼的縮進默認配置:基本縮進4個空格,新行的大括號:0。新行的case 4個空格
<module name="Indentation" /> -->
</module>
</module>
下面是使用CheckStyle檢查過得代碼
圖6
常見錯誤分析
常見的CheckStyle錯誤有這些:
1.Type is missing a javadoc commentClass
缺少類型說明
2.“{” should be on the previous line
“{” 應該位於前一行
3.Methods is missing a javadoc comment
方法前面缺少javadoc註釋
4.Expected @throws tag for “Exception”
在註釋中希望有@throws的說明
5.“.” Is preceeded with whitespace “.”
前面不能有空格
6.“.” Is followed by whitespace“.”
後面不能有空格
7.“=” is not preceeded with whitespace
“=” 前面缺少空格
8.“=” is not followed with whitespace
“=” 後面缺少空格
9.“}” should be on the same line
“}” 應該與下條語句位於同一行
10.Unused @param tag for “unused”
沒有參數“unused”,不需註釋
11.Variable “CA” missing javadoc
變量“CA”缺少javadoc註釋
12.Line longer than 80characters
行長度超過80
13.Line contains a tab character
行含有”tab”
字符
14.Redundant “Public” modifier
冗餘的“public”modifier
15.Final modifier out of order with the JSL
suggestionFinal modifier的順序錯誤
16.Avoid using the “.*” form of import
Import格式避免使用“.*”
17.Redundant import from the same package
從同一個包中Import內容
18.Unused import-java.util.list
Import進來的java.util.list沒有被使用
19.Duplicate import to line 13
重複Import同一個內容
20.Import from illegal package
從非法包中 Import內容
21.“while” construct must use “{}”
“while” 語句缺少“{}”
22.Variable “sTest1” must be private and haveaccessor method
變量“sTest1”應該是private的,並且有調用它的方法
23.Variable “ABC” must match pattern“^[a-z][a-zA-Z0-9]*$”
變量“ABC”不符合命名規則“^[a-z][a-zA-Z0-9]*$”
24.“(” is followed by whitespace
“(” 後面不能有空格
25.“)” is proceeded by whitespace
“)” 前面不能有空格
代碼重複
PMD介紹
安裝PMD 的Eclipse插件
1. 下載地址:
http://jingyan.baidu.com/article/19192ad835de6ee53e57073c.html
2. 解壓net.sourceforge.pmd.eclipse-3.2.6.v200903300643.zip文件,到系統路徑下。如:D:\geyouchao\eclipse-plugins\pmd,此文件夾下有兩個文件夾features、plugins。
3. 我們使用link的方式安裝。在Eclipse的dropins文件夾下新建pmd.link文件,內容爲:
path=D:\\geyouchao\\eclipse-plugins\\pmd
4. 關閉Eclipse,重啓。然後在Eclipse的window》Preferences下就可以看到PMD菜單,安裝成功,如下圖
圖7
使用PMD
圖8
下面是PCD生成的重複代碼,可以對其中的代碼進行分析,修改
圖9
代碼覆蓋率
Coverlipse介紹
安裝coverlipse 的Eclipse插件
1. 下載地址:
https://sourceforge.net/projects/coverlipse/files/Coverlipse/
下載下圖中5個文件
圖10
1. 解壓coverlipse-0.9.6.zip文件,到系統路徑下。如:D:\geyouchao\eclipse-plugins\ coverlipse-0.9.6,此文件夾下有兩個文件夾features、plugins。
2. 我們使用link的方式安裝。在Eclipse的dropins文件夾下新建coverlipse.link文件,內容爲:
path=D:\\geyouchao\\eclipse-plugins\\coverlipse
3. 關閉Eclipse,重啓。右鍵java代碼,點擊dubug,安裝成功,如下圖
圖11
使用coverlipse
Wait..
Emma介紹
Wait…
依賴項分析
jdepend介紹
安裝jdepend 的Eclipse插件
1. 下載地址:
http://andrei.gmxhome.de/jdepend4eclipse/links.html
圖12
2. 拷貝de.loskutov.eclipse.jdepend_1.2.4.201406241900.jar文件,到Eclipse的dropins目錄下。如:D:\geyouchao\eclipse4.2-xu\dropins。
3. 關閉Eclipse,重啓。通過右鍵單擊源文件夾並選擇 Run JDepend Analysis。一定要選擇一個含源代碼的源文件夾;否則看不到此菜單項。
圖13
使用jdepend
圖14
下面對jdepend的分析的結果簡單介紹
圖15
1. Selected objects():選擇分析的包
2. Package:包全路徑
3. CC(concr.cl.):當前行對應包的具體類的數量。
4. AC(abstr.cl.):當前行對應包的抽象類和接口的數量。
5. Ca(aff.):依賴於被分析package的其他package的數量,用於衡量pacakge的職責。即有多少包調用了它。(AfferentCouplings)
6. Ce(eff.):被分析package的類所依賴的其他package的數量,用於衡量package的獨立性。即它調用了多少其他包。(EfferentCouplings)
7. A:被分析package中的抽象類和接口與所在package所有類數量的比例,取值範圍爲0-1。(Abstractness )
8. I:I=Ce/(Ce+Ca),用於衡量package的不穩定性,取值範圍爲0-1。I=0表示最穩定,I=1表示最不穩定。即如果這個類不調用任何其他包,則它是最穩定的。(Instability )
9. D:分析package和理想曲線A+I=1的垂直距離,用於衡量package在穩定性和抽象性之間的平衡。(Distance)
理想的package要麼完全是抽象類和穩定(x=0,y=1),要麼完全是具體類和不穩定(x=1,y=0)。取值範圍爲0-1,
D=0表示完全符合理想標準,
D=1表示package最大程度地偏離了理想標準。即你的包要麼全是接口,不調用任何其他包(完全是抽象類和穩定),要麼是具體類,不被任何其他包調用。
10. Cycle!:循環依賴
11. Package with cycle:包與包直接有循環調用
12. Depends upon-efferentdependencies:依賴的包
13. Used by-afferentdependencies:被引用的包
圖16
Instability:不穩定
Abstractness:抽象性
問題分析
圖17
針對上圖中Cycle!列中有感嘆號圖標問題,是因爲以上三個包中的類有傳遞依賴,故出現此警告。
解決辦法:
把其中的某個或者某些類再單獨抽出新包,解決此問題。
參考資料
1. http://www.ibm.com/developerworks/cn/java/j-ap01117/
2. http://www.clarkware.com/software/JDepend.html
複雜度監控
metrics(量度)介紹
安裝metrics 的Eclipse插件
1. 下載地址:
https://sourceforge.net/projects/metrics/
圖18
圖19
圖20
圖21
2. 解壓updatesite_1.3.6.zip文件,net.sourceforge.metrics.updatesite文件夾下有features和plugins分別拷貝到Eclipse的對應目錄下。如:D:\geyouchao\eclipse4.2-xu\plugins等。
3. 關閉Eclipse,重啓。然後在Eclipse的window》Preferences下就可以看到Metrics Preferences菜單,安裝成功,如下圖
圖22
設置metrics參數
下圖是metrics提供的配置項,下面對各個配置項進行解釋
圖23
number of overridden methods |
重載方法的數量 |
number of attributes |
屬性的數量 |
number of children |
子類的數量 |
number of classes |
類的數量 |
method lines of code |
代碼的方法行 |
number of methods |
方法的數量 |
nested block depth |
塊嵌套深度 |
depth of inheritance tree |
繼承樹的深度 |
number of packages |
包的數量 |
afferent coupling |
傳入耦合 |
number of Interfaces |
接口的數量 |
mccabe cyclomatic complexity |
McCabe圈複雜度 |
total lines of code |
代碼的總行數 |
instability |
不穩定 |
number of parameters |
參數的數量 |
lack of cohesion of methods |
方法缺乏凝聚力 |
efferent coupling |
傳出耦合 |
number of static methods |
靜態方法數 |
normalized distance |
正常距離 |
abstractness |
抽象性 |
specialization index |
專業化指數 |
weighted methods per class |
每類的加權方法 |
number of static attributes |
靜態屬性數 |
lack of cohesion of methods:
介於0-1之間,0表示最有凝聚力,1表示完全沒有凝聚力。
1)如果所有方法都使用所有的實例字段,一個類是完全有凝聚力的
2)靜態方法和實例方法計數,它還包括構造函數、屬性的getter和setter,所有方法。
在Since Sonar 4.1中此量度已經被刪除。
3) 子的數量NOC(Number ofchildren)子類在類的層次內,子類可以最直接地從屬於一類。隨着子類數量的增大,重用也增加了。但父類抽象的表示可能減少,即一些子類可能不是父類真正的成員,同時,測試數量(用來檢查每個子類在操作前後的要求)也將增加。
4) 方法中聚合的不足LCOM(Lack ofcohesion in Methods)
一個類內的每種方法訪問一個或多個屬性(也稱實例變量)。LCOM是訪問一個或多個相同屬性方法的數量
如果LCOM很大,則說明方法可以通過屬性與其他方法耦合,這就增加了類設計的複雜性。通常,對LCOM值很大的類,可以把它分爲兩個或多個單獨的類,這樣每個類能的設計更方便。
這裏講的耦合和聚合與傳統軟件中講的是一樣的。我們希望高聚合和低耦合,即保持低的LCOM.但在某些時候,LCOM很大也是合理的。
5)每個類的加權方法WMC(Weighted Methodsper Class)
6)Out-of-range:超出範圍,溢出
下面是metric的安全範圍設置,在此頁面中可以設置每項指標的安全範圍。若警告啓用,指標值超出我們設置的安全範圍就好發出警告。
圖24
使用metrics
1. 右鍵單擊您的項目並選擇 Properties 菜單。在結果窗口中,選擇 EnableMetrics plugin 複選框並單擊 OK:
圖25
2. 從 Eclipse 中選擇 Window 菜單打開 Metrics 視圖,然後選擇 Show View | Other...。
3. 選擇 Metrics | Metrics View 打開如圖 13 中顯示的窗口。您需要使用Java 透視圖並重新構建項目,從而顯示這些度量值。
圖26
注:一定要重新構建項目
圖27
Java 模擬技術(mock)
Mock是什麼
Mock通常是指,在測試一個對象A時,我們構造一些假的對象來模擬與A之間的交互,而這些Mock對象的行爲是我們事先設定且符合預期。通過這些Mock對象來測試A在正常邏輯,異常邏輯或壓力情況下工作是否正常。
引入Mock最大的優勢在於:Mock的行爲固定,它確保當你訪問該Mock的某個方法時總是能夠獲得一個沒有任何邏輯的直接就返回的預期結果。
Mock Object的使用通常會帶來以下一些好處:
隔絕其他模塊出錯引起本模塊的測試錯誤。
隔絕其他模塊的開發狀態,只要定義好接口,不用管他們開發有沒有完成。
一些速度較慢的操作,可以用MockObject代替,快速返回。
對於分佈式系統的測試,使用Mock Object會有另外兩項很重要的收益:
通過Mock Object可以將一些分佈式測試轉化爲本地的測試
將Mock用於壓力測試,可以解決測試集羣無法模擬線上集羣大規模下的壓力
mock技術的目的和作用是模擬一些在應用中不容易構造或者比較複雜的對象,從而把測試與測試邊界以外的對象隔離開。
Mock應用場景
在使用Mock的過程中,發現Mock是有一些通用性的,對於一些應用場景,是非常適合使用Mock的:
真實對象具有不可確定的行爲(產生不可預測的結果,如股票的行情)
真實對象很難被創建(比如具體的web容器)
真實對象的某些行爲很難觸發(比如網絡錯誤)
真實情況令程序的運行速度很慢
真實對象有用戶界面
測試需要詢問真實對象它是如何被調用的(比如測試可能需要驗證某個回調函數是否被調用了)
真實對象實際上並不存在(當需要和其他開發小組,或者新的硬件系統打交道的時候,這是一個普遍的問題)
當然,也有一些不得不Mock的場景:
一些比較難構造的Object:這類Object通常有很多依賴,在單元測試中構造出這樣類通常花費的成本太大。
執行操作的時間較長Object:有一些Object的操作費時,而被測對象依賴於這一個操作的執行結果,例如大文件寫操作,數據的更新等等,出於測試的需求,通常將這類操作進行Mock。
異常邏輯:一些異常的邏輯往往在正常測試中是很難觸發的,通過Mock可以人爲的控制觸發異常邏輯。
在一些壓力測試的場景下,也不得不使用Mock,例如在分佈式系統測試中,通常需要測試一些單點(如namenode,jobtracker)在壓力場景下的工作是否正常。而通常測試集羣在正常邏輯下無法提供足夠的壓力(主要原因是受限於機器數量),這時候就需要應用Mock去滿足。
在這些場景下,我們應該如何去做Mock的工作了,一些現有的Mock工具可以幫助我們進行Mock工作。
EasyMock應用
Easymock官網:
EasyMock 是早期比較流行的MocK測試框架。它提供對接口的模擬,能夠通過錄制、回放、檢查三步來完成大體的測試過程,可以驗證方法的調用種類、次數、順序,可以令 Mock 對象返回指定的值或拋出指定異常。通過 EasyMock,我們可以方便的構造 Mock 對象從而使單元測試順利進行。
EasyMock 是採用 MIT license 的一個開源項目
使用EasyMock大致可以劃分爲以下幾個步驟:
① 使用 EasyMock 生成 Mock 對象;
② 錄製 Mock 對象的預期行爲和輸出;
③ 將 Mock 對象切換到 播放 狀態;
④ 調用 Mock 對象方法進行單元測試;
⑤ 對 Mock 對象的行爲進行驗證。
mockito應用
mockito官網
https://code.google.com/p/mockito/
由於官網上不了,可以到csdn上下載
http://download.csdn.net/download/wjjiang917/5519381
是EasyMock之後流行的mock工具。相對EasyMock學習成本低,而且具有非常簡潔的API,測試代碼的可讀性很高。
使用mockito大致可以劃分爲以下幾個步驟:
① 使用 mockito 生成 Mock 對象;
② 定義(並非錄製) Mock 對象的行爲和輸出(expectations部分);
③ 調用 Mock 對象方法進行單元測試;
④ 對 Mock 對象的行爲進行驗證。
PowerMock應用
PowerMock官網
https://code.google.com/p/powermock/
這個工具是在EasyMock和Mockito上擴展出來的,目的是爲了解決EasyMock和Mockito不能解決的問題,比如對static, final, private方法均不能mock。其實測試架構設計良好的代碼,一般並不需要這些功能,但如果是在已有項目上增加單元測試,老代碼有問題且不能改時,就不得不使用這些功能了。
PowerMock 在擴展功能時完全採用和被擴展的框架相同的API, 熟悉 PowerMock 所支持的模擬框架的開發者會發現 PowerMock 非常容易上手。PowerMock 的目的就是在當前已經被大家所熟悉的接口上通過添加極少的方法和註釋來實現額外的功能。目前PowerMock 僅擴展了 EasyMock 和 mockito,需要和EasyMock或Mockito配合一起使用。
代碼評審(review)和重構
Jupiter
Wait…
參考資料
2. http://www.ibm.com/developerworks/cn/java/j-ap01117/
3. http://www.blogjava.net/askcuix/archive/2009/02/08/253775.html
4. http://www.cnblogs.com/sunzhenchao/archive/2013/06/14/3136140.html
5. http://tieba.baidu.com/p/4304901637
6. http://www.ibm.com/developerworks/cn/opensource/os-cn-easymock/
7. http://www.ibm.com/developerworks/cn/java/j-easymock.html
8. http://www.cnblogs.com/huangbin/archive/2013/04/27/3047671.html
9.