Maison > Java > javaDidacticiel > Malentendus et résumés d'expériences sur la gestion des exceptions Java

Malentendus et résumés d'expériences sur la gestion des exceptions Java

黄舟
Libérer: 2017-02-06 11:28:01
original
1053 Les gens l'ont consulté

Cet article se concentre sur certains malentendus dans la sélection et l'utilisation des exceptions Java. J'espère que les lecteurs pourront maîtriser certains points et principes de la gestion des exceptions et prêter attention au résumé et à l'induction. Ce n'est qu'en gérant les exceptions que nous pouvons améliorer les connaissances de base des développeurs, améliorer la robustesse du système, améliorer l'expérience utilisateur et augmenter la valeur du produit.

Malentendu 1 : Sélection des exceptions

Figure 1. Classification des anomalies

Malentendus et résumés dexpériences sur la gestion des exceptions Java

La figure 1 décrit la structure des exceptions, en fait nous tous sachez-le Les anomalies sont divisées en anomalies détectées et anomalies non détectées, mais en pratique les applications de ces deux anomalies sont confondues. Parce que les exceptions non vérifiées sont si faciles à utiliser, de nombreux développeurs pensent que les exceptions vérifiées sont de peu d'utilité. En fait, les scénarios d'application anormaux peuvent être résumés comme suit :

Le code appelant ne peut pas continuer à s'exécuter et doit être terminé immédiatement. Il existe de nombreuses possibilités pour cette situation, comme le serveur ne peut pas être connecté, les paramètres sont incorrects, etc. Les exceptions non contrôlées conviennent dans ces cas et ne nécessitent pas de capture ni de traitement explicites du code appelant, et le code est concis et clair.

Le code appelant nécessite un traitement et une récupération plus approfondis. Si SQLException est défini comme une exception non détectée, les développeurs tiendront pour acquis que SQLException n'a pas besoin d'être explicitement capturé et traité par le code appelant lors de l'exploitation des données, ce qui entraînera de graves problèmes de fermeture de connexion, de non-annulation de transaction et de données sales. apparaissant dans la base de données, etc. Étant donné que SQLException est définie comme une exception détectée, les développeurs sont amenés à l'intercepter explicitement et à nettoyer les ressources une fois que le code a généré une exception. Bien entendu, après avoir nettoyé les ressources, vous pouvez continuer à générer des exceptions non détectées pour empêcher l'exécution du programme. Basée sur l’observation et la compréhension, la détection des anomalies peut principalement s’appliquer aux classes d’outils.

Incompréhension 2 : Afficher les exceptions directement sur la page ou le client

Les exemples d'impression d'exceptions directement sur le client sont courants. En prenant JSP comme exemple, une fois qu'une exception se produit lors de l'exécution du code, le conteneur sera par défaut imprimé les informations sur la pile d'exceptions directement sur la page. En fait, du point de vue du client, toute exception n'a aucune signification pratique. La grande majorité des clients ne peuvent pas du tout comprendre les informations sur les exceptions. Le développement de logiciels devrait également essayer d'éviter de présenter les exceptions directement aux utilisateurs.


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;
    } 
}
Copier après la connexion

Comme le montre l'exemple de code, le code d'erreur est introduit dans l'exception. Une fois qu'une exception se produit, nous n'en avons besoin que. pour ajouter le code d'erreur de l'exception Présentez-le à l'utilisateur ou convertissez le code d'erreur en une invite plus compréhensible. En fait, le code d'erreur contient également une autre fonction. Les développeurs peuvent également savoir exactement quel type d'exception s'est produit en fonction du code d'erreur.

Mythe 3 : Pollution de la hiérarchie du code

Nous divisons souvent le code en différentes hiérarchies telles que Service, Business Logic, DAO, etc. La couche DAO contiendra des méthodes qui lèvent des exceptions, telles que comme le montre le listing 2 :

Listing 2

public Customer retrieveCustomerById(Long id) throw SQLException {
//根据 ID 查询数据库
}
Copier après la connexion

Le code ci-dessus semble correct à première vue, mais si vous y réfléchissez attentivement du point de vue du couplage de conception, l'exception SQL ici a a pollué le code appelant supérieur. La couche appelante doit utiliser explicitement try-catch pour le capturer ou le lancer à un niveau supérieur. Selon le principe d'isolation de conception, nous pouvons le modifier de manière appropriée pour :


清单 3

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

误区四:忽略异常

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

清单 4

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

可以重构成:

清单 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
}
}
Copier après la connexion

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

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

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

清单 6

for(int i=0; i<100; i++){
   try{
   }catch(XXXException e){
        //….
   }
}
Copier après la connexion

我们都知道异常处理占用系统资源。一看,大家都认为不会犯这样的错误。换个角度,类 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);
   }
}
Copier après la connexion

可以重构成

清单 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);
   }
}
Copier après la connexion

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

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

清单 9

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

如果我们如上例所示,将所有的 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);
       }
}
}
Copier après la connexion

同一段异常会被打印 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);
  }
}
Copier après la connexion

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

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


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

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


Comment pouvons-nous éviter efficacement des problèmes similaires ? La plupart des frameworks actuels ont déjà pris en compte des problèmes similaires. Vous pouvez configurer les propriétés ou les fichiers XML, les paramètres ou analyser la classe d'implémentation du journal dans la bibliothèque Lib au moment de l'exécution. utilisé est déterminé uniquement lorsque l’application est en cours d’exécution.

En fait, sur la base du principe selon lequel il n'est pas nécessaire d'imprimer les journaux à plusieurs niveaux, nous pouvons simplifier de nombreuses classes qui appellent à l'origine le code d'impression des journaux. Dans de nombreux cas, nous pouvons utiliser des intercepteurs ou des filtres pour imprimer des journaux et réduire les coûts de maintenance et de migration du code.


Conclusion

Ce qui précède est une expérience purement personnelle et un résumé. Les choses sont dialectiques. Il n'y a pas de principes absolus. Ce qui vous convient est le plus efficace. principe. J'espère que l'explication et l'analyse ci-dessus pourront vous être utiles.

Ce qui précède est un résumé des malentendus et de l'expérience de la gestion des exceptions Java. Pour plus de contenu connexe, veuillez faire attention au site Web PHP chinois (m.sbmmt.com) !


Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal