在軟件行業(yè)里,幾乎所有的開發(fā)人員都在談代碼質(zhì)量,而每個(gè)人對代碼質(zhì)量都有一套自己的看法。甚至術(shù)語代碼味道(code smell) 也已進(jìn)入大眾詞匯表,成為描述代碼需要改進(jìn)的一種方式。
代碼味道是由我們開發(fā)人員根據(jù)自己的一些工作經(jīng)驗(yàn)積累來判斷的,有人覺得代碼注釋可以體現(xiàn)代碼結(jié)構(gòu)和質(zhì)量,還有些人又認(rèn)為代碼注釋是用來解釋過于復(fù)雜代碼的一種說明機(jī)制。顯然,Javadocs? 很有用,但是多少內(nèi)嵌注釋才足以維護(hù)代碼?如果代碼已經(jīng)編寫得足夠好,它還需要自己解釋嗎?從這些我們可以看出代碼味道是一種主觀評估的機(jī)制,在很多情況下面,盡管一些看其來糟透了的代碼可能是他人曾經(jīng)編寫最好的代碼。在我們工作中,是否有很多這樣的聲音,”是的,初看起來有點(diǎn)亂,但是它的擴(kuò)展性不錯(cuò)”。
因此,我們需要客觀評估代碼質(zhì)量的方法,某種可以決定性地告訴我們正在查看的代碼是否存在風(fēng)險(xiǎn)的東西。不管您是否相信,這種東西確實(shí)存在!用來客觀評估代碼質(zhì)量的機(jī)制已經(jīng)出現(xiàn)了一段時(shí)間了,只是大多數(shù)開發(fā)人員忽略了它們。這些機(jī)制被稱為代碼度量 (code metric)。
目前一些公司如華為、普元等都在代碼質(zhì)量方面有比較嚴(yán)格的要求,采用CMMI5的規(guī)范來評估代碼質(zhì)量。他們根據(jù)單元測試覆蓋率作為代碼質(zhì)量的一種保證手段。單元測試覆蓋的種類有下面幾種:語句覆蓋、分支覆蓋、條件覆蓋、路徑覆蓋。在單元測試中前三種覆蓋率都非常容易達(dá)到,但會(huì)存在一定的缺陷。在這篇文章中,我就不詳細(xì)解說前三種覆蓋率的計(jì)算方法了,重點(diǎn)談一下路徑覆蓋率的問題。
圈復(fù)雜度,它可以精確地測量路徑復(fù)雜度。通過利用某一方法路由不同的路徑,這一基于整數(shù)的度量可適當(dāng)?shù)孛枋龇椒◤?fù)雜度。實(shí)際上,過去幾年的各種研究已經(jīng)確定:圈復(fù)雜度大于 10 的方法存在很大的出錯(cuò)風(fēng)險(xiǎn)。因?yàn)槿?fù)雜度通過某一方法來表示路徑,這是用來確定某一方法到達(dá) 100% 的覆蓋率將需要多少測試用例的一個(gè)好方法。公式圈復(fù)雜度V(G)=P+1 ,P是代碼中判定結(jié)點(diǎn)的數(shù)量,下面我們看一個(gè)簡單的類。
package com.alisoft.kplan.atest;
public class PathTest {
public String testA(boolean p1){
String a = null;
if(p1){
a = “”+ p1+ “”;
}
return a.trim();
}
}
在我們平時(shí)開發(fā)過程中,通常這樣寫一個(gè)測試用例,語句覆蓋率達(dá)到100%
package com.alisoft.kplan.atest;
import junit.framework.Assert;
import org.junit.Test;
public
class PathTestTest {
@Test
public void testTestA() {
PathTest pt = new PathTest();
Assert.assertEquals(pt.testA(true), “true”);
}
}
這個(gè)測試用例雖然語句覆蓋率達(dá)到100%但是我們會(huì)發(fā)現(xiàn),其中有一個(gè)潛在的空指針錯(cuò)誤沒有被發(fā)現(xiàn)。問題來了,那么我們在編寫測試用例的時(shí)候,怎么來寫一個(gè)優(yōu)秀的測試用例呢,答案很簡單,就是根據(jù)圈復(fù)雜度來計(jì)算你的類方法復(fù)雜度,圈復(fù)雜度值越大,就說明你的方法越復(fù)雜,存在的缺陷會(huì)越多。通過計(jì)算公式V(G)=P+1 testA方法的圈復(fù)雜度為2,那么我們只要編寫兩個(gè)測試用例就可以就可以完成testA()方法的基本路徑覆蓋。我們在看一下這個(gè)測試用例
public class PathTestTest {
@Test
public void testTestA() {
PathTest pt = new PathTest();
Assert.assertEquals(pt.testA(true), “true”);
}
@Test
public void testTestAfalse() {
PathTest pt = new PathTest();
Assert.assertEquals(pt.testA(true), “false”);
}
}
通過這個(gè)測試用例,我們就可以很容易的發(fā)現(xiàn)方法中的那個(gè)空指針錯(cuò)誤。從這個(gè)例子來看,這個(gè)方法非常簡單,因?yàn)樗娜?fù)雜度只有2 ,像我們有些系統(tǒng)中某些方法的圈復(fù)雜度值高達(dá)150左右,那么你還能這么容易的發(fā)現(xiàn)你的程序缺陷嗎?按理論值來算的話,你需要編寫150個(gè)測試用例才能完成每個(gè)基本分支的測試。為什么要TDD模式開發(fā)?為什么要求大家都寫單元測試?為什么評估軟件質(zhì)量要用覆蓋率來評估?歸根結(jié)底一句話:降低代碼復(fù)雜度才能保證軟件質(zhì)量。
推薦大家使用圈復(fù)雜度計(jì)算的工具JavaNCSS,可以生成html報(bào)告。PMD等工具都可以評估代碼復(fù)雜度。
在持續(xù)集成環(huán)境中,隨時(shí)間變化評估方法的復(fù)雜度是很有必要的。如果某一方法的圈復(fù)雜度值在不斷增長,那么您有兩個(gè)響應(yīng)選擇:
1、確保相關(guān)測試用例的路徑覆蓋率是否覆蓋到方法中所有的路徑。
2、重構(gòu)方法,降低長期維護(hù)風(fēng)險(xiǎn)。