国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

Home 類庫下載 java類庫 Writing Quality Code: Tips for Improving Java Programs: Exceptions

Writing Quality Code: Tips for Improving Java Programs: Exceptions

Oct 14, 2016 pm 03:53 PM

 There are three mechanisms for Java exceptions:

The Error class and its subclasses represent errors. They are exceptions that do not need to be handled by programmers and cannot be handled, such as VirtualMachineError virtual machine errors, ThreadDeath thread zombies, etc.

The RunTimeException class and its subclasses represent unchecked exceptions, which are exceptions that may be thrown by the system. Programmers can handle them or not. The most classic ones are NullPointException and IndexOutOfBoundsException out-of-bounds exceptions.

Exception class and its subclasses (excluding unchecked exceptions) represent checked exceptions. This is an exception that programmers must handle. If not handled, the program cannot be compiled. For example, IOException represents an I/O exception. , the database access exception represented by SQLException.

We know that the creation process of an object goes through memory allocation, static code initialization, constructor execution, etc. The key step in object generation is the constructor. Is it also allowed to throw exceptions in the constructor? From a Java syntax perspective, you can throw exceptions in the constructor. All three types of exceptions are acceptable. However, from the perspective of system design and development, try not to throw exceptions in the constructor. We use three different types of exceptions. exception to illustrate it.

(1). Errors thrown in the constructor cannot be handled by programmers.

When the constructor is executed, if a VirtualMachineError occurs, there is no other way but to throw it. The programmer cannot predict this. The occurrence of class errors cannot be caught and handled.

(2). The constructor should not throw unchecked exceptions

Let’s take a look at such an example. The code is as follows:

class Person {
    public Person(int _age) {
        // 不滿18歲的用戶對象不能建立
        if (_age < 18) {
            throw new RuntimeException("年齡必須大于18歲.");
        }
    }

    public void doSomething() {
        System.out.println("doSomething......");
    }
}

The intention of this code is obvious. Users under the age of 18 will not generate a Person. Instance objects, without objects, the class behavior doSomething method cannot be executed. The idea is good, but this will lead to unpredictable results. For example, we refer to the Person class like this:

public static void main(String[] args) {
        Person p =  new Person(17);
        p.doSomething();        /*其它的業(yè)務(wù)邏輯*/
    }

 Obviously, the p object cannot be created because it is a RunTimeException. , developers can capture it or not. The code seems logically correct and has no flaws, but in fact, this program will throw an exception and cannot be executed. This code gives us two warnings:

Increases the burden on upper-level code writers: catch this RuntimeException exception, then who will tell me about this exception? Only through document constraints, once the constructor of the Person class is refactored and throws other unchecked exceptions, the main method can be tested without modification, but there may be hidden defects here, and it is still very difficult to write. Defects that are difficult to reproduce. This is our usual idea of ??not catching this RuntimeException. Since it has been written as an unchecked exception, the coder of the main method does not need to handle this exception at all. At worst, the Person method will not be executed! This is very dangerous. Once an exception occurs, the entire thread will no longer continue to execute, or the link is not closed, or the data is not written to the database, or a memory exception occurs, which will have an impact on the entire system.

The subsequent code will not be executed: the implementer of the main method originally wanted to use the establishment of the p object as part of its code logic. After executing the doSomething method, other logic needs to be completed, but because unchecked exceptions are not captured, The exception will eventually be thrown to the JVM, which will cause all subsequent code to no longer continue to execute after the execution of the entire thread ends, which will have a fatal impact on the business logic.

(3). The constructor should try not to throw a checked exception as much as possible

Let’s take a look at the following example, the code is as follows:

//父類
class Base {
    // 父類拋出IOException
    public Base() throws IOException {
        throw new IOException();
    }
}
//子類
class Sub extends Base {
    // 子類拋出Exception異常
    public Sub() throws Exception {

    }
}

It’s just such a simple piece of code that shows how to throw a checked exception in the constructor Three disadvantages:

Causes subclass expansion: In our example, the parameterless constructor of the subclass cannot be omitted. The reason is that the parameterless constructor of the parent class throws an IOException, and the parameterless constructor of the subclass defaults to The constructor of the parent class is called, so the parameterless constructor of the subclass must also throw IOException or its parent class.

Violates the Liskov substitution principle: The "Liskov substitution principle" means that subclasses can appear where the parent class can appear, and replacing the parent class with the subclass will not cause any exception. Then we look back and see if the Sub class can replace the Base class. For example, our upper-level code is written like this:

public static void main(String[] args) {
        try {
            Base base = new Base();
        } catch (Exception e) {    
            e.printStackTrace();
        }
    }

Then, we expect to replace new Base() with new Sub(), and the code can compile and run normally. It's a pity that the compilation fails because the Sub constructor throws an Exception. It throws more exceptions than the parent class's constructor and has a wider range of exceptions. A new catch block must be added to solve it. ?

? You may want to ask, why does Java's constructor allow the constructor of a subclass to throw a wider range of exception classes? This is exactly the opposite of the exception mechanism of class methods. The exceptions of class methods require this:

// 父類
class Base {
    // 父類方法拋出Exception
    public void testMethod() throws Exception {

    }
}

// 子類
class Sub extends Base {
    // 父類方法拋出Exception
    @Override
    public void testMethod() throws IOException {

    }
}

  子類的方法可以拋出多個異常,但都必須是覆寫方法的子類型,對我們的例子來說,Sub類的testMethod方法拋出的異常必須是Exception的子類或Exception類,這是Java覆寫的要求。構(gòu)造函數(shù)之所以于此相反,是因為構(gòu)造函數(shù)沒有覆寫的概念,只是構(gòu)造函數(shù)間的引用調(diào)用而已,所以在構(gòu)造函數(shù)中拋出受檢異常會違背里氏替換原則原則,使我們的程序缺乏靈活性。

  3.子類構(gòu)造函數(shù)擴展受限:子類存在的原因就是期望實現(xiàn)擴展父類的邏輯,但父類構(gòu)造函數(shù)拋出異常卻會讓子類構(gòu)造函數(shù)的靈活性大大降低,例如我們期望這樣的構(gòu)造函數(shù)。

// 父類
class Base {
    public Base() throws IOException{
        
    }
}
// 子類
class Sub extends Base {
    public Sub() throws Exception{
        try{
            super();
        }catch(IOException e){
            //異常處理后再拋出
            throw e;
        }finally{
            //收尾處理
        }
    }
}

  很不幸,這段代碼編譯不通過,原因是構(gòu)造函數(shù)Sub沒有把super()放在第一句話中,想把父類的異常重新包裝再拋出是不可行的(當(dāng)然,這里有很多種 “曲線” 的實現(xiàn)手段,比如重新定義一個方法,然后父子類的構(gòu)造函數(shù)都調(diào)用該方法,那么子類構(gòu)造函數(shù)就可以自由處理異常了),這是Java語法機制。

  將以上三種異常類型匯總起來,對于構(gòu)造函數(shù),錯誤只能拋出,這是程序人員無能為力的事情;非受檢異常不要拋出,拋出了 " 對己對人 " 都是有害的;受檢異常盡量不拋出,能用曲線的方式實現(xiàn)就用曲線方式實現(xiàn),總之一句話:在構(gòu)造函數(shù)中盡可能不出現(xiàn)異常。

  注意 :在構(gòu)造函數(shù)中不要拋出異常,盡量曲線實現(xiàn)。

建議115:使用Throwable獲得棧信息

  AOP編程可以很輕松的控制一個方法調(diào)用哪些類,也能夠控制哪些方法允許被調(diào)用,一般來說切面編程(比如AspectJ),只能控制到方法級別,不能實現(xiàn)代碼級別的植入(Weave),比如一個方法被類A的m1方法調(diào)用時返回1,在類B的m2方法調(diào)用時返回0(同參數(shù)情況下),這就要求被調(diào)用者具有識別調(diào)用者的能力。在這種情況下,可以使用Throwable獲得棧信息,然后鑒別調(diào)用者并分別輸出,代碼如下: 

class Foo {
    public static boolean method() {
        // 取得當(dāng)前棧信息
        StackTraceElement[] sts = new Throwable().getStackTrace();
        // 檢查是否是methodA方法調(diào)用
        for (StackTraceElement st : sts) {
            if (st.getMethodName().equals("methodA")) {
                return true;
            }
        }
        return false;
    }
}
//調(diào)用者
class Invoker{
    //該方法打印出true
    public static void methodA(){
        System.out.println(Foo.method());
    }
    //該方法打印出false
    public static void methodB(){
        System.out.println(Foo.method());
    }
}

  注意看Invoker類,兩個方法methodA和methodB都調(diào)用了Foo的method方法,都是無參調(diào)用,返回值卻不同,這是我們的Throwable類發(fā)揮效能了。JVM在創(chuàng)建一本Throwable類及其子類時會把當(dāng)前線程的棧信息記錄下來,以便在輸出異常時準(zhǔn)確定位異常原因,我們來看Throwable源代碼。

