歡迎光臨診斷 Java 代碼
一個隔周更新的新專欄
著重討論和您日常編程工作有關的 Java 解決方案
本文為第一篇
介紹了錯誤模式的概念
一個非常有用的概念
它將提高您檢測和修正代碼中錯誤的能力
您會了解到一種最普遍的錯誤模式
這將為您開始識別和避免更高級的錯誤模式奠定基礎
錯誤模式和它們為什麼有用
正如好的編程技能涉及很多設計模式(您可以在不同的程序上下文中組合和應用這些模式)的知識一樣
好的調試技能也涉及對錯誤模式的一定了解
錯誤模式就是已發出的錯誤和程序中潛在的錯誤之間的重復出現的相互關系
這種概念對編程來說並不新鮮
醫生們在診斷疾病時依靠相似類型的相互關系
他們在實習期間通過和資格較老的醫生共同工作來學習這些
他們的教育就是集中在做這種診斷上的
相反
我們軟件工程師的教育是集中在過程設計和算法分析上的
這些技能固然重要
但是人們對調試過程的教育卻很少關注
相反
我們得自己去
拾起
這種技能
隨著極端編程的出現和它對單元測試的注重
這種做法已經開始改變了
但是頻繁的單元測試只是解決了問題的一部分
一旦發現錯誤
就必須診斷和糾正它們
幸運的是
很多錯誤都遵循我們可以識別的幾種錯誤模式的其中一種
一旦您可以識別出這些錯誤模式
您就可以診斷出錯誤的原因並且更快地糾正它了
錯誤模式與反模式有關
反模式是一次又一次被證明是失敗的公共軟件設計的模式
雖然反模式是設計模式
錯誤模式卻是與編程錯誤相關的錯誤的程序行為的模式
這與設計根本沒有關系
而是與編程和調試過程有關
通過示例學習
為了說明錯誤模式後面的思想
讓我們來考慮一種基本錯誤模式
編程新手(經常還有更高級的程序員)常常會遇到這種錯誤模式
在後面的文章中
我們會談到更高級的錯誤模式
對每一種模式
我會討論將有助於把該模式的錯誤的發生控制到最少的編程原則(並非暗示所有的錯誤都是不遵循編程原則的結果
不管我們遵循多少原則
我們都會犯錯誤)
為了分類起見
我會使用下面的形式(從醫學上借用一些術語)來概括錯誤模式描述
模式名稱
症狀
起因
治療方法和預防措施
Rogue Tile 模式
也許它是編程新手中最普遍的錯誤模式
起因是復制和粘貼一段代碼到程序的其它部分
有時
復制的一小部分因為功能上需求的略微不同而作了改動
不可避免地
錯誤在一個副本中被修正了
而在另一個副本中沒有被修正
這樣在錯誤症狀復發時就會讓您很頭疼
盡管大多數程序員很快就熟悉了這種錯誤模式
但他們中很少人采取適當的措施來將這種錯誤的出現控制到最少
您很容易就會偷懶不去思考而簡單地復制您認為已經可以運行的代碼
但是工作效率由於修正代碼而喪失
這是因為不加選擇的復制—粘貼操作很快降低了復制代碼帶來的任何工作效率
我稱此為 Rogue Tile 模式是因為
一段代碼的各個副本可以被看成是分布在程序中的
tile
由於不同副本中的代碼出現了差異
副本就變成了
rogue tile
症狀
這種錯誤的模式的最普遍症狀是
在您認為已經修正了問題以後
程序還繼續表現出錯誤的行為
起因
為了理解這種情況發生的原因
我們來看看下面的二元樹類層次結構
public abstract class Tree {
}
public class Leaf extends Tree {
public Object value;
}
public class Branch extends Tree {
public Object value;
public Tree left;
public Tree right;
}
對於這些類要注意的第一件事就是
兩種具體類都包含 Object 類型的 value 字段
如果您決定稍後讓樹包含
比如說
Interger
您也許會忘記更新其中的一個字段聲明
如果程序的其它部分需要這些字段是 Interger 的話
程序就很可能不會編譯
您或許記得您改變了其中一個類的 value 字段的類型
卻忽略了一個事實
就是您沒有在其它類中作相應的改變
一些預防措施
當然
這個示例所示的錯誤是編程新手可以很快學會通過分解出公共代碼來避免的
在本例中
字段聲明應該移到 Tree 類中
它的兩個子類就會繼承這個字段
而且對字段聲明的任何改變都只需要在一個地方出現
繼續看這個示例
我們可能還會編寫在一個 Tree 中相加和相乘所有節點的方法
為了簡單起見
我將以遞歸的方式來編寫這些方法
// in class Tree:
public abstract int add();
public abstract int multiply();
// in class Branch:
public int add() {
return this
value
intValue() + left
add() + right
add();
}
public int multiply() {
return this
value
intValue() * left
multiply() + right
multiply();
}
// in class Leaf:
public int add() { return this
value
intValue(); }
public int multiply() { return this
value
intValue(); }
請注意我在 multiply 方法中為 Branch 類引入的錯誤
我沒有用第三項去乘
而是加了它
錯誤發生了
因為我通過復制 add 方法中的代碼並作輕微(但不完全)的改動創建了 multiply 方法
這種錯誤非常隱蔽
因為調用 multiply 方法永遠不會發出錯誤信號
事實上
在很多情況下
它會返回一個看上去完全合理的結果
就象以前一樣
我們可以通過分解出公共代碼來將這種錯誤控制到最少
在這種情況下
我們可以編寫一個單獨的方法
它在 Tree 上累計一個運算符(作為一個參數傳送)
我們可以使用一種被稱為公共模式的設計模式(不是錯誤模式!)在對象中封裝這個運算符
public abstract class Operator {
public abstract int apply(int l
int r);
}
public class Adder extends Operator {
public int apply(int l
int r) {
return l + r;
}
}
public class Multiplier extends Operator {
public int apply(int l
int r) {
return l * r;
}
}
然後我們就可以如下面的代碼所示在我們的 Tree 類層次結構中改變這個方法
// in class Tree:
public abstract int accumulate(Operator o);
public int add() {
return this
accumulate(new Adder());
}
public int multiply() {
return this
accumulate(new Multiplier());
}
// in class Leaf:
public int accumulate(Operator o) {
return value
intValue();
}
in class Branch:
public int accumulate(Operator o) {
return o
apply(this
value
intValue()
o
apply(left
accumulate(o)
right
accumulate(o)));
}
通過分解出公共代碼
我們消除了在 add 和 multiply 方法正文中出現復制—粘貼錯誤的可能性
另外
請注意我們不再需要為 Tree 的每一個子類編寫單獨的 add 和 multiply 方法了
分解出公共代碼是一個很好的習慣
但它並不適用於所有的情況
比如說
Java 類型系統的簡單性經常迫使我們在精確類型檢驗和保持對程序的每個不同的功能性元素的單點控制(請參閱參考資料
閱讀我寫的關於 NextGen 的文章)之間作出選擇
正因為這個
Rogue Tile 模式是所有開發人員必須一直努力以控制到最少的一種錯誤類型
From:http://tw.wingwit.com/Article/program/Java/JSP/201311/19742.html