追求代码质量: 监视圈复杂度
http://www.ibm.com/developerworks/cn/java/j-cq03316/
每位開發(fā)人員對(duì)代碼質(zhì)量的含義都有著自己的看法,并且大多數(shù)人對(duì)如何查找編寫欠佳的代碼也有自己的想法。甚至術(shù)語代碼味道(code smell)?也已進(jìn)入大眾詞匯表,成為描述代碼需要改進(jìn)的一種方式。
圈什么?
關(guān)于這篇文章和代碼質(zhì)量主題的任何其他文章的問題,請(qǐng)?jiān)L問由 Andrew Glover 主持的?Improve your Java Code Quality?討論論壇。
代碼味道通常由開發(fā)人員直接判定,有趣的是,它是許多代碼注釋綜合在一起的味道。一些人聲稱公正的代碼注釋是好事情,而另一些人聲稱代碼注釋只是解釋過于復(fù)雜的代碼的一種機(jī)制。顯然,Javadocs? 很有用,但是多少內(nèi)嵌注釋才足以維護(hù)代碼?如果代碼已經(jīng)編寫得足夠好,它還需要解釋自己?jiǎn)?#xff1f;
這告訴我們,代碼味道是一種評(píng)估代碼的機(jī)制,它具有主觀性。我相信,那些聞起來味道糟透了的代碼可能是其他人曾經(jīng)編寫的最好的代碼。以下這些短語聽起來是不是很熟悉?
是的,它初看起來有點(diǎn)亂,但是您要看到它多么可擴(kuò)展!!或者
它讓您感到迷惑,但顯然您不了解它的模式。我們需要的是客觀評(píng)估代碼質(zhì)量的方法,某種可以決定性地告訴我們正在查看的代碼是否存在風(fēng)險(xiǎn)的東西。不管您是否相信,這種東西確實(shí)存在!用來客觀評(píng)估代碼質(zhì)量的機(jī)制已經(jīng)出現(xiàn)了一段時(shí)間了,只是大多數(shù)開發(fā)人員忽略了它們。這些機(jī)制被稱為代碼度量 (code metric)。
代碼度量的歷史
幾十年前,少數(shù)幾個(gè)非常聰明的人開始研究代碼,希望定義一個(gè)能夠與缺陷關(guān)聯(lián)的測(cè)量系統(tǒng)。這是一個(gè)非常有趣的主張:通過研究帶 bug 代碼中的模式,他們希望創(chuàng)建正式的模型,然后可以評(píng)估這些模型,在缺陷成為缺陷之前?捕獲它們。
在這條研究之路上,其他一些非常聰明的人也決定通過研究代碼看看他們是否可以測(cè)量開發(fā)人員的生產(chǎn)效率。對(duì)每位開發(fā)人員的代碼行的經(jīng)典度量似乎只停留在表面上:
Joe 生產(chǎn)的代碼要比 Bill 多,因此 Joe 生產(chǎn)率更高一些,值得我們花錢聘請(qǐng)這樣的人。此外,我注意到 Bill 經(jīng)常在飲水機(jī)邊閑晃,我認(rèn)為我們應(yīng)該解雇 Bill。但是這種生產(chǎn)率度量在實(shí)踐中是非常令人失望的,主要是因?yàn)樗菀妆粸E用。一些代碼測(cè)量包括內(nèi)嵌注釋,并且這種度量實(shí)際上受益于剪切粘貼式開發(fā) (cut-and-paste style development)。
Joe 編寫了許多缺陷!其他每條缺陷也都是由他間接造成的。我們不該解雇 Bill,他的代碼實(shí)際上是免檢的。可以預(yù)見,生產(chǎn)率研究被證實(shí)是非常不準(zhǔn)確的,但在管理團(tuán)隊(duì) (management body) 廣泛使用這種生產(chǎn)率度量以期了解每個(gè)人的能力的價(jià)值之前,情況并非如此。來自開發(fā)人員社區(qū)的痛苦反應(yīng)是有理由的,對(duì)于一些人而言,那種痛苦感覺從未真正走遠(yuǎn)。
未經(jīng)雕琢的鉆石
盡管存在這些失敗,但在那些復(fù)雜度與缺陷的相互關(guān)系的研究中仍然有一些美玉。大多數(shù)開發(fā)人員忘記進(jìn)行代碼質(zhì)量研究已有很長(zhǎng)一段時(shí)間了,但對(duì)于那些仍正在鉆研的人而言(特別是如果您也正在為追求代碼質(zhì)量而努力鉆研),會(huì)在今天的應(yīng)用中發(fā)現(xiàn)這些研究的價(jià)值。例如,您曾注意到一些長(zhǎng)的方法有時(shí)難以理解嗎?是否曾無法理解嵌套很深的條件從句中的邏輯?您的避開這類代碼的本能是正確的。一些長(zhǎng)的方法和帶有大量路徑的方法是?難以理解的,有趣的是,這類方法容易導(dǎo)致缺陷。
我將使用一些例子展示我要表達(dá)的意思。
回頁首
數(shù)字的海洋
研究顯示,平均每人在其大腦中大約能夠處理 7(±2)位數(shù)字。這就是為什么大多數(shù)人可以很容易地記住電話號(hào)碼,但卻很難記住大于 7 位數(shù)字的信用卡號(hào)碼、發(fā)射次序和其他數(shù)字序列的原因。
此原理還可以應(yīng)用于代碼的理解上。您以前大概已經(jīng)看到過類似清單 1 中所示的代碼片段:
清單 1. 適用記憶數(shù)字的原理
if (entityImplVO != null) {List actions = entityImplVO.getEntities();if (actions == null) {actions = new ArrayList();}Iterator enItr = actions.iterator();while (enItr.hasNext()) {entityResultValueObject arVO = (entityResultValueObject) actionItr.next();Float entityResult = arVO.getActionResultID();if (assocPersonEventList.contains(actionResult)) {assocPersonFlag = true;}if (arVL.getByName(AppConstants.ENTITY_RESULT_DENIAL_OF_SERVICE).getID().equals(entityResult)) {if (actionBasisId.equals(actionImplVO.getActionBasisID())) {assocFlag = true;}}if (arVL.getByName(AppConstants.ENTITY_RESULT_INVOL_SERVICE).getID().equals(entityResult)) {if (!reasonId.equals(arVO.getStatusReasonID())) {assocFlag = true;}}} }else{entityImplVO = oldEntityImplVO; }清單 1 展示了 9 條不同的路徑。該代碼片段實(shí)際上是一個(gè) 350 多行的方法的一部分,該方法展示了 41 條不同的路徑。設(shè)想一下,如果您被分配一項(xiàng)任務(wù),要修改此方法以添加一項(xiàng)新功能。如果您該方法不是您編寫的,您認(rèn)為您能只做必要的更改而不會(huì)引入任何缺陷嗎?
當(dāng)然,您應(yīng)該編寫一個(gè)測(cè)試用例,但您會(huì)認(rèn)為該測(cè)試用例能將您的特定更改在條件從句的海洋中隔離起來嗎?
回頁首
測(cè)量路徑復(fù)雜度
圈復(fù)雜度?是在我前面提到的那些研究期間開創(chuàng)的,它可以精確地測(cè)量路徑復(fù)雜度。通過利用某一方法路由不同的路徑,這一基于整數(shù)的度量可適當(dāng)?shù)孛枋龇椒◤?fù)雜度。實(shí)際上,過去幾年的各種研究已經(jīng)確定:圈復(fù)雜度(或 CC)大于 10 的方法存在很大的出錯(cuò)風(fēng)險(xiǎn)。因?yàn)?CC 通過某一方法來表示路徑,這是用來確定某一方法到達(dá) 100% 的覆蓋率將需要多少測(cè)試用例的一個(gè)好方法。例如,以下代碼(您可能記得本系列的第一篇文章中使用過它)包含一個(gè)邏輯缺陷:
清單 2. PathCoverage 有一個(gè)缺陷!
public class PathCoverage {public String pathExample(boolean condition){String value = null;if(condition){value = " " + condition + " ";}return value.trim();} }作為響應(yīng),我可以編寫一個(gè)測(cè)試,它將達(dá)到 100% 的行覆蓋率:
清單 3. 一個(gè)測(cè)試產(chǎn)生完全覆蓋!
import junit.framework.TestCase; public class PathCoverageTest extends TestCase {public final void testPathExample() {PathCoverage clzzUnderTst = new PathCoverage();String value = clzzUnderTst.pathExample(true);assertEquals("should be true", "true", value);} }接下來,我將運(yùn)行一個(gè)代碼覆蓋率工具,比如 Cobertura,并將獲得如圖 1 中所示的報(bào)告:
圖 1. Cobertura 報(bào)告
哦,有點(diǎn)失望。代碼覆蓋率報(bào)告指示 100% 的覆蓋率,但我們知道這是一個(gè)誤導(dǎo)。
二對(duì)二
注意,清單 2 中的?pathExample()?方法有一個(gè)值為 2 的 CC(一個(gè)用于默認(rèn)路徑,一個(gè)用于?if?路徑)。使用 CC 作為更精確的覆蓋率測(cè)量尺度意味著第二個(gè)測(cè)試用例是必需的。在這里,它將是不進(jìn)入?if?條件語句而采用的路徑,如清單 4 中的?testPathExampleFalse()?方法所示:
清單 4. 沿著較少采用的路徑向下
import junit.framework.TestCase; public class PathCoverageTest extends TestCase {public final void testPathExample() {PathCoverage clzzUnderTst = new PathCoverage();String value = clzzUnderTst.pathExample(true);assertEquals("should be true", "true", value);} public final void testPathExampleFalse() {PathCoverage clzzUnderTst = new PathCoverage();String value = clzzUnderTst.pathExample(false);assertEquals("should be false", "false", value);} }正如您可以看到的,運(yùn)行這個(gè)新測(cè)試用例會(huì)產(chǎn)生一個(gè)令人討厭的?NullPointerException。在這里,有趣的是我們可以使用圈復(fù)雜度而不是?使用代碼覆蓋率來找出這個(gè)缺陷。代碼覆蓋率指示我們已經(jīng)在一個(gè)測(cè)試用例之后完成了此操作,但 CC 卻會(huì)強(qiáng)迫我們編寫額外的測(cè)試用例。不算太壞,是吧?
幸運(yùn)的是,這里的測(cè)試中的方法有一個(gè)值為 2 的 CC。設(shè)想一下該缺陷被隱藏在 CC 為 102 的方法中的情況。祝您好運(yùn)找到它!
回頁首
圖表上的 CC
Java 開發(fā)人員可使用一些開放源碼工具來報(bào)告圈復(fù)雜度。其中一個(gè)這樣的工具是 JavaNCSS,它通過檢查 Java 源文件來確定方法和類的長(zhǎng)度。此外,此工具還收集代碼庫中每個(gè)方法的圈復(fù)雜度。通過利用 Ant 任務(wù)或 Maven 插件配置 JavaNCSS,可以生成一個(gè)列出以下內(nèi)容的 XML 報(bào)告:
- 每個(gè)包中的類、方法、非注釋代碼行和各種注釋樣式的總數(shù)。
- 每個(gè)類中非注釋代碼行、方法、內(nèi)部類和 Javadoc 注釋的總數(shù)。
- 代碼庫中每個(gè)方法的非注釋代碼行的總數(shù)和圈復(fù)雜度。
該工具附帶了少量樣式表,可以使用它們來生成總結(jié)數(shù)據(jù)的 HTML 報(bào)告。例如,圖 2 闡述了 Maven 生成的報(bào)告:
圖 2. Maven 生成的 JavaNCSS 報(bào)告
此報(bào)告中帶有?Top 30 functions containing the most NCSS?標(biāo)簽的部分詳細(xì)描述了代碼庫中最長(zhǎng)的方法,順便提一句,該方法幾乎總是?與包含最大圈復(fù)雜度的方法相關(guān)聯(lián)。例如,該報(bào)告列出了?DBInsertQueue?類的?updatePCensus()?方法,因?yàn)榇朔椒ǖ姆亲⑨屝锌倲?shù)為 283,圈復(fù)雜度(標(biāo)記為 CCN)為 114。
正如上面所演示的,圈復(fù)雜度是代碼復(fù)雜度的一個(gè)好的指示器;此外,它還是用于開發(fā)人員測(cè)試的一個(gè)極好的衡量器。一個(gè)好的經(jīng)驗(yàn)法則是創(chuàng)建數(shù)量與將被測(cè)試代碼的圈復(fù)雜度值相等的測(cè)試用例。在圖 2 中所見的?updatePCensus()?方法中,將需要 114 個(gè)測(cè)試用例來達(dá)到完全覆蓋。
回頁首
分而治之
在面對(duì)指示高圈復(fù)雜度值的報(bào)告時(shí),第一個(gè)行動(dòng)是檢驗(yàn)所有相應(yīng)測(cè)試的存在。如果存在一些測(cè)試,測(cè)試的數(shù)量是多少?除了極少數(shù)代碼庫以外,幾乎所有代碼庫實(shí)際上都有 114 個(gè)測(cè)試用例用于?updatePCensus()?方法(實(shí)際上,為一個(gè)方法編寫如此多的測(cè)試用例可能會(huì)花費(fèi)很長(zhǎng)時(shí)間)。但即使是很小的一點(diǎn)進(jìn)步,它也是減少方法中存在缺陷風(fēng)險(xiǎn)的一個(gè)偉大開始。
如果沒有任何相關(guān)的測(cè)試用例,顯然需要測(cè)試該方法。您首先想到的可能是:到重構(gòu)的時(shí)間了,但這樣做將打破第一個(gè)重構(gòu)規(guī)則,即將編寫一個(gè)測(cè)試用例。先編寫測(cè)試用例會(huì)降低重構(gòu)中的風(fēng)險(xiǎn)。減少圈復(fù)雜度的最有效方式是隔離代碼部分,將它們放入新的方法中。這會(huì)降低復(fù)雜度,使方法更容易管理(因此更容易測(cè)試)。當(dāng)然,隨后應(yīng)該測(cè)試那些更小的方法。
在持續(xù)集成環(huán)境中,隨時(shí)間變化?評(píng)估方法的復(fù)雜度是有可能的。如果是第一次運(yùn)行報(bào)告,那么您可以監(jiān)視方法的復(fù)雜度值或任何相關(guān)的成長(zhǎng)度(growth)。如果在 CC 中看到一個(gè)成長(zhǎng)度,那么您可以采取適當(dāng)?shù)膭?dòng)作。
如果某一方法的 CC 值在不斷增長(zhǎng),那么您有兩個(gè)響應(yīng)選擇:
- 確保相關(guān)測(cè)試的健康情況仍然表現(xiàn)為減少風(fēng)險(xiǎn)。
- 評(píng)估重構(gòu)方法減少任何長(zhǎng)期維護(hù)問題的可能性。
還要注意的是,JavaNCSS 不是惟一用于 Java 平臺(tái)促進(jìn)復(fù)雜度報(bào)告的工具。PMD 是另一個(gè)分析 Java 源文件的開源項(xiàng)目,它有一系列的規(guī)則,其中之一就是報(bào)告圈復(fù)雜度。CheckStyle 是另一個(gè)具有類似的圈復(fù)雜度規(guī)則的開放源碼項(xiàng)目。PMD 和 CheckStyle 都有 Ant 任務(wù)和 Maven 插件(請(qǐng)參閱?參考資料,從那里獲得關(guān)于至此為止討論的所有工具的更多信息。)
使用復(fù)雜度度量
因?yàn)槿?fù)雜度是如此好的一個(gè)代碼復(fù)雜度指示器,所以測(cè)試驅(qū)動(dòng)的開發(fā) (test-driven development) 和低 CC 值之間存在著緊密相關(guān)的聯(lián)系。在編寫測(cè)試時(shí)(注意,我沒有暗示是第一次),開發(fā)人員通常傾向于編寫不太復(fù)雜的代碼,因?yàn)閺?fù)雜的代碼難以測(cè)試。如果您發(fā)現(xiàn)自己難以編寫某一代碼,那么這是一種警示,表示正在測(cè)試的代碼可能很復(fù)雜。在這些情況下,TDD 的簡(jiǎn)短的 “代碼、測(cè)試、代碼、測(cè)試” 循環(huán)將導(dǎo)致重構(gòu),而這將繼續(xù)驅(qū)使非復(fù)雜代碼的開發(fā)。
所以,在使用遺留代碼庫的情況下,測(cè)量圈復(fù)雜度特別有價(jià)值。此外,它有助于分布式開發(fā)團(tuán)隊(duì)監(jiān)視 CC 值,甚至對(duì)具有各種技術(shù)級(jí)別的大型團(tuán)隊(duì)也是如此。確定代碼庫中類方法的 CC 并連續(xù)監(jiān)視這些值將使您的團(tuán)隊(duì)在復(fù)雜問題出現(xiàn)時(shí)?搶先處理它們。
參考資料
學(xué)習(xí)
- 您可以參閱本文在 developerWorks 全球站點(diǎn)上的?英文原文?。
- “追求代碼質(zhì)量: 不要被覆蓋報(bào)告所迷惑”(Andrew Glover,developerWorks,2006 年 1 月):測(cè)試覆蓋率的測(cè)量是否讓您誤入歧途?找出新系列中的第一篇文章!
- “測(cè)試優(yōu)先 Ruby 編程”(Pat Eyler,developerWorks,2005 年 5 月):test-first 開發(fā)中的一個(gè)簡(jiǎn)單練習(xí),其中包括關(guān)于重構(gòu)的討論。
- “用 Cobertura 測(cè)量測(cè)試覆蓋率”(Elliotte Rusty Harold,developerWorks,2005 年 5 月):關(guān)于使用 Cobertura 來測(cè)量測(cè)試覆蓋率的初級(jí)讀物。
- Java 技術(shù)專區(qū):數(shù)百篇 Java 編程各方面的文章。
獲得產(chǎn)品和技術(shù)
- JavaNCSS:適用于 Java 平臺(tái)的一個(gè)源代碼測(cè)量套件。
- PMD:這個(gè)流行的開放源碼工具掃描 Java 代碼以發(fā)現(xiàn)問題。
- CheckStyle:來自 SourceForge 的另一個(gè) Java 分析工具。
討論
- 參與論壇討論。
- developerWorks blogs:加入 developerWorks 社區(qū)!
轉(zhuǎn)載于:https://www.cnblogs.com/code-style/p/3488885.html
總結(jié)
以上是生活随笔為你收集整理的追求代码质量: 监视圈复杂度的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一个值得敬佩的朋友
- 下一篇: 【2013年总结】 向着IT前进