国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

參數(shù)上溯造型解決了錯(cuò)誤的方法調(diào)用

2019-11-18 15:14:25
字體:
供稿:網(wǎng)友

  內(nèi)容:
整體和部分
Broken Dispatch 錯(cuò)誤模式
癥狀
起因
治療和預(yù)防措施
結(jié)論
參考資料
關(guān)于作者
對(duì)本文的評(píng)價(jià)

在象 java 語言這樣的面向?qū)ο蟮恼Z言中,方法是可重載和可覆蓋的,即便在中等復(fù)雜程度的程序中,方法調(diào)度也會(huì)給代碼治理帶來困難。在本周的診斷 Java 代碼中,Eric Allen 討論了由這些困難導(dǎo)致的錯(cuò)誤模式,概述了由于參數(shù)不匹配導(dǎo)致的錯(cuò)誤方法調(diào)用的征兆,同時(shí)給出了應(yīng)對(duì)這個(gè)問題的一些辦法。
整體和部分
還記得這條諺語嗎,“整體大于部分之和”?假如把一個(gè)個(gè)獨(dú)立的事件組合成一個(gè)相互作用的整體,產(chǎn)生的結(jié)果會(huì)比單個(gè)個(gè)體的作用之和要大得多。

程序也是一樣的道理。隨著一個(gè)個(gè)新方法被添加到程序中,整個(gè)程序可能的控制流程迅速增加。對(duì)于大型程序而言,很快局面就會(huì)無法控制了。就象是一個(gè)荒謬而又不可思議的戲法,有時(shí)您得到的最終結(jié)果并不是您所期望的方向 ? 這同您在重載方法或者覆蓋方法時(shí)碰到的情況有些類似。

快速跟蹤代碼
清單 1. 實(shí)現(xiàn)不可變列表
我們討論的起點(diǎn)
清單 2. 為鏈表定義強(qiáng)制參數(shù)
編寫構(gòu)造函數(shù)以強(qiáng)制類 LinkedList 的所有實(shí)例都是 String 列表。

清單 3. 新構(gòu)造函數(shù)用唯一的 String 表達(dá)式列表作為參數(shù)。
介紹錯(cuò)誤的行為

清單 4. 在方法調(diào)用中上溯造型參數(shù)
重寫有關(guān)的 LinkedList 構(gòu)造函數(shù)是一種簡單的解決方案。

Broken Dispatch 錯(cuò)誤模式
面向?qū)ο笳Z言的最強(qiáng)大的特性之一就是繼續(xù)多態(tài)性。這一特性答應(yīng)我們根據(jù)參數(shù)類型重載和覆蓋方法。但是,象其它功能強(qiáng)大的工具一樣,這個(gè)特性也會(huì)引入新的危險(xiǎn)。

雖然 Java 程序員們很快就能學(xué)會(huì)治理一次調(diào)用中將調(diào)用哪個(gè)方法的規(guī)則,但在大型程序中卻很輕易出現(xiàn)這種情況:在一個(gè)類中重載了一個(gè)方法,結(jié)果卻是以前在另一個(gè)類中可以運(yùn)行的代碼被中斷了。這樣的錯(cuò)誤正符合我所說的 Broken Dispatch 模式。

該模式可以描述如下:

傳遞給某個(gè)重載方法,比如 foo 的參數(shù),卻被傳給了另一個(gè)方法,比如 goo,它支持更廣泛的參數(shù)類型。
goo 然后通過這些參數(shù)調(diào)用 foo。
但是由于 goo 內(nèi)的這些參數(shù)的靜態(tài)類型更為廣泛,因此,可能會(huì)調(diào)用方法 foo 的錯(cuò)誤版本。
象這樣的錯(cuò)誤很難診斷,因?yàn)榭赡苤皇翘砑恿诵碌姆椒ǎǘ皇切薷默F(xiàn)有的方法)就引入了錯(cuò)誤。而且,在發(fā)現(xiàn)問題之前,程序可能會(huì)繼續(xù)執(zhí)行相當(dāng)長的一段時(shí)間。

癥狀
為了說明這種模式的本質(zhì),讓我們來看看下面這段示例代碼,它是為實(shí)現(xiàn)我前面的文章“ 空標(biāo)志錯(cuò)誤模式”中的不可變列表而編寫的。

清單 1. 實(shí)現(xiàn)不可變列表

interface List {

public Object getFirst();

public List getRest();

}

class Empty implements List {

public Object getFirst() { throw new NoSUChElementException(); }

public List getRest() { throw new NoSuchElementException(); }

public boolean equals(Object that) {

return this.getClass() == that.getClass();

}

}

class Cons implements List {

Object first;

List rest;

Cons(Object _first) {

this.first = _first;

this.rest = new Empty();

}

Cons(Object _first, List _rest) {

this.first = _first;

this.rest = _rest;

}

public Object getFirst() { return this.first; }

public List getRest() { return this.rest; }

...

}

在那篇文章中,我們把鏈表實(shí)現(xiàn)作為這些不可變列表的容器。

