<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    新的起點(diǎn) 新的開(kāi)始

    快樂(lè)生活 !

    異常設(shè)計(jì)

    轉(zhuǎn)之 http://blog.csdn.net/success_dream/archive/2006/11/24/1412422.aspx

    摘要

      本文是設(shè)計(jì)技術(shù)專(zhuān)欄文章,討論有關(guān)異常設(shè)計(jì)的問(wèn)題。本文關(guān)注何時(shí)使用異常,并舉例演示異常的恰當(dāng)使用。此外,本文還提供一些異常設(shè)計(jì)的基本原則。

      五個(gè)月前,我開(kāi)始撰寫(xiě)有關(guān)設(shè)計(jì)對(duì)象的文章。本文是設(shè)計(jì)文技術(shù)系列文章的延續(xù),討論了有關(guān)錯(cuò)誤報(bào)告和異常的設(shè)計(jì)原則。我假設(shè)讀者已經(jīng)知道什么是異常,以及異常是如何工作的。你若想回顧一下異常方面的知識(shí),請(qǐng)閱讀本文的姐妹篇《Java異?!?。
      
    異常的好處

       異常帶來(lái)諸多好處。首先,它將錯(cuò)誤處理代碼從正常代碼(normal?code)中分離出來(lái)。你可以將那些執(zhí)行概率為99.9%的代碼封裝在一個(gè)try 塊內(nèi),然后將異常處理代碼----這些代碼是不經(jīng)常執(zhí)行的----置于catch子句中。這種方式的好處是,正常代碼因此而更簡(jiǎn)潔。
      如果你不 知道如何處理某個(gè)方法中的一個(gè)特定錯(cuò)誤,那么你可以在方法中拋出異常,將處理權(quán)交給其他人。如果你拋出一個(gè)檢查異常 (checked?exception),那么Java編譯器將強(qiáng)制客戶(hù)程序員(cilent?programmer)處理這個(gè)潛在異常,或者捕捉之,或 者在方法的throws子句中聲明之。Java編譯器確保檢查異常被處理,這使得Java程序更為健壯。
      
    何時(shí)拋出異常

      異常應(yīng)于何時(shí)拋出?答案歸于一條原則:
      如果方法遇到一個(gè)不知道如何處理的意外情況(abnormal?condition),那么它應(yīng)該拋出異常。
      不幸的是,雖然這條原則易于記憶和引用,但是它并不十分清晰。實(shí)際上,它引出了另一個(gè)的問(wèn)題:什么是意外情況?
      這是一個(gè)價(jià)值6.4萬(wàn)美元的問(wèn)題。是否視某特殊事件為“意外情況”是一個(gè)主觀決定。其依據(jù)通常并不明顯。正因?yàn)槿绱耍艃r(jià)值不菲。
      一個(gè)更有用的經(jīng)驗(yàn)法則是:
      在有充足理由將某情況視為該方法的典型功能(typical?functioning?)部分時(shí),避免使用異常。
      因此,意外情況就是指方法的“正常功能”(normal?functioning)之外的情況。請(qǐng)?jiān)试S我通過(guò)幾個(gè)例子來(lái)說(shuō)明問(wèn)題。
      
    幾個(gè)例子

      第一個(gè)示例使用java.io包的FileInputStream類(lèi)和DataInputStream類(lèi)。這是使用FileInputStream類(lèi)將文件內(nèi)容發(fā)送到標(biāo)準(zhǔn)輸出(standard?output)的代碼:
    //?In?source?packet?in?file?except/ex9/Example9a.java
    import?java.io.*;
    class?Example9a?{

    ????public?static?void?main(String[]?args)
    ????????throws?IOException?{

    ????????if?(args.length?==?0)?{
    ????????????System.out.println("Must?give?filename?as?first?arg.");
    ????????????return;
    ????????}

    ????????FileInputStream?in;
    ????????try?{
    ????????????in?=?new?FileInputStream(args[0]);
    ????????}
    ????????catch?(FileNotFoundException?e)?{
    ????????????System.out.println("Can't?find?file:?"?+?args[0]);
    ????????????return;
    ????????}

    ????????int?ch;
    ????????while?((ch?=?in.read())?!=?-1)?{
    ????????????System.out.print((char)?ch);
    ????????}
    ????????System.out.println();

    ????????in.close();
    ????}
    }
       在本例中,F(xiàn)ileInputStream類(lèi)的read方法報(bào)告了“已到達(dá)文件末尾”的情況,但是,它并沒(méi)有采用拋出異常的方式,而是返回了一個(gè)特殊 值:-1。在這個(gè)方法中,到達(dá)文件末尾被視為方法的“正常”部分,這不是意外情況。讀取字節(jié)流的通常方式是,繼續(xù)往下讀直到達(dá)字節(jié)流末尾。
      與此不同的是,DataInputStream類(lèi)采取了另一種方式來(lái)報(bào)告文件末尾:
    //?In?source?packet?in?file?except/ex9b/Example9b.java
    import?java.io.*;
    class?Example9b?{

    ????public?static?void?main(String[]?args)
    ????????throws?IOException?{

    ????????if?(args.length?==?0)?{
    ????????????System.out.println("Must?give?filename?as?first?arg.");
    ????????????return;
    ????????}

    ????????FileInputStream?fin;
    ????????try?{
    ????????????fin?=?new?FileInputStream(args[0]);
    ????????}
    ????????catch?(FileNotFoundException?e)?{
    ????????????System.out.println("Can't?find?file:?"?+?args[0]);
    ????????????return;
    ????????}

    ????????DataInputStream?din?=?new?DataInputStream(fin);
    ????????try?{
    ????????????int?i;
    ????????????for?(;;)?{
    ????????????????i?=?din.readInt();
    ????????????????System.out.println(i);
    ????????????}
    ????????}
    ????????catch?(EOFException?e)?{
    ????????}

    ????????fin.close();
    ????}
    }
      DataInputStream類(lèi)的readInt()方法每次讀取四個(gè)字節(jié),然后將其解釋為一個(gè)int型數(shù)據(jù)。當(dāng)讀到文件末尾時(shí),readInt()方法將拋出EOFException。
       這個(gè)方法拋出異常的原因有二。首先,readInt()無(wú)法返回一個(gè)特殊值來(lái)指示已經(jīng)到達(dá)文件末尾,因?yàn)樗锌赡艿姆祷刂刀际呛戏ǖ恼蛿?shù)據(jù)。(例如, 它不能采用-1這個(gè)特殊值來(lái)指示文件末尾,因?yàn)?1可能就是流中的正常數(shù)據(jù)。)其次,如果readInt()在文件末尾處只讀到一個(gè)、兩個(gè)、或者三個(gè)字 節(jié),那么,這就可以視為“意外情況”了。本來(lái)這個(gè)方法是要讀四個(gè)字節(jié)的,但只有一到三個(gè)字節(jié)可讀。由于該異常是使用這個(gè)類(lèi)時(shí)的不可分割的部分,它被設(shè)計(jì)為 檢查型異常(Exception類(lèi)的子類(lèi))??蛻?hù)程序員被強(qiáng)制要求處理該異常。
      指示“已到達(dá)末尾”情況的第三種方式在StringTokenizer類(lèi)和Stack類(lèi)中得到演示:
    //?In?source?packet?in?file?except/ex9b/Example9c.java
    //?This?program?prints?the?white-space?separated?tokens?of?an
    //?ASCII?file?in?reverse?order?of?their?appearance?in?the?file.
    import?java.io.*;
    import?java.util.*;
    class?Example9c?{

    ????public?static?void?main(String[]?args)
    ????????throws?IOException?{

    ????????if?(args.length?==?0)?{
    ????????????System.out.println("Must?give?filename?as?first?arg.");
    ????????????return;
    ????????}

    ????????FileInputStream?in?=?null;
    ????????try?{
    ????????????in?=?new?FileInputStream(args[0]);
    ????????}
    ????????catch?(FileNotFoundException?e)?{
    ????????????System.out.println("Can't?find?file:?"?+?args[0]);
    ????????????return;
    ????????}

    ????????//?Read?file?into?a?StringBuffer
    ????????StringBuffer?buf?=?new?StringBuffer();
    ????????try?{
    ????????????int?ch;
    ????????????while?((ch?=?in.read())?!=?-1)?{
    ????????????????buf.append((char)?ch);
    ????????????}
    ????????}
    ????????finally?{
    ????????????in.close();
    ????????}

    ????????//?Separate?StringBuffer?into?tokens?and
    ????????//?push?each?token?into?a?Stack
    ????????StringTokenizer?tok?=?new?StringTokenizer(buf.toString());
    ????????Stack?stack?=?new?Stack();
    ????????while?(tok.hasMoreTokens())?{
    ????????????stack.push(tok.nextToken());
    ????????}

    ????????//?Print?out?tokens?in?reverse?order.
    ????????while?(!stack.empty())?{
    ????????????System.out.println((String)?stack.pop());
    ????????}
    ????}
    }
       上面的程序逐字節(jié)讀取文件,將字節(jié)數(shù)據(jù)轉(zhuǎn)換為字符數(shù)據(jù),然后將字符數(shù)據(jù)放到StringBuffer中。它使用StringTokenizer類(lèi)提取以 空白字符為分隔符的token(這里是一個(gè)字符串),每次提取一個(gè)并壓入Stack中。最后,所有token都被從Stack中彈出并打印,每行打印一 個(gè)。因?yàn)镾tack類(lèi)實(shí)現(xiàn)的是后進(jìn)先出(LIFO)棧,所以,打印出來(lái)的數(shù)據(jù)順序和文件中的數(shù)據(jù)順序剛好相反。
       StringTokenizer類(lèi)和Stack類(lèi)都必須能夠指示“已到達(dá)末尾”情況。StringTokenizer的構(gòu)造方法接納源字符串。每一次調(diào)用 nextToken()方法都將返回一個(gè)字符串,它是源字符串的下一個(gè)token。源字符串的所有token都必然會(huì)被消耗掉, StringTokenizer類(lèi)必須通過(guò)某種方式指示已經(jīng)沒(méi)有更多的token供返回了。這種情況下,本來(lái)是可以用一個(gè)特殊的值null來(lái)指示沒(méi)有更多 token的。但是,此類(lèi)的設(shè)計(jì)者采用了另一個(gè)辦法。他提供了一個(gè)額外的方法hasMoreTokens(),該方法返回一個(gè)布爾值來(lái)指示是否已到達(dá)末 尾。每次調(diào)用nextToken()方法之前,你必須先調(diào)用hasMoreTokens()。
      這種方法表明設(shè)計(jì)者并不認(rèn)為到達(dá)token流的 末尾是意外情況。相反,它是使用這個(gè)類(lèi)的常規(guī)情況。然而,如果你在調(diào)用nextToken()之前不檢查hasMoreTokens(),那么你最后會(huì)得 到一個(gè)異常NoSuchElementException。雖然該異常在到達(dá)token流末尾時(shí)拋出,但它卻是一個(gè)非檢查異常 (RuntimeException的子類(lèi))。該異常的拋出不是為了指示“已到達(dá)末尾”,而是指示一個(gè)軟件缺陷----你并沒(méi)有正確地使用該類(lèi)。
       與此類(lèi)似,Stack類(lèi)有一個(gè)類(lèi)似的方法empty(),這個(gè)方法返回一個(gè)布爾值指示棧已經(jīng)為空。每次調(diào)用pop()之前,你都必須先調(diào)用empty ()方法。如果你忘了調(diào)用empty()方法,而直接在一個(gè)空棧上調(diào)用pop()方法,那么,你將得到一個(gè)異常EmptyStackException。 雖然該異常是棧已經(jīng)為空的情況下拋出的,但它也是一個(gè)非檢查異常。它的作用不是檢測(cè)空棧,而是指示客戶(hù)代碼中的一個(gè)軟件缺陷(Stack類(lèi)的不恰當(dāng)使 用)。
      
    異常表示沒(méi)有遵守契約

      通過(guò)上面的例子,你應(yīng)該已經(jīng)初步了解到,何時(shí)應(yīng)拋出異常而不是使用其他方法進(jìn)行通信。若從另一個(gè)角度來(lái)看待異常,視之為“沒(méi)有遵守契約”,你可能對(duì)應(yīng)當(dāng)怎樣使用異常有更深層的理解。
      面向?qū)ο蟪绦蛟O(shè)計(jì)中經(jīng)常討論的一個(gè)設(shè)計(jì)方法是契約設(shè)計(jì),它指出方法是客戶(hù)(方法的調(diào)用者)和聲明方法的類(lèi)之間的契約。這個(gè)契約包括客戶(hù)必須滿(mǎn)足的前置條件(precondition)和方法本身必須滿(mǎn)足的后置條件(postcondition)。
      前置條件
       String類(lèi)的charAt(int?index)方法是一個(gè)帶有前置條件的方法。這個(gè)方法規(guī)定客戶(hù)傳入的index參數(shù)的最小取值是0,最大取值是 在該String對(duì)象上調(diào)用length()方法的結(jié)果減去1。也就是說(shuō),如果字符串長(zhǎng)度為5,那么index參數(shù)的取值限于0、1、2、3、4。
      后置條件
      String類(lèi)的charAt(int?index)方法的后置條件要求返回值必須是該字符串對(duì)象在index位置上的字符數(shù)據(jù),而且該字符串對(duì)象必須保持不變。
       如果客戶(hù)調(diào)用charAt()并傳入-1、和length()一樣大或者更大的值,那就認(rèn)為客戶(hù)沒(méi)有遵守契約。這種情況下,charAt()方法是不能 正確執(zhí)行的,它將拋出異常StringIndexOutOfBoundsException。該異常指出客戶(hù)程序中存在某種缺陷或String類(lèi)使用不 當(dāng)。
      如果charAt()方法接收的輸入沒(méi)有問(wèn)題(客戶(hù)遵守了契約),但是由于某種原因它無(wú)法返回指定的索引上的字符數(shù)據(jù)(沒(méi)有滿(mǎn)足后置條件),它將拋出異常來(lái)指示這種情況。這種異常指出方法的實(shí)現(xiàn)中包含缺陷或者方法在獲得運(yùn)行時(shí)資源上存在問(wèn)題。
      因此,如果一個(gè)事件表示了“異常條件”或者“沒(méi)有遵守契約”,那么,Java程序所要做的就是拋出異常。
      
    拋出什么?

      一旦你決定拋出異常,你就要決定拋出什么異常。你可以?huà)伋鯰hrowable或其子類(lèi)的對(duì)象。你可以?huà)伋鯦ava?API中定義的、或者自定義的Throwable對(duì)象。那么,如何決定?
      
       通常,你只需要拋出異常,而非錯(cuò)誤。Error是Throwable的子類(lèi),它用于指示災(zāi)難性的錯(cuò)誤,比如OutOfMemoryError,這個(gè)錯(cuò)誤 將由JVM報(bào)告。有時(shí)一個(gè)錯(cuò)誤也可以被Java?API拋出,如java.awt.AWTError。然而,在你的代碼中,你應(yīng)該嚴(yán)格限制自己只拋出異常 (Exception的子類(lèi))。把錯(cuò)誤的拋出留給那些大牛人。
      檢查型異常和非檢查型異常
      現(xiàn)在,主要問(wèn)題就是拋出檢查型異常還是非 檢查型異常了。檢查型異常是Exception的子類(lèi)(或者Exception類(lèi)本身),但不包括RuntimeException和它的子類(lèi)。非檢查型 異常是RuntimeException和它的任何子類(lèi)。Error類(lèi)及其子類(lèi)也是檢查型的,但是你應(yīng)該僅著眼于異常,你所做的應(yīng)該是決定拋出 RuntimeException的子類(lèi)(非檢查異常)還是Exception的子類(lèi)(檢查異常)。
      如果拋出了檢查型異常(而沒(méi)有捕獲它),那么你需要在方法的throws子句中聲明該異常。客戶(hù)程序員使用這個(gè)方法,他要么在其方法內(nèi)捕獲并處理這個(gè)異常,要么還在throws子句中拋出。檢查型異常強(qiáng)制客戶(hù)程序員對(duì)可能拋出的異常采取措施。
      如果你拋出的是非檢查型異常,那么客戶(hù)程序員可以決定捕獲與否。然而,編譯器并不強(qiáng)制客戶(hù)程序員對(duì)非檢查型異常采取措施。事實(shí)上,他們甚至不知道可能這些異常。顯然,在非檢查型異常上客戶(hù)程序員會(huì)少費(fèi)些腦筋。
      有一個(gè)簡(jiǎn)單的原則是:
      如果希望客戶(hù)程序員有意識(shí)地采取措施,那么拋出檢查型異常。
       一般而言,表示類(lèi)的誤用的異常應(yīng)該是非檢查型異常。String類(lèi)的chartAt()方法拋出的 StringIndexOutOfBoundsException就是一個(gè)非檢查型異常。String類(lèi)的設(shè)計(jì)者并不打算強(qiáng)制客戶(hù)程序員每次調(diào)用 charAt(int?index)時(shí)都檢查index參數(shù)的合法性。
      另一方面,java.io.FileInputStream類(lèi)的 read()方法拋出的是IOException,這是一個(gè)檢查異常。這個(gè)異常表明嘗試讀取文件時(shí)出錯(cuò)了。這并不意味著客戶(hù)程序員錯(cuò)誤地使用了 FileInputStream類(lèi),而是說(shuō)這個(gè)方法無(wú)法履行它地職責(zé),即從文件中讀出下一個(gè)字節(jié)。FileInputStream類(lèi)地設(shè)計(jì)者認(rèn)為這個(gè)意外 情況很普遍,也很重要,因而強(qiáng)制客戶(hù)程序員處理之。
      這就是竅門(mén)所在。如果意外情況是方法無(wú)法履行職責(zé),而你又認(rèn)為它很普遍或很重要,客戶(hù)程序員必須采取措施,那么拋出檢查型異常。否則,拋出非檢查型異常。
      自定義異常類(lèi)
       最后,你決定實(shí)例化一個(gè)異常類(lèi),然后拋出這個(gè)異常類(lèi)的實(shí)例。這里沒(méi)有具體的規(guī)則。不要拋出用一條字符串信息指出意外情況的Exception類(lèi),而是自 定義一個(gè)異常類(lèi)或者從已有異常類(lèi)中選出一個(gè)合適的。那么,客戶(hù)程序員就可以分別為不同的異常定義相應(yīng)的catch語(yǔ)句,或者只捕獲一部分。
      你可能希望在異常對(duì)象中嵌入一些信息,從而告訴catch子句該異常的更詳細(xì)信息。但是,你并不僅僅依賴(lài)嵌入的信息來(lái)區(qū)別不同的異常。例如,你并不希望客戶(hù)程序員查詢(xún)異常對(duì)象來(lái)決定問(wèn)題發(fā)生在I/O上還是非法參數(shù)。
       注意,String.charAt(int?index)接收一個(gè)非法輸入時(shí),它拋出的不是RuntimeException,甚至也不是 IllegalArgumentException,而是StringIndexOutOfBoundsException。這個(gè)類(lèi)型名指出問(wèn)題來(lái)自字符 串索引,而且這個(gè)非法索引可以通過(guò)查詢(xún)這個(gè)異常對(duì)象而找出。
      
    結(jié)論

      本文的要點(diǎn)是,異常就是意外情況,而不該用于報(bào)告那些可以作為方法的正常功能的情況。雖然使用異??梢苑蛛x常規(guī)代碼和錯(cuò)誤處理代碼,從而提高代碼的可讀性,但是,異常的不恰當(dāng)使用會(huì)降低代碼的可讀性。
      以下是本文提出的異常設(shè)計(jì)原則:

    如果方法遭遇了一個(gè)無(wú)法處理的意外情況,那么拋出一個(gè)異常。

    避免使用異常來(lái)指出可以視為方法的常用功能的情況。

    如果發(fā)現(xiàn)客戶(hù)違反了契約(例如,傳入非法輸入?yún)?shù)),那么拋出非檢查型異常。

    如果方法無(wú)法履型契約,那么拋出檢查型異常,也可以?huà)伋龇菣z查型異常。

    如果你認(rèn)為客戶(hù)程序員需要有意識(shí)地采取措施,那么拋出檢查型異常。

      
    關(guān)于作者

       Bill?Venners擁有長(zhǎng)達(dá)12年的軟件從業(yè)經(jīng)驗(yàn)。他以Artima軟件公司的名義在硅谷提供軟件咨詢(xún)和培訓(xùn)服務(wù)。他精通不同平臺(tái)上的多種語(yǔ)言, 包括針對(duì)微處理器的匯編程序設(shè)計(jì)、Unix上的C編程、Windows上的C++編程、和Web上的Java開(kāi)發(fā),所開(kāi)發(fā)的軟件覆蓋了電子、教育、半導(dǎo)體 和人身保險(xiǎn)等行業(yè)。他是《深入Java虛擬機(jī)》的作者。

    posted on 2007-03-16 13:39 advincenting 閱讀(379) 評(píng)論(0)  編輯  收藏


    只有注冊(cè)用戶(hù)登錄后才能發(fā)表評(píng)論。


    網(wǎng)站導(dǎo)航:
     

    公告

    Locations of visitors to this pageBlogJava
  • 首頁(yè)
  • 新隨筆
  • 聯(lián)系
  • 聚合
  • 管理
  • <2025年5月>
    27282930123
    45678910
    11121314151617
    18192021222324
    25262728293031
    1234567

    統(tǒng)計(jì)

    常用鏈接

    留言簿(13)

    隨筆分類(lèi)(71)

    隨筆檔案(179)

    文章檔案(13)

    新聞分類(lèi)

    IT人的英語(yǔ)學(xué)習(xí)網(wǎng)站

    JAVA站點(diǎn)

    優(yōu)秀個(gè)人博客鏈接

    官網(wǎng)學(xué)習(xí)站點(diǎn)

    生活工作站點(diǎn)

    最新隨筆

    搜索

    積分與排名

    最新評(píng)論

    閱讀排行榜

    評(píng)論排行榜

    主站蜘蛛池模板: 免费黄网站在线观看| 亚洲色精品VR一区区三区| 亚洲性猛交XXXX| 亚洲三区在线观看无套内射| 国产亚洲精品成人a v小说| 亚洲人成网站在线观看青青| mm1313亚洲精品无码又大又粗| 国产免费午夜a无码v视频| 免费又黄又爽又猛的毛片| 免费人成年激情视频在线观看| 免费A级毛片无码A| 亚洲成a人片在线观看日本麻豆| 亚洲第一页日韩专区| 国产日产亚洲系列最新| 日本亚洲视频在线| 亚洲精品中文字幕麻豆| 亚洲另类精品xxxx人妖| 亚洲中文字幕无码久久| 国产精品亚洲专区无码WEB| 黄网站色视频免费看无下截| 污污污视频在线免费观看| 国产成人AV免费观看| 57pao一国产成视频永久免费| 99在线视频免费观看视频| 国产成人3p视频免费观看 | 久久久久久久久久免免费精品| 国产vA免费精品高清在线观看| 三级网站免费观看| 精品熟女少妇av免费久久| 亚洲成在人线aⅴ免费毛片| 国产精品国产午夜免费福利看| 在线观看亚洲精品福利片| 亚洲国产精品久久久久网站 | 91亚洲va在线天线va天堂va国产| 亚洲国产av高清无码| 羞羞的视频在线免费观看| 一区二区三区在线免费| 日韩免费无码视频一区二区三区| 999国内精品永久免费视频| 国产又粗又猛又爽又黄的免费视频| 亚洲一区AV无码少妇电影☆|