Lorsqu’on développe des applications (que ce soit des applications Big Data ou non) en Java ou dans n’importe quel autre langage informatique, on va forcément faire face aux erreurs. Même après avoir corrigé son application grâce aux tests unitaires, il y’a des situations où les bugs sont inévitables. Par exemple, vous ne pouvez pas toujours compter sur le fait que l’utilisateur n’essaiera pas d’effectuer une division par 0.

Pour combler ces bugs inévitables, les langages de programmation de haut niveau comme Java fournissent des mécanismes permettant de  gérer  les  erreurs  qui  peuvent  intervenir  lors  de  l’exécution  du programme.  Le  mécanisme de gestion d’erreurs le plus répandu est celui des exceptions (encore appelé le try catch). Dans cette article, nous allons vous montrer comment gérer les exceptions en Java avec le gestionnaire d’erreur try catch.

Comprendre la notion “d’erreur” en Java

Avant d’entrer dans la gestion d’erreur proprement dite, il faut commencer par comprendre la notion d’erreur et leurs potentielles sources. Les erreurs lors de l’exécution d’une application peuvent être divisées en 2 : les erreurs de bas niveau qui impliquent la violation de contraintes, telles que :

  •     le déréférencement d’un pointeur nul
  •     l’accès hors limite à un tableau
  •     la division par zéro
  •     la tentative d’ouverture d’un fichier inexistant
  •     la mauvaise conversion (par exemple, conversion d’un booléen en un nombre entier).

et des erreurs logiques de plus haut niveau, telles que la violation de la précondition d’une fonction :

  •     appel de la méthode “pop” de la classe Stack avec une pile vide
  •     appel d’une fonction “factorielle” avec un nombre négatif

Les erreurs logiques peuvent entraîner des erreurs de bas niveau si elles ne sont pas détectées. Souvent, il est préférable de les détecter pour fournir un meilleur retour d’information à l’utilisateur de votre application.

Les erreurs, quelque soit leur nature, ont généralement 2 sources. Elles peuvent être dues :

  • à l’utilisateur : par exemple, par exemple la fourniture d’un mauvais nom de fichier, ou un fichier d’entrée mal formaté. Un bon programme doit être écrit de manière à anticiper ces situations et les traiter. Par exemple, dans le cas d’un mauvais nom de fichier, un programme interactif peut afficher un message d’erreur et demander un nouveau nom.
  •  au programmeur :  ces erreurs doivent être détectées le plus tôt possible afin de fournir un bon retour d’information. Pour certains programmes, il peut être souhaitable d’effectuer une récupération après avoir détecté ce type d’erreur, par exemple en écrivant les données en cours.

Notez que la récupération n’est souvent pas possible à l’endroit de l’erreur (parce que l’erreur peut se produire à l’intérieur d’une fonction utilitaire qui ne sait rien du programme global ou de ce que la récupération des erreurs devrait impliquer). Par conséquent, il est souhaitable de “transmettre l’erreur” à un niveau qui peut la traiter.

Il y a plusieurs façons possibles de gérer les erreurs :

  • On peut soit écrire un message d’erreur et quitter. Cela ne permet aucune récupération.
  • Soit alors, on peut renvoyer une valeur spéciale pour indiquer qu’une erreur s’est produite. C’est l’approche habituelle pour les fonctions  du langage de programmation C (qui renvoient souvent 0 ou -1 pour signaler une erreur). Cependant, cette méthode ne fonctionne pas si la fonction renvoie également une valeur et qu’il n’y a pas de valeur spéciale pouvant être utilisée pour signaler une erreur. De plus, elle exige que le code appelant vérifie l’existence d’une erreur. Cela peut réduire l’efficacité du code et est souvent omis par les programmeurs par paresse ou par négligence. Cela peut également rendre le code plus lourd. Par exemple, si la fonction g peut renvoyer un code d’erreur, il faudrait écrire quelque chose comme ceci :
retour = g(x) ;
if (retour == ERROR_CODE) { ... }
else f(retour ) ;
  au lieu de simplement :
 f(g(x)) ;

Pour résoudre ces problèmes, les solutions possibles sont les suivantes :

  • utiliser un paramètre de référence ou une variable globale pour contenir un code d’erreur. Cela résout le premier problème de l’approche précédente, mais pas le deuxième ou le troisième.
  • utiliser des exceptions. Ceci est la méthode de choix pour les langages de programmation modernes comme Java.

Les  exceptions en Java

Lorsqu’une erreur est détectée, une exception est levée. En d’autres termes, le code qui a provoqué l’erreur cesse immédiatement de s’exécuter et le contrôle est transféré à la clause de capture catch de cette exception.

Le bloc try peut se trouver dans la fonction qui a causé l’erreur ou dans la  fonction qui a appelé la fonction erronée (c’est-à-dire que si la fonction courante  n’est pas préparée à traiter l’exception, elle est “transmise” à la pile d’appel). Si aucune fonction n’est préparée à capturer l’exception, un message d’erreur est affiché et le programme s’arrête.

Il existe des exceptions prédéfinies par des bibliothèques standard de Java. Voici quelques exemples d’exceptions:

  • ArithmeticException : par exemple,  division d’un nombre par 0
  • ClassCastException : par exemple, tentative de transformer un objet String en Integer
  • IndexOutOfBoundsException : par exemple, appeler un index de tableau qui n’existe. tab[10] alors que la variable tab n’a que 9 éléments par exemple.
  • NullPointerException : appel d’une variable non-existante ou d’un fichier non-existant par exemple.
  • FileNotFoundException : exemple, tentative d’ouverture d’un fichier inexistant en lecture

Capturer les exceptions en java

Lorsqu’une portion de code est susceptible de lancer/lever une exception, il est possible de capturer cette potentielle exception et indiquer le traitement qui doit en être fait.  La capture d’une exception en Java se fait à l’aide du bloc de mots clés  try … catch … finally.

try {
     // les instructions susceptibles de provoquer des exceptions
    // incluant éventuellement des appels de fonction
} catch ( exception-1 id-1 ) {
   //les instructions pour gérer cette exception 
} catch ( exception-2 id-2 ) {
   // les instructions pour gérer cette exception
   .
   .
   .
} finally {
   // instructions à exécuter à chaque fois que ce bloc try s'exécute.
}

Chaque clause catch spécifie le type d’une exception et lui donne un nom (de la même manière qu’un en-tête de fonction spécifie le type et le nom d’un paramètre). Les exceptions Java sont des objets, de sorte que les instructions d’une clause catch peuvent faire référence à l’objet d’exception lancé en utilisant le nom spécifié. La clause finally est facultative, ele indique la suite du code à exécuter une fois que l’erreur a été traitée.

En général, il peut y avoir une ou plusieurs clauses catch dans le même gestionnaire d’erreur. La clause finally clôture le gestionnaire. Par exemple, supposons un programme qui tente d’affecter à la case de position 4 d’un tableau de taille 4 une valeur.

public class TestException {

    public static void main(String[] args) {
        int tab[] = {1,2,3,4};
        try{
            tab[4] = 3;

        } catch(Exception e ){
            System.out.println("Erreur d'index");
        }
    }
}

 Le programme ci-dessus affiche à la console Erreur d’index.  S’il n’y avait pas de bloc try, le programme se serait arrêté brusquement, et un message plus compliqué comme ceci allait apparaître sur la console :

Java.lang.ArrayIndexOutOfBoundsException: Index 4 out of bounds for length 4
at TestException.main(...)

En fait, s’il n’y avait pas de bloc try/catch pour l’exception ArrayIndexOutOfBoundsException, le programme ne se serait pas compilé parce qu’il ne répertorie pas cette exception comme une exception qui pourrait être levée.

Détails sur la clause Finally

Une clause finally est généralement incluse pour s’assurer que certaines opérations de nettoyage (par exemple, la fermeture des fichiers ouverts) sont effectuées.  Une clause finally s’exécute toujours lorsque son bloc try s’exécute (qu’il y ait ou non une exception).

De plus, si la clause finally comprend une instruction de transfert de contrôle (return, break, continue, throw), cette instruction prévaut sur tout transfert de contrôle initié dans le bloc try ou dans une clause catch.

Supposons que la clause finally n’inclut pas de transfert de contrôle. Voici les situations qui peuvent se présenter :

  1. Aucune exception ne se produit pendant l’exécution du try, et aucun transfert de contrôle n’est exécuté dans le try.  La clause finally s’exécute, puis l’instruction qui suit le bloc try.
  1.  Aucune exception ne se produit pendant l’exécution du try, mais un transfert de contrôle est exécuté. La clause finally s’exécute, puis le transfert de contrôle a lieu.
  1. Une exception se produit pendant l’exécution du try, mais il n’y a pas de clause catch pour cette exception. La clause finally s’exécute, puis l’exception non attrapée est “transmise” au bloc try suivant, éventuellement dans une fonction appelante.
  1. Une exception se produit pendant l’exécution du try, et il existe une clause catch pour cette exception. La clause catch n’exécute pas de transfert de contrôle.  La clause catch s’exécute, puis la clause finally, puis l’instruction suivant le bloc try.
  1. Une exception se produit pendant l’exécution du try, il y a une clause catch pour cette exception, et la clause catch exécute un transfert de contrôle.  La clause catch s’exécute, puis la clause finally, puis le transfert de contrôle a lieu.

    Si le bloc finally inclut un transfert de contrôle, il a la priorité sur tout transfert de contrôle exécuté dans le try ou dans une clause catch exécutée. Ainsi, dans tous les cas énumérés ci-dessus, la clause finally s’exécute, puis le transfert de contrôle a lieu. Voici un exemple :

try {
	return 0;
} finally {
        
	return 2;
}

Le résultat de l’exécution de ce code est 2.

La hiérarchie des exceptions en Java

Toute les exceptions et erreurs dérivent de la classe Throwable. Cela veut dire que lorsque vous voulez gérer les exceptions potentielles dans vos applications Big Data, vous partez du package Throwable. Par exemple, vous pouvez consciemment demander à votre application de générer une exception si certaines conditions sont rencontrées. La figure suivante illustre la hiérarchie des execptions en Java.

hierachie des exceptions en java

Toutes les classes qui gèrent les erreurs et exceptions en java comme vous pouvez le voir sur la figure, héritent de la classe Throwable. Si à ce stade, vous n’êtes pas familier avec le concept d’héritage, je vous recommande notre article spécialisé sur le sujet : Apprendre la programmation orientée objet.

La classe Error représente la classe de base de toutes les erreurs. L’application Java s’arrête instantanément dès l’apparition d’un objet de la classe Error. La classe Exception représente la classe de base de toutes les exceptions.

Note : Les exceptions héritant de la classe RuntimeException n’ont pas besoin d’être détectées impérativement dans les blocs try/catch.

Créer sa propre exception

Comme je l’ai mentionné précédemment, vous pouvez créer vos propres exceptions, ou demander à vos applications d’en générer une si un comportement précis se déroule ou si un ensemble de conditions sont remplies. Illustrons cela par un exemple.

Créons une classe LiquidNotFoundException qui hérite de la classe Exception. Une méthode qui risque lever une exception de type LiquidNotFoundException l’indique à l’aide de throws.

public class LiquidNotFoundException extends Exception{
    public LiquidNotFoundException(String s){
        super(s);
    }
}

public class Conteneur {
    private int quantiteConteneur;
    public Conteneur(){
        quantiteConteneur = 0;
    }

public void puiser(int quantite) throws LiquidNotFoundException{

     if(quantite > quantiteConteneur) {
		throw new LiquidNotFoundException("Pas assez de liquide dans le conteneur !");
	}
        else quantiteConteneur -=quantite; 
    }
}

Dans notre application, on compare la valeur de la variable quantite à celle de quantiteConteneur. Si sa valeur est supérieure à cette dernière, alors on demande à notre application de générer notre exception personnalisée LiquidNotFoundException. Celle-ci est une extension de Exception qui va afficher le message “Pas assez de liquide dans le conteneur“. Vous voyez que grace aux exceptions personnalisées, vous contrôlez mieux vos applications, et vous contrôlez également les messages d’erreur. Si on avait juste étendu notre exception à la classe Exception, alors, lorsque l’exception serait survenu, on aurait un message d’erreur bizarre de type “java.lang.Throw.exception at 39. LiquidNotFoundException“. Franchement ! Ce message n’est pas du tout significatif aux yeux de l’utilisateur final de l’application.

Voilà ! Nous sommes arrivés au terme de ce tutoriel synthétique sur la gestion des erreurs en Java. Comme vous l’avez vu, la gestion des erreurs et exceptions fait partie intégrante du cycle de vie du développement applicatif, que ce soit pour des applications Big Data ou non. Si vous ne gérez pas (ou ne savez pas gérer) les problèmes potentiels qui découlent du fonctionnement de votre application en production, il y’a des cas d’usage que vous ne réussirez pas à développer. De plus, vous mettrez un temps fou dans le run de vos applications. J’espère que ce tutoriel vous a aidé à utiliser le gestionnaire try catch de java pour gérer les exceptions et erreurs qui peuvent survenir dans vos applications. Ce tutoriel est une extension de notre tutoriel plus complet sur java intitulé : Apprendre Java – le guide complet, qui vous aide à utiliser le java pour développer des applications Big Data. Je vous recommande vivement de le lire si vous souhaitez travailler dans le Big Data.


Juvénal JVC

Juvénal est spécialisé depuis 2011 dans la valorisation à large échelle des données. Son but est d'aider les professionnels de la data à développer les compétences indispensables pour réussir dans le Big Data. Il travaille actuellement comme Lead Data Engineer auprès des grands comptes. Lorsqu'il n'est pas en voyage, Juvénal rédige des livres ou est en train de préparer la sortie d'un de  ses livres. Vous pouvez télécharger un extrait de son dernier livre en date ici : https://www.data-transitionnumerique.com/extrait-ecosystme-hadoop/

>