public class Throwable implements Serializable {
    private static final StackTraceElement[] UNASSIGNED_STACK = new StackTraceElement[0];
    //出現(xiàn)異常記錄的棧幀
    private StackTraceElement[] stackTrace = UNASSIGNED_STACK;
    //默認(rèn)構(gòu)造函數(shù)
    public Throwable() {
        //記錄棧幀
        fillInStackTrace();
    }
    //本地方法,抓取執(zhí)行時的棧信息
    private native Throwable fillInStackTrace(int dummy);

    public synchronized Throwable fillInStackTrace() {
        if (stackTrace != null || backtrace != null /* Out of protocol state */) {
            fillInStackTrace(0);
            stackTrace = UNASSIGNED_STACK;
        }
        return this;
    }

}

  在出現(xiàn)異常時(或主動聲明一個Throwable對象時),JVM會通過fillInStackTrace方法記錄下棧幀信息,然后生成一個Throwable對象,這樣我們就可以知道類間的調(diào)用順序,方法名稱及當(dāng)前行號等了。

  獲得棧信息可以對調(diào)用者進(jìn)行判斷,然后決定不同的輸出,比如我們的methodA和methodB方法,同樣地輸入?yún)?shù),同樣的調(diào)用方法,但是輸出卻不同,這看起來很想是一個bug:方法methodA調(diào)用method方法正常顯示,而方法methodB調(diào)用卻會返回錯誤數(shù)據(jù),因此我們雖然可以根據(jù)調(diào)用者的不同產(chǎn)生不同的邏輯,但這僅局限在對此方法的廣泛認(rèn)知上,更多的時候我們使用method方法的變形體,代碼如下:  

class Foo {
    public static boolean method() {
        // 取得當(dāng)前棧信息
        StackTraceElement[] sts = new Throwable().getStackTrace();
        // 檢查是否是methodA方法調(diào)用
        for (StackTraceElement st : sts) {
            if (st.getMethodName().equals("methodA")) {
                return true;
            }
        }
        throw new RuntimeException("除了methodA方法外,該方法不允許其它方法調(diào)用");
    }
}

  只是把“return false” 替換成了一個運行期異常,除了methodA方法外,其它方法調(diào)用都會產(chǎn)生異常,該方法常用作離線注冊碼校驗,讓破解者視圖暴力破解時,由于執(zhí)行者不是期望的值,因此會返回一個經(jīng)過包裝和混淆的異常信息,大大增加了破解難度。

回到頂部

建議116:異常只為異常服務(wù)

  異常只為異常服務(wù),這是何解?難道異常還能為其它服務(wù)不成?確實能,異常原本是正常邏輯的一個補充,但是有時候會被當(dāng)做主邏輯使用,看如下代碼:

//判斷一個枚舉是否包含String枚舉項
    public static <T extends Enum<T>> boolean Contain(Class<T> clz,String name){
        boolean result = false;
        try{
            Enum.valueOf(clz, name);
            result = true;
        }catch(RuntimeException e){
            //只要是拋出異常,則認(rèn)為不包含
        }
        return result;
    }

  判斷一個枚舉是否包含指定的枚舉項,這里會根據(jù)valueOf方法是否拋出異常來進(jìn)行判斷,如果拋出異常(一般是IllegalArgumentException異常),則認(rèn)為是不包含,若不拋出異常則可以認(rèn)為包含該枚舉項,看上去這段代碼很正常,但是其中有是哪個錯誤:

異常判斷降低了系統(tǒng)的性能

降低了代碼的可讀性,只有詳細(xì)了解valueOf方法的人才能讀懂這樣的代碼,因為valueOf拋出的是一個非受檢異常

隱藏了運行期可能產(chǎn)生的錯誤,catch到異常,但沒有做任何處理。

  我們這段代碼是用一段異常實現(xiàn)了一個正常的業(yè)務(wù)邏輯,這導(dǎo)致代碼產(chǎn)生了壞味道。要解決從問題也很容易,即不在主邏輯中實使用異常,代碼如下:

// 判斷一個枚舉是否包含String枚舉項
    public static <T extends Enum<T>> boolean Contain(Class<T> clz, String name) {
        // 遍歷枚舉項
        for (T t : clz.getEnumConstants()) {
            // 枚舉項名稱是否相等
            if (t.name().equals(name)) {
                return true;
            }
        }
        return false;
    }

  異常只能用在非正常的情況下,不能成為正常情況下的主邏輯,也就是說,異常是是主邏輯的輔助場景,不能喧賓奪主。

  而且,異常雖然是描述例外事件的,但能避免則避免之,除非是確實無法避免的異常,例如: 

public static void main(String[] args) {
        File file = new File("a.txt");
        try {
            FileInputStream fis = new FileInputStream(file);
            // 其它業(yè)務(wù)處理
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            // 異常處理
        }
    }

  這樣一段代碼經(jīng)常在我們的項目中出現(xiàn),但經(jīng)常寫并不代表不可優(yōu)化,這里的異常類FileNotFoundException完全可以在它誕生前就消除掉:先判斷文件是否存在,然后再生成FileInputStream對象,這也是項目中常見的代碼:

public static void main(String[] args) {
        File file = new File("a.txt");
        // 經(jīng)常出現(xiàn)的異常,可以先做判斷
        if (file.exists() && !file.isDirectory()) {
            try {
                FileInputStream fis = new FileInputStream(file);
                // 其它業(yè)務(wù)處理
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                // 異常處理
            }
        }
    }

  雖然增加了if判斷語句,增加了代碼量,但是卻減少了FileNotFoundException異常出現(xiàn)的幾率,提高了程序的性能和穩(wěn)定性。

回到頂部

建議117:多使用異常,把性能問題放一邊

  我們知道異常是主邏輯的例外邏輯,舉個簡單的例子來說,比如我在馬路上走(這是主邏輯),突然開過一輛車,我要避讓(這是受檢異常,必須處理),繼續(xù)走著,突然一架飛機從我頭頂飛過(非受檢異常),我們可以選在繼續(xù)行走(不捕捉),也可以選擇指責(zé)其噪音污染(捕捉,主邏輯的補充處理),再繼續(xù)走著,突然一顆流星砸下來,這沒有選擇,屬于錯誤,不能做任何處理。這樣具備完整例外場景的邏輯就具備了OO的味道,任何一個事務(wù)的處理都可能產(chǎn)生非預(yù)期的效果,問題是需要以何種手段來處理,如果不使用異常就需要依靠返回值的不同來進(jìn)行處理了,這嚴(yán)重失去了面向?qū)ο蟮娘L(fēng)格。

  我們在編寫用例文檔(User case Specification)時,其中有一項叫做 " 例外事件 ",是用來描述主場景外的例外場景的,例如用戶登錄的用例,就會在" 例外事件 "中說明" 連續(xù)3此登錄失敗即鎖定用戶賬號 ",這就是登錄事件的一個異常處理,具體到我們的程序中就是:  

public void login(){
        try{
            //正常登陸
        }catch(InvalidLoginException lie){
            //    用戶名無效
        }catch(InvalidPasswordException pe){
            //密碼錯誤的異常
        }catch(TooMuchLoginException){
            //多次登陸失敗的異常
        }
    }

  如此設(shè)計則可以讓我們的login方法更符合實際的處理邏輯,同時使主邏輯(正常登錄,try代碼塊)更加清晰。當(dāng)然了,使用異常還有很多優(yōu)點,可以讓正常代碼和異常代碼分離、能快速查找問題(棧信息快照)等,但是異常有一個缺點:性能比較慢。

  Java的異常機制確實比較慢,這個"比較慢"是相對于諸如String、Integer等對象來說的,單單從對象的創(chuàng)建上來說,new一個IOException會比String慢5倍,這從異常的處理機制上也可以解釋:因為它要執(zhí)行fillInStackTrace方法,要記錄當(dāng)前棧的快照,而String類則是直接申請一個內(nèi)存創(chuàng)建對象,異常類慢一籌也就在所難免了。

  而且,異常類是不能緩存的,期望先建立大量的異常對象以提高異常性能也是不現(xiàn)實的。

  難道異常的性能問題就沒有任何可以提高的辦法了?確實沒有,但是我們不能因為性能問題而放棄使用異常,而且經(jīng)過測試,在JDK1.6下,一個異常對象的創(chuàng)建時間只需1.4毫秒左右(注意是毫秒,通常一個交易是在100毫秒左右),難道我們的系統(tǒng)連如此微小的性能消耗都不予許嗎?


Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undress AI Tool

Undress AI Tool

Undress images for free

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Hot Topics

PHP Tutorial
1502
276