Heim >Java >javaLernprogramm >Detaillierte Einführung in Missverständnisse und Erfahrungszusammenfassungen zur Java-Ausnahmebehandlung

Detaillierte Einführung in Missverständnisse und Erfahrungszusammenfassungen zur Java-Ausnahmebehandlung

黄舟
黄舟Original
2017-03-20 10:16:511208Durchsuche

Dieser Artikel konzentriert sich auf einige Missverständnisse bei der Auswahl und Verwendung von Java-Ausnahmen. Ich hoffe, dass die Leser einige der Punkte und Prinzipien der Ausnahmebehandlung beherrschen und auf Zusammenfassung und Einführung achten können. Nur durch die Behandlung von Ausnahmen können wir die Grundkenntnisse der Entwickler verbessern, die Robustheit des Systems verbessern, die Benutzererfahrung verbessern und den Wert des Produkts steigern.

Missverständnis 1. Auswahl der Ausnahmen

Abbildung 1. Anomalieklassifizierung

Abbildung 1 beschreibt die Anomaliestruktur Tatsächlich wissen wir alle, dass Anomalien in erkannte und nicht erkannte Anomalien unterteilt werden, aber in der Praxis sind die Anwendungen dieser beiden Anomalien verwirrend. Da ungeprüfte Ausnahmen so einfach zu verwenden sind, sind viele Entwickler der Meinung, dass geprüfte Ausnahmen von geringem Nutzen sind. Tatsächlich können abnormale Anwendungsszenarien wie folgt zusammengefasst werden:

1 Der aufrufende Code kann nicht weiter ausgeführt werden und muss sofort beendet werden. Für diese Situation gibt es viele Möglichkeiten, z. B. kann der Server nicht verbunden werden, die Parameter sind falsch usw. In diesen Fällen sind ungeprüfte Ausnahmen geeignet. Sie erfordern kein explizites Abfangen und Verarbeiten des aufrufenden Codes und der Code ist prägnant und klar.

2. Der aufrufende Code muss weiter verarbeitet und wiederhergestellt werden. Wenn SQLException als unentdeckte Ausnahme definiert ist, gehen Entwickler davon aus, dass SQLException beim Bearbeiten von Daten nicht explizit vom aufrufenden Code erfasst und verarbeitet werden muss, was dazu führen kann, dass die Verbindung nicht geschlossen wird, die Transaktion nicht zurückgesetzt wird und fehlerhafte Daten vorliegen Erscheinen in der Datenbank usw. Da SQLException als erkannte Ausnahme definiert ist, müssen Entwickler sie explizit abfangen und Ressourcen bereinigen, nachdem der Code eine Ausnahme generiert hat. Natürlich können Sie nach dem Bereinigen der Ressourcen weiterhin unerkannte Ausnahmen auslösen, um die Ausführung des Programms zu verhindern. Basierend auf Beobachtung und Verständnis kann die Erkennung von Anomalien meist auf Werkzeugklassen angewendet werden.

Missverständnis 2: Ausnahmen direkt auf der Seite oder im Client anzeigen.

Beispiele für das Drucken von Ausnahmen direkt auf dem Client sind üblich. Nehmen Sie als Beispiel JSP. Sobald eine Ausnahme auftritt, während der Code ausgeführt wird, druckt der Container standardmäßig die Ausnahmestapelinformationen auf der Seite. Aus Sicht des Kunden hat jede Ausnahme keine praktische Bedeutung. Die überwiegende Mehrheit der Kunden kann die Ausnahmeinformationen überhaupt nicht verstehen.

Listing 1

 package com.ibm.dw.sample.exception;
/**
 * 自定义 RuntimeException
 * 添加错误代码属性
 */
public class RuntimeException extends java.lang.RuntimeException { 
     //默认错误代码 
    public static final Integer GENERIC = 1000000; 
    //错误代码
    private Integer errorCode; 
     public RuntimeException(Integer errorCode, Throwable cause) {
            this(errorCode, null, cause);
     }
     public RuntimeException(String message, Throwable cause) {
            //利用通用错误代码
            this(GENERIC, message, cause);
     }
     public RuntimeException(Integer errorCode, String message, Throwable cause) {
            super(message, cause);
            this.errorCode = errorCode;
     }
     public Integer getErrorCode() {
            return errorCode;
     } 
}

Wie im Beispielcode gezeigt, wird der Fehlercode in die Ausnahme eingeführt, wir müssen ihn nur präsentieren Geben Sie dem Benutzer den Fehlercode der Ausnahme weiter oder wandeln Sie den Fehlercode in eine verständlichere Eingabeaufforderung um. Tatsächlich enthält der Fehlercode hier auch eine weitere Funktion. Entwickler können anhand des Fehlercodes auch genau erkennen, welche Art von Ausnahme aufgetreten ist.

Missverständnis 3. Verschmutzung der Codehierarchie

Wir unterteilen den Code oft in verschiedene Hierarchien wie Service, Geschäftslogik, DAO usw. Die DAO-Schicht enthält auslösende Ausnahmen Methode, wie in Listing 2 gezeigt:

Listing 2

public Customer retrieveCustomerById(Long id) throw SQLException {
 //根据 ID 查询数据库
}

Der obige Code scheint auf den ersten Blick in Ordnung zu sein, aber aus Sicht der Designkopplung ist er vorsichtig Denken Sie darüber nach, die SQLException hier hat den aufrufenden Code der oberen Ebene verschmutzt, und die aufrufende Ebene muss explizit Try-Catch verwenden, um ihn abzufangen, oder ihn weiter auf die obere Ebene werfen. Gemäß dem Prinzip der Designisolation können wir es entsprechend ändern wie folgt:

Listing 3

public Customer retrieveCustomerById(Long id) {
     try{
            //根据 ID 查询数据库
     }catch(SQLException e){
            //利用非检测异常封装检测异常,降低层次耦合
            throw new RuntimeException(SQLErrorCode, e);
     }finally{
            //关闭连接,清理资源
     }
}

Missverständnis 4. Ausnahmen ignorieren

Das Die folgende Ausnahmebehandlung ist nur die Ausnahme, die an die Konsole ausgegeben wird und keinen Sinn ergibt. Darüber hinaus trat die Ausnahme hier auf und unterbrach das Programm nicht. Anschließend wurde der aufrufende Code weiter ausgeführt, was zu weiteren Ausnahmen führte.

Listing 4

 public void retrieveObjectById(Long id){
   try{
       //..some code that throws SQLException
    }catch(SQLException ex){
     /**
       *了解的人都知道,这里的异常打印毫无意义,仅仅是将错误堆栈输出到控制台。
       * 而在 Production 环境中,需要将错误堆栈输出到日志。
       * 而且这里 catch 处理之后程序继续执行,会导致进一步的问题*/

          ex.printStacktrace();
     }
}

kann umgestaltet werden in:

Listing 5

public void retrieveObjectById(Long id){
 try{
    //..some code that throws SQLException
 }
 catch(SQLException ex){
    throw new RuntimeException(“Exception in retieveObjectById”, ex);
 }
 finally{
    //clean up resultset, statement, connection etc
 }
}

Dieses Missverständnis ist relativ grundlegend und im Allgemeinen wird dieser einfache Fehler nicht gemacht.

Missverständnis 5. Ausnahmen im Anweisungsblock der -Schleife enthalten

Wie im folgenden Code gezeigt, sind Ausnahmen im Anweisungsblock der for-Schleife enthalten.

Listing 6

for(int i=0; i<100; i++){
    try{
    }catch(XXXException e){
         //….
    }
}

Wir alle wissen, dass die Ausnahmebehandlung Systemressourcen verbraucht. Auf den ersten Blick dachten alle, dass ein solcher Fehler nicht passieren würde. Aus einer anderen Perspektive wird eine Schleife in Klasse A ausgeführt und eine Methode der Klasse B in der Schleife aufgerufen. Die in Klasse B aufgerufene Methode enthält jedoch einen Anweisungsblock wie try-catch. Nach dem Entfernen der Klassenhierarchie ist der Code genau derselbe wie oben.

Missverständnis 6. Verwenden Sie Exception, um alle potenziellen Ausnahmen abzufangen.

Während der Ausführung einer Methode werden mehrere verschiedene Arten von Ausnahmen ausgelöst. Der Einfachheit halber verwenden Sie zum Abfangen die Basisklasse Exception alle möglichen Ausnahmen. , wie im folgenden Beispiel gezeigt:

Listing 7

public void retrieveObjectById(Long id){
    try{
        //…抛出 IOException 的代码调用
        //…抛出 SQLException 的代码调用
    }catch(Exception e){
        //这里利用基类 Exception 捕捉的所有潜在的异常,如果多个层次这样捕捉,会丢失原始异常的有效信息
        throw new RuntimeException(“Exception in retieveObjectById”, e);
    }
}

kann in

Listing 8 umstrukturiert werden

public void retrieveObjectById(Long id){
    try{
        //..some code that throws RuntimeException, IOException, SQLException
    }catch(IOException e){
        //仅仅捕捉 IOException
        throw new RuntimeException(/*指定这里 IOException 对应的错误代码*/code,“Exception in retieveObjectById”, e);
    }catch(SQLException e){
        //仅仅捕捉 SQLException
        throw new RuntimeException(/*指定这里 SQLException 对应的错误代码*/code,“Exception in retieveObjectById”, e);
    }
}

误区七、多层次封装抛出非检测异常

如果我们一直坚持不同类型的异常一定用不同的捕捉语句,那大部分例子可以绕过这一节了。但是如果仅仅一段代码调用会抛出一种以上的异常时,很多时候没有必要每个不同类型的 Exception 写一段 catch 语句,对于开发来说,任何一种异常都足够说明了程序的具体问题。

清单 9