假設(shè)我們?cè)谝粋€(gè)獨(dú)立的包中實(shí)現(xiàn)鏈表,我們知道這個(gè)包中類 LinkedList 的所有實(shí)例都將是 String 列表。我們可以象下面這樣編寫構(gòu)造函數(shù)來強(qiáng)制定義該不變量:

清單 2. 為鏈表定義強(qiáng)制參數(shù)

public class LinkedList {

PRivate List value;

/**

* Constructs an empty LinkedList.

*/

public LinkedList() { this.value = new Empty(); }

/**

* Constructs a LinkedList containing only the given element.

*/

public LinkedList(String _first) { this.value = new Cons(_first); }

/**

* Constructs a LinkedList consisting of the given Object followed by

* all the elements in the given LinkedList.

*/

public LinkedList(String _first, LinkedList _rest) {

this.value = new Cons(_first, _rest.value);

}

public Object getFirst() { return this.value.getFirst(); }

public LinkedList getRest() {

return new LinkedList(this.value.getRest());

}

public void push(String s) { this.value = new Cons(s, this.value); }

public String pop() {

String result = (String)this.value.getFirst();

this.value = this.value.getRest();

return result;

}

public boolean isEmpty() { return this.value instanceof Empty; }

public String toString() {...}

...

}

假設(shè)我們寫了這些代碼,并且所有的測試案例都可以正常運(yùn)行。(或者,更現(xiàn)實(shí)些,假設(shè)它起初并不能正常運(yùn)行,可經(jīng)過幾個(gè)調(diào)試周期后,我們使它變得能夠正常運(yùn)行了。)

也許幾個(gè)月后,您開發(fā)了類 Cons 的一個(gè)新構(gòu)造函數(shù),它使用列表的 String 表達(dá)作為其唯一的參數(shù)。這種構(gòu)造函數(shù)非常有用 ? 它答應(yīng)我們用下面這樣的表達(dá)式構(gòu)造新的列表:

清單 3. 新構(gòu)造函數(shù)僅使用列表的 String 表示法作為參數(shù)

