Heim  >  Artikel  >  Java  >  Missverständnisse und Erfahrungsberichte zur Java-Ausnahmebehandlung

Missverständnisse und Erfahrungsberichte zur Java-Ausnahmebehandlung

黄舟
黄舟Original
2017-02-06 11:28:01969Durchsuche

Dieser Artikel konzentriert sich auf einige Missverständnisse bei der Auswahl und Verwendung von Java-Ausnahmen. Ich hoffe, dass die Leser einige 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 von Ausnahmen

Abbildung 1. Anomalieklassifizierung

Missverständnisse und Erfahrungsberichte zur Java-Ausnahmebehandlung

Abbildung 1 beschreibt die Struktur von Ausnahmen, in der Tat wir alle Wissen Sie, Anomalien werden in erkannte und nicht erkannte Anomalien unterteilt, aber in der Praxis sind die Anwendungen dieser beiden Anomalien verwechselt. 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:

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.

Der aufrufende Code erfordert eine weitere Verarbeitung und Wiederherstellung. 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 auf dem Client anzeigen

Beispiele für das Drucken von Ausnahmen direkt auf dem Client sind häufig: Wenn beim Ausführen des Codes eine Ausnahme auftritt, wird JSP als Beispiel verwendet. Der Container wird standardmäßig direkt auf die Seite gedruckt. 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 nur in die Ausnahme eingeführt, sobald eine Ausnahme auftritt um den Fehlercode der Ausnahme hinzuzufügen. Präsentieren Sie ihn dem Benutzer 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.

Mythos 3: Verschmutzung der Codehierarchie

Wir unterteilen den Code häufig in verschiedene Hierarchien wie Service, Geschäftslogik, DAO usw. Die DAO-Schicht enthält Methoden, die Ausnahmen auslösen, z wie Listing 2 gezeigt wird:

Listing 2

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

Der obige Code scheint auf den ersten Blick in Ordnung zu sein, aber wenn man aus der Perspektive der Designkopplung sorgfältig darüber nachdenkt, ist die SQLException hier vorhanden Der obere aufrufende Code wurde verschmutzt, und die aufrufende Ebene muss explizit Try-Catch verwenden, um ihn zu erfassen oder auf eine höhere Ebene zu werfen. Gemäß dem Prinzip der Designisolation können wir es entsprechend ändern zu:


清单 3

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

误区四:忽略异常

如下异常处理只是将异常输出到控制台,没有任何意义。而且这里出现了异常并没有中断程序,进而调用代码继续执行,导致更多的异常。

清单 4

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

可以重构成:

清单 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
}
}

这个误区比较基本,一般情况下都不会犯此低级错误。

误区五:将异常包含在循环语句块中

如下代码所示,异常包含在 for 循环语句块中。

清单 6

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

我们都知道异常处理占用系统资源。一看,大家都认为不会犯这样的错误。换个角度,类 A 中执行了一段循环,循环中调用了 B 类的方法,B 类中被调用的方法却又包含 try-catch 这样的语句块。褪去类的层次结构,代码和上面如出一辙。

误区六:利用 Exception 捕捉所有潜在的异常

一段方法执行过程中抛出了几个不同类型的异常,为了代码简洁,利用基类 Exception 捕捉所有潜在的异常,如下例所示:

清单 7

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

可以重构成

清单 8

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


Wie können wir ähnliche Probleme effektiv vermeiden? Die meisten aktuellen Frameworks haben ähnliche Probleme bereits berücksichtigt. Sie können Eigenschaften oder XML-Dateien und Parameter konfigurieren oder die Protokollimplementierungsklasse zur Laufzeit scannen Der verwendete Wert wird nur ermittelt, wenn die Anwendung ausgeführt wird.

Tatsächlich können wir viele Klassen vereinfachen, die ursprünglich den Protokolldruckcode aufrufen, basierend auf dem Prinzip, dass es nicht erforderlich ist, Protokolle auf mehreren Ebenen zu drucken. In vielen Fällen können wir Interceptoren oder Filter verwenden, um Protokolle zu drucken und die Kosten für die Codewartung und -migration zu senken.


Fazit: Das Obige ist eine rein persönliche Erfahrung und Zusammenfassung. Es gibt keine absoluten Prinzipien Prinzip. Ich hoffe, dass die obige Erklärung und Analyse für Sie hilfreich sein kann.

Das Obige ist die Missverständnisse und Erfahrungszusammenfassung der Java-Ausnahmebehandlung. Weitere verwandte Inhalte finden Sie auf der chinesischen PHP-Website (www.php.cn)!

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