try{
    //可能抛出 RuntimeException、IOExeption 或者其它;
    //注意这里和误区六的区别,这里是一段代码抛出多种异常。以上是多段代码,各自抛出不同的异常
}catch(Exception e){
    //一如既往的将 Exception 转换成 RuntimeException,但是这里的 e 其实是 RuntimeException 的实例,已经在前段代码中封装过
    throw new RuntimeException(/**/code, /**/, e);
}

如果我们如上例所示,将所有的 Exception 再转换成 RuntimeException,那么当 Exception 的类型已经是 RuntimeException 时,我们又做了一次封装。将 RuntimeException 又重新封装了一次,进而丢失了原有的 RuntimeException 携带的有效信息。

解决办法是我们可以在 RuntimeException 类中添加相关的检查,确认参数 Throwable 不是 RuntimeException 的实例。如果是,将拷贝相应的属性到新建的实例上。或者用不同的 catch 语句块捕捉 RuntimeException 和其它的 Exception。个人偏好方式一,好处不言而喻。

误区八、多层次打印异常

我们先看一下下面的例子,定义了 2 个类 A 和 B。其中 A 类中调用了 B 类的代码,并且 A 类和 B 类中都捕捉打印了异常。

清单 10

 public class A {
 private static Logger logger = LoggerFactory.getLogger(A.class);
 public void process(){
     try{
     //实例化 B 类,可以换成其它注入等方式
     B b = new B();
     b.process();
     //other code might cause exception
    } catch(XXXException e){
       //如果 B 类 process 方法抛出异常,异常会在 B 类中被打印,在这里也会被打印,从而会打印 2 次
       logger.error(e);
       throw new RuntimeException(/* 错误代码 */ errorCode, /*异常信息*/msg, e);
       }
    }
}
public class B{
 private static Logger logger = LoggerFactory.getLogger(B.class);
    public void process(){
        try{
            //可能抛出异常的代码
        }
        catch(XXXException e){
            logger.error(e);
            throw new RuntimeException(/* 错误代码 */ errorCode, /*异常信息*/msg, e);
        }
 }
}

同一段异常会被打印 2 次。如果层次再复杂一点,不去考虑打印日志消耗的系统性能,仅仅在异常日志中去定位异常具体的问题已经够头疼的了。

其实打印日志只需要在代码的最外层捕捉打印就可以了,异常打印也可以写成 AOP,织入到框架的最外层。

误区九、异常包含的信息不能充分定位问题

异常不仅要能够让开发人员知道哪里出了问题,更多时候开发人员还需要知道是什么原因导致的问题,我们知道 java .lang.Exception 有字符串类型参数的构造方法,这个字符串可以自定义成通俗易懂的提示信息。

简单的自定义信息开发人员只能知道哪里出现了异常,但是很多的情况下,开发人员更需要知道是什么参数导致了这样的异常。这个时候我们就需要将方法调用的参数信息追加到自定义信息中。下例只列举了一个参数的情况,多个参数的情况下,可以单独写一个工具类组织这样的字符串。

清单 11

public void retieveObjectById(Long id){
    try{
        //..some code that throws SQLException
   }catch(SQLException ex){
        //将参数信息添加到异常信息中
        throw new RuntimeException(“Exception in retieveObjectById with Object Id :”+ id, ex);
   }
}

误区十、不能预知潜在的异常

在写代码的过程中,由于对调用代码缺乏深层次的了解,不能准确判断是否调用的代码会产生异常,因而忽略处理。在产生了 Production Bug 之后才想起来应该在某段代码处添加异常补捉,甚至不能准确指出出现异常的原因。这就需要开发人员不仅知道自己在做什么,而且要去尽可能的知道别人做了什么,可能会导致什么结果,从全局去考虑整个应用程序的处理过程。这些思想会影响我们对代码的编写和处理。

误区十一、混用多种第三方日志库

现如今 Java 第三方日志库的种类越来越多,一个大项目中会引入各种各样的框架,而这些框架又会依赖不同的日志库的实现。最麻烦的问题倒不是引入所有需要的这些日志库,问题在于引入的这些日志库之间本身不兼容。如果在项目初期可能还好解决,可以把所有代码中的日志库根据需要重新引入一遍,或者换一套框架。但这样的成本不是每个项目都承受的起的,而且越是随着项目的进行,这种风险就越大。

怎么样才能有效的避免类似的问题发生呢,现在的大多数框架已经考虑到了类似的问题,可以通过配置 Properties 或 xml 文件、参数或者运行时扫描 Lib 库中的日志实现类,真正在应用程序运行时才确定具体应用哪个特定的日志库。

其实根据不需要多层次打印日志那条原则,我们就可以简化很多原本调用日志打印代码的类。很多情况下,我们可以利用拦截器或者过滤器实现日志的打印,降低代码维护、迁移的成本。

结束语

Das obige ist der detaillierte Inhalt vonDetaillierte Einführung in Missverständnisse und Erfahrungszusammenfassungen zur Java-Ausnahmebehandlung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn