開(kāi)發(fā)人員測(cè)試的主要缺點(diǎn)是:絕大部分測(cè)試都是在理想的場(chǎng)景中進(jìn)行的。在這些情況下并不會(huì)出現(xiàn)缺陷 —— 能導(dǎo)致出現(xiàn)問(wèn)題的往往是那些邊界情況。
什么是邊界情況呢?比方說(shuō),把 null 值傳入一個(gè)并未編寫(xiě)如何處理 null 值的方法中,這是一種邊界情況。大多數(shù)開(kāi)發(fā)人員通常都不能成功測(cè)試這樣的場(chǎng)景,因?yàn)檫@沒(méi)多大意義。但不管有沒(méi)有意義,發(fā)生了這樣的情況,會(huì)拋出一個(gè) NullPointerException,然后整個(gè)程序會(huì)崩潰。
本月,我將為您推薦一種多層面的方法,來(lái)處理代碼中那些不易預(yù)料的缺陷。嘗試為應(yīng)用程序整合進(jìn)防御性編程、契約式設(shè)計(jì)和一種叫做 OVal 的易用的通用驗(yàn)證框架。
清單 1 中的代碼為給定的 Class 對(duì)象(省去了 java.lang.Object,因?yàn)樗袑?duì)象都終由它擴(kuò)展)構(gòu)建一個(gè)類(lèi)層次。但如果仔細(xì)看的話(huà),您會(huì)注意到一個(gè)有待發(fā)現(xiàn)的潛在缺陷,即該方法對(duì)對(duì)象值所做的假設(shè)。
public static Hierarchy buildHierarchy(Class clzz){
Hierarchy hier = new Hierarchy();
hier.setBaseClass(clzz);
Class superclass = clzz.getSuperclass();
if(superclass != null && superclass.getName().equals("java.lang.Object")){
return hier;
}else{
while((clzz.getSuperclass() != null) &&
(!clzz.getSuperclass().getName().equals("java.lang.Object"))){
clzz = clzz.getSuperclass();
hier.addClass(clzz);
}
return hier;
}
}
剛編好這個(gè)方法,我還沒(méi)注意到這個(gè)缺陷,但由于我狂熱地崇拜開(kāi)發(fā)人員測(cè)試,于是我編寫(xiě)了一個(gè)使用 TestNG 的常規(guī)測(cè)試。而且,我還利用了 TestNG 方便的 DataProvider 特性,借助該特性,我創(chuàng)建了一個(gè)通用的測(cè)試用例并通過(guò)另一個(gè)方法來(lái)改變它的參數(shù)。運(yùn)行清單 2 中定義的測(cè)試用例會(huì)產(chǎn)生兩個(gè)通過(guò)結(jié)果!一切都運(yùn)轉(zhuǎn)良好,不是嗎?
清單 2. 驗(yàn)證兩個(gè)值的 TestNG 測(cè)試
import java.util.Vector;
import static org.testng.Assert.assertEquals;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class BuildHierarchyTest {
@DataProvider(name = "class-hierarchies")
public Object[][] dataValues(){
return new Object[][]{
{Vector.class, new String[] {"java.util.AbstractList",
"java.util.AbstractCollection"}},
{String.class, new String[] {}}
};
}
@Test(dataProvider = "class-hierarchies"})
public void verifyHierarchies(Class clzz, String[] names) throws Exception{
Hierarchy hier = HierarchyBuilder.buildHierarchy(clzz);
assertEquals(hier.getHierarchyClassNames(), names, "values were not equal");
}
}
至此,我還是沒(méi)有發(fā)現(xiàn)缺陷,但一些代碼問(wèn)題卻困擾著我。如果有人不經(jīng)意地為 Class 參數(shù)傳入一個(gè) null 值會(huì)怎么樣呢?清單 1 中第 4 行的 clzz.getSuperclass() 調(diào)用會(huì)拋出一個(gè) NullPointerException,是這樣嗎?
測(cè)試我的理論很容易;甚至都不用從頭開(kāi)始。僅僅把 {null, null} 添加到初始 BuildHierarchyTest 的 dataValues 方法中的多維 Object 數(shù)組中,然后再次運(yùn)行它。我定會(huì)得到如圖 1 所示的 NullPointerException:
圖 1. 可怕的 NullPointerException
防御性編程
一旦出現(xiàn)這個(gè)問(wèn)題,下一步是要拿出對(duì)抗的策略。問(wèn)題是我控制不了這個(gè)方法能否接收這種輸入。對(duì)于這類(lèi)問(wèn)題,開(kāi)發(fā)人員通常會(huì)使用防御性編程技術(shù),該技術(shù)專(zhuān)門(mén)用來(lái)在發(fā)生摧毀性后果前捕捉潛在錯(cuò)誤。
對(duì)象驗(yàn)證是處理不確定性的一項(xiàng)經(jīng)典的防御性編程策略。相應(yīng)地,我會(huì)添加一項(xiàng)檢驗(yàn)來(lái)驗(yàn)證 clzz 是否為 null,如清單 3 所示。如果其值終為 null,我會(huì)拋出一個(gè) RuntimeException 來(lái)警告他人注意這個(gè)潛在問(wèn)題。
清單 3. 添加驗(yàn)證 null 值的檢驗(yàn)
public static Hierarchy buildHierarchy(Class clzz){
if(clzz == null){
throw new RuntimeException("Class parameter can not be null");
}
Hierarchy hier = new Hierarchy();
hier.setBaseClass(clzz);
Class superclass = clzz.getSuperclass();
if(superclass != null && superclass.getName().equals("java.lang.Object")){
return hier;
}else{
while((clzz.getSuperclass() != null) &&
(!clzz.getSuperclass().getName().equals("java.lang.Object"))){
clzz = clzz.getSuperclass();
hier.addClass(clzz);
}
return hier;
}
}