Maison  >  Article  >  Java  >  Introduction détaillée aux malentendus et résumés d'expériences sur la gestion des exceptions Java

Introduction détaillée aux malentendus et résumés d'expériences sur la gestion des exceptions Java

黄舟
黄舟original
2017-03-20 10:16:511166parcourir

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 des 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

La figure 1 décrit la structure de l'anomalie , en fait, nous savons tous que 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 :

1. 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 sont applicables dans ces situations, car elles ne nécessitent pas de capture ni de traitement explicites du code appelant, et le code est concis et clair.

2. Le code appelant doit être traité et récupéré davantage. 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. Prenez JSP comme exemple. Une fois qu'une exception se produit lors de l'exécution du code, par défaut, le conteneur imprimera directement les informations de la pile d'exceptions. 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;
     } 
}

Comme indiqué dans l'exemple de code, le code d'erreur est introduit dans l'exception. Une fois qu'une exception se produit, il suffit de présenter le code d'erreur. code d'erreur de l'exception à 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.

Malentendu 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 lancer des exceptions méthode, comme indiqué dans le listing 2 :

Listing 2

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

Le code ci-dessus semble correct à première vue, mais avec précaution du point de vue du couplage de conception Pensez-y, l'exception SQLException ici a pollué le code appelant de la couche supérieure, et la couche appelante doit utiliser explicitement try-catch pour l'attraper, ou la lancer plus loin vers le niveau supérieur. Selon le principe d'isolation de la conception, nous pouvons le modifier de manière appropriée pour :

Listing 3

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

Malentendu 4. Ignorer les exceptions

Le la gestion des exceptions suivante est simplement l'exception est affichée sur la console et n'a aucun sens. De plus, l'exception s'est produite ici et n'a pas interrompu le programme, puis le code appelant a continué à s'exécuter, entraînant davantage d'exceptions.

Listing 4

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

          ex.printStacktrace();
     }
}

peut être refactorisé en :

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

Ce malentendu est relativement basique, et généralement cette simple erreur ne sera pas commise.

Malentendu 5. Contenir des exceptions dans le bloc d'instructions boucle

Comme le montre le code suivant, les exceptions sont incluses dans le bloc d'instructions de boucle for.

Listing 6

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

Nous savons tous que la gestion des exceptions consomme des ressources système. À première vue, tout le monde pensait qu’une telle erreur ne serait pas commise. D'un autre point de vue, une boucle est exécutée dans la classe A et une méthode de classe B est appelée dans la boucle. Cependant, la méthode appelée dans la classe B contient un bloc d'instructions tel que try-catch. Après avoir supprimé la hiérarchie des classes, le code est exactement le même que ci-dessus.

Mythe 6. Utilisez Exception pour intercepter toutes les exceptions potentielles

Plusieurs types d'exceptions différents sont générés lors de l'exécution d'une méthode. Par souci de simplicité du code, utilisez la classe de base Exception pour intercepter. toutes les exceptions potentielles, comme le montre l'exemple suivant :

Listing 7

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

peut être restructuré en

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

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

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

结束语

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
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