new Cons(′(this is a list)")

new Cons(′(so is this)")

這樣,我們寫了這個(gè)構(gòu)造函數(shù)而且它的所有測試案例也正常運(yùn)行了。太棒了!但是,接著,我們發(fā)現(xiàn),太不可思議了,類 LinkedList 的方法測試中有一些忽然中斷了。發(fā)生了什么事?

起因
問題在于類 LinkedList 的構(gòu)造函數(shù),它只有一個(gè) String 作為參數(shù)。

這個(gè)構(gòu)造函數(shù)以前曾調(diào)用底層的類 Cons 的構(gòu)造函數(shù)。但是,既然我們用一個(gè)更加明確的方法 ? 該方法只有一個(gè) String 作為參數(shù) ? 重載了這個(gè)構(gòu)造函數(shù),那么被調(diào)用的就是這個(gè)更加非凡的方法了。

除非傳遞給 LinkedList 構(gòu)造函數(shù)的 String 是一個(gè)有效的 Cons 表示法,否則試圖對(duì)其進(jìn)行語法分析時(shí)就會(huì)導(dǎo)致程序崩潰。更糟糕的是,假如 String 正好是一個(gè)有效的 Cons 表示法,程序就會(huì)使用這些毀壞的數(shù)據(jù)繼續(xù)執(zhí)行。假如那樣的話,我們就在數(shù)據(jù)中引入了一個(gè)破壞者數(shù)據(jù)。關(guān)于破壞者數(shù)據(jù)的討論,請(qǐng)參閱最后一部分,“ 破壞者數(shù)據(jù)錯(cuò)誤模式”。

Broken Dispatch 錯(cuò)誤,與所有的錯(cuò)誤模式一樣,在“布滿測試”的代碼(借用自極端編程術(shù)語)中最輕易診斷,在這種環(huán)境中連最微不足道的方法也有相應(yīng)的單元測試。在這樣的環(huán)境里,最為一般的癥狀是為您從未碰過的代碼編寫的測試案例忽然中斷運(yùn)行。

假如這種情況發(fā)生,有可能是 Broken Dispatch 模式的一種情況。假如測試案例中斷是在您重載另一個(gè)方法后立即發(fā)生的,那就幾乎可以肯定。

假如這段代碼沒有經(jīng)過布滿測試,情況就變得更加困難了。錯(cuò)誤癥狀可能表現(xiàn)為,比如,返回速度比預(yù)期快得多(并且結(jié)果錯(cuò)誤)的方法調(diào)用。換句話說,您可能會(huì)發(fā)現(xiàn)本來應(yīng)該發(fā)生的某些事件從未發(fā)生(因?yàn)檎_的方法未曾被執(zhí)行)。

要記住,盡管類似的癥狀也可能是其它錯(cuò)誤模式的緣故。但是,假如碰到這種的癥狀,最好是開始寫更多的單元測試,從發(fā)現(xiàn)錯(cuò)誤的方法開始,回退測試程序執(zhí)行的過程。

治療和預(yù)防措施
關(guān)于這個(gè)錯(cuò)誤模式的好消息是有一些簡單的解決方案。最直接的一種方法是把方法調(diào)用中的參數(shù) 上溯造型。在我們的示例中,這意味著重寫相關(guān)的 LinkedList 構(gòu)造函數(shù),如下所示:

清單 4. 在方法調(diào)用中上溯造型參數(shù)

public LinkedList(String _first) {

this.value = new Cons((Object)_first);

}

當(dāng)然,這種辦法只解決了調(diào)用 Cons 構(gòu)造函數(shù)這個(gè)問題。還有其它地方的調(diào)用,我們也得上溯造型,這種技術(shù)對(duì)于 Cons 類的客戶來說是很討厭的。在這樣的情形下,您就得權(quán)衡一下,應(yīng)用這個(gè)方便的構(gòu)造函數(shù)給您帶來的好處以及它引入錯(cuò)誤的潛在危險(xiǎn),二者孰重孰輕了。

接口的表達(dá)性和健壯性之間的平衡也存在這種困境。一種兩全其美的方法是用一個(gè) static 方法來替代這個(gè) Cons 的 String 構(gòu)造函數(shù),該 static 方法只有一個(gè) String 作為參數(shù)并返回一個(gè)新的 Cons 對(duì)象。

總結(jié)
以下是對(duì)上面這個(gè)錯(cuò)誤模式的總結(jié):

模式: Broken Dispatch
癥狀:在重載了另一個(gè)方法之后,測試您從未碰過的代碼的測試案例忽然發(fā)生中斷。
起因: 重載使得未碰過的方法調(diào)用了一個(gè)方法,而該方法不是您希望調(diào)用的那個(gè)方法。
治療和預(yù)防措施:插入顯式上溯造型。或者,重新考慮您提供給不同的類的方法集。
記住,當(dāng)您重載或者覆蓋一個(gè)方法時(shí),參數(shù)結(jié)構(gòu)是確保按意圖調(diào)用方法的一個(gè)要害部分。

下個(gè)月,我們將暫停錯(cuò)誤模式的討論轉(zhuǎn)而處理其它一些重要的課題。診斷 Java 代碼的下一部分將考查尾遞歸方法如何影響 Java 程序的性能。請(qǐng)不要擔(dān)心,我們很快就會(huì)回到錯(cuò)誤模式上來。

參考資料

參加本文的討論論壇。
務(wù)必把 Java 語言規(guī)范加為書簽,該書提供不錯(cuò)的關(guān)于方法調(diào)用規(guī)則的討論。
經(jīng)典的 Java VM 里有一個(gè)錯(cuò)誤,導(dǎo)致方法覆蓋不能正確的調(diào)度除它們的父類以外的包里定義的某些子類。請(qǐng)參閱 Sun Web 站點(diǎn)的討論以獲取具體信息。
Bill Venners 的 Under the Hood(JavaWorld,1997 年 6 月)講述了 JVM 中方法調(diào)用的字節(jié)碼實(shí)現(xiàn)。
下載 JUnit 并使您的代碼“布滿測試”。
要得到更多的關(guān)于極端編程方法的信息,請(qǐng)閱讀“XP 精華”(developerWorks,2001 年 3 月),它提供了關(guān)于這種非常受歡迎而且靈活的進(jìn)程的優(yōu)秀文摘。
閱讀 Eric 的診斷 Java 代碼的完整系列。

關(guān)于作者
Eric Allen 畢業(yè)于 Cornell 大學(xué),曾獲得計(jì)算機(jī)科學(xué)和數(shù)學(xué)的學(xué)士學(xué)位。他目前是 Cycorp 公司的 Java 軟件開發(fā)人員帶頭人,還是 Rice 大學(xué)的編程語言小組的兼職碩士生。他的研究涉及 Java 的正規(guī)語義模型和 Java 語言的擴(kuò)展,都是在源代碼和字節(jié)碼的級(jí)別上的。目前,他正在為 NextGen 編程語言實(shí)現(xiàn)一種從源代碼到字節(jié)碼的編譯器,這也是 Java 語言的泛型運(yùn)行時(shí)類型的一種擴(kuò)展。請(qǐng)通過 eallen@cyc.com 與 Eric 聯(lián)系。

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 九寨沟县| 清丰县| 西充县| 鄂托克旗| 张家口市| 泸定县| 洪江市| 禄丰县| 涟源市| 望都县| 冀州市| 台南市| 南陵县| 西盟| 明水县| 铜川市| 温州市| 浑源县| 莱西市| 安宁市| 双城市| 哈尔滨市| 利辛县| 泰来县| 大厂| 阿图什市| 阳东县| 香格里拉县| 洮南市| 汾西县| 台湾省| 通城县| 二连浩特市| 措美县| 禄丰县| 兴海县| 探索| 翼城县| 江孜县| 仙游县| 深州市|