# 追求代碼質量: 馴服復雜的冗長代碼
_測量代碼是否冗長的工具和度量_
只是從遠處看一眼亂七八糟四處蔓延的代碼塊,開發人員就會感到心驚肉跳 —— 這很正常!冗長的代碼常常是復雜性的標志,會導致代碼難以測試和維護。本月將學習三種測試代碼復雜性的重要方法,它們分別基于方法長度、類長度和內部類耦合。在這一期的 _追求代碼質量_ 系列文章中,專家 Andrew Glover 將向您展示如何使用諸如 PMD 和 JavaNCSS 之類的工具,在您需要的時候獲得更高的精度。
我毫不慚愧地承認,在看到復雜的代碼塊時,我也會感到恐懼和心里發毛。事實上,我敢說您在遇到大量方法和亂七八糟四處蔓延的類時,_也會_ 有些心里發毛的。不能說在這些情況下尋求退路的人不是完人,這只是優秀開發人員的一種本能。過于復雜的代碼難以測試和維護,這通常還意味著更高的出錯率。
我在 [本系列前面的文章](#resources) 中已經解釋了圈復雜性,它是令人討厭的代碼的一種先兆。具有高圈復雜度值的測試方法幾乎總是把事情弄得一團糟,無法輕易收場。上一個月,我向您展示了如何使用 Extract Method 模式重構您的代碼,從而將您帶出迷宮。降低方法的復雜度可以使代碼更易于測試和維護,如圖 1 所示:
##### 圖 1\. 降低復雜度可以使代碼更易于維護和測試

不過,圈復雜性并不是確定高風險代碼的惟一復雜性度量。您還可以利用類長度、方法長度和內部類耦合。這些度量之間存在著錯綜復雜的關聯,但是很容易發現這些關聯。這個月,我將解釋它們為什么那么重要,以及如何使用 PMD 和 JavaNCSS 跟蹤它們。
## 代碼太多了!
了解簡單代碼和流暢代碼之間的區別非常重要。簡單代碼不必過分簡單或易于編寫,只需_易于理解_ 即可。您可以使用 C++ 編寫簡單代碼,就像使用 Visual Basic 編寫它們那樣。不過,用任何語言_反簡化_ 代碼的最快方式都是一次編寫大量的代碼。
## 提高代碼質量
不要錯過 Andrew 的 [討論論壇](http://www.ibm.com/developerworks/forums/dw_forum.jsp?S_TACT=105AGX52&cat=10&S_CMP=cn-a-j&forum=812),可從中獲得大多數緊急問題的答案。
考慮一下如何將此規則應用于方法和類。大多數人對記住信用卡號感到頭疼的一個簡單原因是,我們一次只能管理 7(±2) 片數據。了解了這一點,就會明白過多的條件會給以后帶來麻煩,使測試和維護變得很困難。相同的原理也可以應用于邏輯塊。
所有給定代碼主體通常包含已分組的語句,它們擁有共同的目標,比如創建一個集合,將數據項添加到該集合中。但是,在一個長方法(long method)中分組數量眾多的邏輯塊可能會讓人很快忘記該方法的總體意圖,因為很少有人可以有效處理這樣一個大的數據集。恰恰是這個缺點帶來了代碼基中的維護問題。冗長的方法是缺陷的避風港,因為很少有人可以有效地分析它們。長方法不僅完成太多的工作,也需要人們費很大的勁去理解!
就像長方法會讓開發人員討厭一樣,長類(long class)也會令開發人員討厭。相同的討論也可以應用于總體代碼,冗長的類可能會做太多的工作,并承擔太多的責任。
### 什么樣才算太長?
當然了,長方法或類的劃分有點主觀。有一個很有幫助的經驗法則,您可以說非代碼注釋行超過 100 行的方法是長方法。不過,實際的數值是根據談論的人而變化的。就我而言,截止點(cutoff point)大約是 50 行代碼,但有些開發人員會說,如果某一方法需要您向下滾動整整一天才能看完,那么該方法太長了。截止點的定義取決于您自己。
類似地,您必須有自己的確定正確類大小的良好判斷。許多人所提倡的一條經驗法則是,類的代碼行超過 1,000 行就可以說該類太長了。而另一些人則認為最好不要超過 500 行代碼。
* * *
## 內部類耦合
對于一對象與其他對象之間的關系,復雜模式會不斷重復其自身。對于導入許多外部依賴項或者擁有許多 `public` 方法的類,不但理解起來有些困難,而且所帶來的責任重擔的增加也會導致某種_脆弱_。
我將從依賴項開始。如果某一對象導入的外部類超過 80 個(不包括普通的 Java? 系統庫),那么就可以說該類具有高度_輸出耦合_,這意味著更改導入的類可能會影響該類本身。在最糟糕的情況下,如果導入的是具體的類,并且它們的行為發生更改,那么執行導入的類可能會中斷!(請參閱 [參考資料](#resources),了解關于輸出耦合的更多信息。)
觀察對象導入的數量就很容易預測脆弱性,但如果使用 `.*` 符號(例如 `com.acme.user.*`)導入整個包,則很可能產生誤導。為了更精確起見,可能需要注意對象所擁有的_惟一類型_ 的數量(該數量可通過解析代碼獲得 —— _不是_`import` 語句)。如果應用程序的包結構大致上以某種在少數包中包含許多類的方式設計,則惟一類型度量(types metric)可能很有幫助。
包含許多 `public` 方法的類也有許多導入。這些類通常會成為代碼基的中心,就像 Facades 或工具類那樣。因為存在這種責任(通過大量 `public` 方法導出),所以它們具有高度的輸入耦合,也會導致反向的脆弱性。如果這些類中的任何一個發生更改,各種表面上不相關的應用程序部分_可能_ 發生中斷。
* * *
## 復雜性是如何產生關聯的
到目前為止,所給出的模式都在暗示臃腫的代碼(長方法、太多的 public 方法、過多的條件和導入,等等)將影響可讀性、可測試性和可維護性。因為該模式用各種度量來重復自己,所以所有這些因素都會導致_相互關聯_。例如,長方法通常得容忍高圈復雜度值,如圖 2 所示:
##### 圖 2\. 長方法與圈復雜性相互關聯

不過,相關性并不止于此。具有過多導入的類會有許多惟一類型。這些類通常非常大。而大型的類通常擁有長方法,長方法又常常有很高的圈復雜度值。圖 3 展示了復雜性度量是如何相關的:
##### 圖 3\. 復雜性度量是如何相關的

* * *
## PMD 和 JavaNCSS
少量的繁瑣代碼可用 PMD 和(更小范圍的)JavaNCSS 輕松處理,很容易結合使用這兩種工具,以構建諸如 Ant 和 Maven 之類的平臺。
可以將 PMD 看作是基于規則的引擎,它分析源代碼并報告正被違反的某一規則的所有實例。PMD 目前定義了大約 200 個規則,其中一些特定規則是針對方法長度、類長度和惟一類型的,還有一些用于計算 `public` 方法。您還可以定義定制規則和修改現有規則(例如,為了反映域的需求)。
### 定制 PMD
例如,我將使用 PMD 的經過恰當命名的 ExcessiveMethodLength 規則來發現長方法。此規則的默認長度閾值是 100(這意味著如果某個所掃描方法的長度超過 100 行,則 PMD 會報告出現一個違規),但是如果您喜歡的話,可以降低該閾值。
PMD 規則可以定義屬性,通過站在 PMD 開發團隊的角度很好地進行預見,您可以通過使用規則集文件在運行的時候覆蓋這些屬性。要將 ExcessiveMethodLength 規則的默認值從 100 降低到 50,可以將 `properties` 元素添加到 `rule` 定義中并引用屬性的名稱。在清單 1 中,我將一個名為 `minimum` 的屬性添加到了 PMD `rule` 定義中:
##### 清單 1\. 定制 ExcessiveMethodLength 規則
```
<rule ref="rulesets/codesize.xml/ExcessiveMethodLength">
<properties>
<property name="minimum" value="50"/>
</properties>
</rule>
```
用 Ant 工具調用帶有定制規則集文件的 PMD 需要通過 PMD 任務的 `rulesetfiles` 屬性提供一條到該定制文件的路徑,如清單 2 中所示:
##### 清單 2\. 引用定制規則集文件
```
<pmd rulesetfiles="./tools/pmd/rules-pmd.xml">
<formatter type="xml" toFile="${defaulttargetdir}/pmd_report.xml"/>
<formatter type="html" toFile="${defaulttargetdir}/pmd_report.html"/>
<fileset dir="./src/java">
<include name="**/*.java"/>
</fileset>
</pmd>
```
PMD 報告由源文件導致的違規,正如您在圖 4 中可以看到的,在本例中,只有少數幾個方法的源代碼行超過了 50 行:
##### 圖 4\. PMD Ant 報告的示例

對于長類,PMD 有 ExcessiveClassLength 規則,長類的默認值為 1,000 行代碼。對于 ExcessiveMethodLength 規則,很容易使用更適合的值覆蓋默認值。此外,PMD 還有一個用來計算惟一類型的規則,即 CouplingBetweenObjects 規則。要計算導入,請參見 ExcessiveImports 規則。這兩個規則都是可配置的。
### 使用 JavaNCSS 測量代碼是否冗長
PMD 定義了用來分析源代碼的特定規則,與 PMD 相對,JavaNCSS 分析代碼基并報告_所有一切_ 與代碼長度相關的事項,包括類大小、方法大小和類中找到的方法數量。對于 JavaNCSS,閾值無關緊要,它計算所找到的每個文件并報告值,而_不管_ 大小如何。盡管與 PMD 相比較而言,這類數據看起來似乎有些呆板(并且可能有點羅嗦!),但它有它存在的道理。
通過報告所有文件大小,JavaNCSS 使理解相關值成為可能,而 PMD 常常難以做到這一點。例如,PMD 只報告違規的文件,這意味著只理解部分代碼基的數據,而 JavaNCSS 在上下文中提供了代碼長度數據,如圖 5 所示:
##### 圖 5\. JavaNCSS Ant 報告的示例

* * *
## 結束語
綠地開發(greenfield development)是指開發團隊首先開發一個空白的 IDE 控制臺,并用漂亮、簡潔的代碼填充它,這只是軟件應用程序生存期中一個_非常小_ 的片段。如今,很多跨國企業仍然在運行基于 COBOL 的應用程序,從開發人員的角度看,這意味著要與您不認識的人在很久以前編寫的代碼作斗爭。
在遇到這樣的難題時,通常會令人感到非常厭惡,您只能在連續幾天的時間里聲稱自己生病了進行逃避。隨后的某一時刻,您必須面對大量代碼塊并將它們搞定。使用針對類長度、方法長度和內部類耦合的復雜性度量(即對象導入和惟一類型)是理解您所面臨的困難的第一步。從一些與類大小和方法大小有關的經驗法則開始,然后使用諸如 PMD 和 JavaNCSS 之類的工具詳細介紹。
當第一次在遺留代碼基上使用復雜性度量時,您將了解到一個龐大的數量,但不要就此停住腳步。通過繼續監視復雜性度量,您可以作出更明智的決定,并在不斷擴展和維護代碼時降低風險。
- 追求代碼質量
- 追求代碼質量: 對 Ajax 應用程序進行單元測試
- 追求代碼質量: 使用 TestNG-Abbot 實現自動化 GUI 測試
- 追求代碼質量: 用 AOP 進行防御性編程
- 追求代碼質量: 探究 XMLUnit
- 追求代碼質量: 用 JUnitPerf 進行性能測試
- 追求代碼質量: 通過測試分類實現敏捷構建
- 追求代碼質量: 可重復的系統測試
- 追求代碼質量: JUnit 4 與 TestNG 的對比
- 追求代碼質量: 馴服復雜的冗長代碼
- 追求代碼質量: 用代碼度量進行重構
- 追求代碼質量: 軟件架構的代碼質量
- 讓開發自動化: 除掉構建腳本中的氣味
- 追逐代碼質量: 決心采用 FIT
- 追求代碼質量: 不要被覆蓋報告所迷惑