程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 診斷Java代碼: Impostor Type錯誤模式

診斷Java代碼: Impostor Type錯誤模式

編輯:關於JAVA

當使用字段中特殊的標記來區別對象類型時,可能會產生標記對相關數據誤貼標簽的錯誤 ― 通稱為 Impostor Type 錯誤模式。在診斷 Java 代碼的這一部分中,Eric Allen 對這個錯誤的症狀和起因進行了分析,詳細說明了預防錯誤發生的方法,並討論了一種吸引人的混合實現方法,這種方法不使用 impostor type,但最後,還是有很多相同的缺點產生。請在 討論論壇與作者及其他讀者分享您對本文的看法。

程序中除了最無關緊要的部分外都要對某些數據類型進行操作。靜態類型系統提供了一種方法,它能夠確保程序不會對給定類型的數據進行不當的操作。Java 語言的優點之一是嚴格的區分類型,所以在程序運行前已消除了類型錯誤。作為開發人員,我們可以使用這個類型系統提供更健壯且沒有錯誤的代碼。然而,我們卻常常沒有讓類型系統發揮出最大的潛力。

Impostor Type 錯誤模式

很多程序可以更多地使用靜態類型系統,但它們沒有這樣做,而是依賴包含區別數據類型標記的特殊字段。

依靠這些特殊字段區別數據類型,這樣的程序放棄了類型系統專門提供給它們的保護措施。當這些標記中的一個對它的數據誤貼了標簽,就會產生我稱之為 Impostor Type的錯誤。

症狀

impostor type 錯誤的一種常見症狀是很多概念上不同類型的數據都被同樣(並且錯誤)的方式處理。另一常見症狀是數據與任何指定的類型都不匹配。

首要規則是,只要當概念上的數據類型和它被程序處理的方法不匹配,就可以懷疑是否發生了這個模式的錯誤。

為說明引入這種模式的錯誤是多麼的輕而易舉,讓我們來考慮一個簡單的示例。假設我們需要處理各種各樣的歐幾裡得幾何學形狀,如圓形、正方形等等。這些幾何形狀沒有坐標,但含有一個 scale 變量,所以可以計算它們的面積。

清單 1. 用 imposter type 實現各種幾何形狀

public class Form {
   String shape;
   double scale;
   public Form(String _shape, double _scale) {
     this.shape = _shape;
     this.scale = _scale;
   }
   public double getArea() {
     if (shape.equals("square")) {
       return scale * scale;
     }
     else if (shape.equals("circle")) {
       return Math.PI * scale * scale;
     }
     else { // shape.equals("triangle"), an equilateral triangle
       return scale * (scale * Math.sqrt(3) / 4);
     }
   }
}

盡管您會發現人們經常這麼做,但用這種方法實現幾何形狀還是存在嚴重缺點。

最顯著的缺點之一是這個方法不能真正的擴展。如果要為我們的 form 引入一個新的幾何形狀(比如,“五邊形”),我們必須進入並修改 getArea() 方法的源代碼。不過可擴展性是個獨立的考慮因素;在本文中,我們把重點放在實現幾何形狀所造成的錯誤的易受性上。我會在以後的文章中回到關於可擴展性的問題上來。

如果我們在程序其它部分構造了一個新的 Form 對象,如下所示,請考慮將會發生什麼情況:

清單 2. 構造一個新的 form

Form f = new Form("sqaure", 2);

當然,“square”被拼錯了,但是編譯器認為,這是完全合法的代碼。

現在考慮一下,當我們試圖對新的 Form 對象調用,比如說 getArea() 方法時發生什麼情況。因為 Form 對象中的幾何形狀與 if-then-else代碼塊中的任一測試的幾何形狀都不匹配,它的面積將在 else分句中被計算,好像它是個三角形似的!

這裡將不會報錯。事實上,在很多情況下,返回值看起來都好象是完全合理的數字。即使我們插入些冗余代碼,檢查 else分句中的隱含條件是否包含(比如說,斷言),也要到代碼執行時才能發現錯誤。

很多其它相似的錯誤也可能在上述代碼中產生。 if-then-else 代碼塊可能會偶爾遺漏一句分句,導致類型與那句分句相對應的所有 Form 都被錯誤地處理了。此外,因為 impostor type 在字段中只是一個 String ,所以它可能會被意外或惡意地修改。

無論用哪一種方法,這樣的修改會帶來各種各樣的損害。

治療和預防措施

正如您可能設想過的那樣,我建議用類型系統在靜態檢查期間將它們清除,從而避免這種類型的錯誤。請考慮這種新穎的實現方法:

清單 3. 用實際類型實現 form

public abstract class Form {
   double scale;
   public Form(double _scale) {
     this.scale = _scale;
   }
   public abstract double getArea();
}
class Square extends Form {
   public Square(double _scale) {
     super(_scale);
   }
   public double getArea() {
     return scale * scale;
   }
}
class Circle extends Form {
   public Circle(double _scale) {
     super(_scale);
   }
   public double getArea() {
     return Math.PI * scale * scale;
   } 
}
class Triangle extends Form {
   public Triangle(double _scale) {
     super(_scale);
   }
   public double getArea() {
     return scale * (scale * Math.sqrt(3) / 4);
   }
}

現在考慮一下,在創建一個新 Form 時,如果誤輸入了“Sqaure”,會發生什麼情況。編譯器將會報錯,告訴我們類 Sqaure 找不到。代碼將連運行的機會也沒有。

同樣地,編譯器將不會允許我們忘記為我們的任意子類定義 getArea() 方法。當然,任何對象要改變 Form 的類型是不可能的。

最後說明

在離開這個主題之前,我還想討論另一種可能的實現,一種我曾經討論過的兩種實現方法的混合。

在這種情況下,不使用 impostor type,但代碼包含很多相同的易受性,似乎它們以前就有。實際上,這種實現方法比對每個類型單獨實現 getArea() 方法 更差。

清單 4. 一種混合的實現方式

public abstract class Form {
   double scale;
   public Form(double _scale) {
     this.scale = _scale;
   }
   public double getArea() {
     if (this instanceof Square) {
       return scale * scale;
     }
     else if (this instanceof Circle) {
       return Math.PI * scale * scale;
     }
     else { // this instanceof Triangle
       return scale * (scale * Math.sqrt(3) / 4);
     }
   }
}
class Square extends Form {
   public Square(double _scale) {
     super(_scale);
   }
}
class Circle extends Form {
   public Circle(double _scale) {
     super(_scale);
   }
}
class Triangle extends Form {
   public Triangle(double _scale) {
     super(_scale);
   }
}

盡管編譯器仍舊會捕獲類型的拼寫錯誤,且對象類型是無法改變的,我們又一次使用了 if-then-else代碼塊調度適當的類型。這樣,我們又要面臨 if-then-else代碼塊中 instanceof檢查與我們所操作的那組類型不匹配的情況。

還必須提出,像第一種實現方法那樣,這個實現方法的擴展性不如第二種。

總結

那麼,簡而言之,這就是我們最近的錯誤模式:

模式:Impostor Type

症狀:一種程序,它用同樣的方式處理概念上不同類型的數據,或者無法識別某種類型的數據。

起因:程序針對各種類型的數據使用帶標記的字段,而不是獨立的類。

治療和預防措施:盡可能將概念上不同的數據類型分成幾個獨立的類。

重點在於,這種語言為您提供了避免這類錯誤的最好資源 ― 只是要記得使用它們。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved