• 12 heures
  • {0} Facile|{1} Moyenne|{2} Difficile

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 30/04/2014

La gestion des erreurs

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Avant de clôturer cette partie, nous allons aborder un des sujets les plus fâcheux pour un programmeur : la gestion des erreurs.
Vous verrez que cela pourra s'avérer extrêmement utile lorsque vous travaillerez avec des données externes à l'application. Ce sera notamment le cas si vous êtes amenés à charger du contenu multimédia à l'intérieur de l'application ou si vous souhaitez échanger des données en réseau. Aussi, essayez d'être particulièrement attentif à ce qui va suivre, ce chapitre est important.

Les principes de base

Introduction à la gestion des erreurs

Lorsque nous prononçons le mot « erreurs », il est fréquent de voir des sourcils se froncer. Il s'agit en effet d'un sujet délicat, souvent synonyme de complications. Néanmoins, nous devons en passer par là pour s'assurer qu'un code s'exécute correctement.
En ce qui nous concerne, nous pouvons distinguer deux principaux types d'erreurs : les erreurs de compilation et les erreurs d'exécution.

Sans grande surprise, les erreurs de compilation sont des erreurs qui se produisent lors de la compilation. Elles sont généralement facile à déceler puisque que le compilateur nous informe de leur existence. Par ailleurs, lors de l'apparition d'une erreur de ce genre, la compilation est stoppée et la génération du fichier SWF n'est pas achevée. Il nous est alors nécessaire de corriger cette erreur afin de pouvoir finaliser la compilation.
Ce ne sont pas ces erreurs qui nous intéressent dans ce chapitre, nous allons plutôt nous concentrer sur les erreurs d'exécution.

Les erreurs d'exécution surviennent, comme leur nom l'indique, durant l'exécution du code. Contrairement aux erreurs de compilation, les erreurs d'exécution ne bloquent pas la compilation d'un programme, mais se produisent lors de l'exécution d'une instruction ou d'un code erroné. Suivant la complexité du programme, ces erreurs peuvent être difficiles à détecter, et donc à corriger.

La gestion des erreurs consiste alors à ajouter ou modifier du code dans un programme spécialement conçu pour traiter une ou plusieurs erreurs. Cela revient à détecter les éventuelles erreurs d'exécution et à les contourner afin de pouvoir reprendre, si possible, une exécution « normale » du programme. Le cas échéant, il peut être intéressant d'en informer tout simplement l'utilisateur.

Les différents types d'erreurs d'exécution

Les erreurs synchrones

Les erreurs d'exécution dites « synchrones » se produisent lors de l'exécution d'une instruction renvoyant un résultat immédiat. Nous pouvons également parler d'exceptions. Pour vous donner un exemple, voici un code qui ajoute un objet de type Sprite à la liste d'affichage sans l'avoir instancier au préalable :

var objet:Sprite;
addChild(objet);

Comme vous pouvez le voir, la compilation du code ne pose absolument aucun problème : le fichier SWF est donc bien généré. Toutefois, lors de l'exécution du code, vous allez voir apparaître ceci dans la console de sortie :

[Fault] exception, information=TypeError: Error #2007: Le paramètre child ne doit pas être nul.

Un tel message s'affichera à chaque fois qu'une erreur n'aura pas été traitée ; on dit alors que l'exception n'a pas été interceptée. Ce message permet au développeur de cibler l'erreur afin de la réparer ou de la corriger. Comme vous avez peut-être pu le constater, l'exécution du code s'est stoppée lors de l'apparition de l'erreur. En revanche, ceci n'est valable que dans la version de débogage du Flash Player. En effet, dans la version commerciale, le Flash Player va tenter de passer outre l'erreur et de continuer l'exécution du code si possible.

Pour en revenir à la gestion des exceptions, rappelez-vous que celles-ci sont générées dès lors que l'instruction en question est exécutée. Il est alors possible de traiter l'erreur immédiatement dans le même bloc que l'instruction incriminée. Pour cela, nous verrons comment isoler le code erroné à l'intérieur d'une instruction try...catch afin de réagir tout de suite et de pouvoir reprendre l'exécution de la suite du programme.

Les erreurs asynchrones

Lors d'une opération quelconque, il peut arriver qu'une erreur ne puisse pas être retournée immédiatement. Ceci pourrait être le cas, par exemple, lors de la lecture ou de l'écriture de données. En effet, l'accès à des données externes à l'application ne se fait pas de façon immédiate. Dans cette situation, une instruction try...catch serait alors inutile. La réponse aux erreurs liées à ces opérations est donc gérée par événements.

Lorsque la génération d'une erreur n'est pas directe mais différée, l'erreur d'exécution est dite « asynchrone ». Celle-ci est représentée par la classe ErrorEvent ou l'une de ses sous-classes. La réponse à cette dernière se fait grâce à une fonction d'écouteur comme d'ordinaire.

Les erreurs synchrones

L'intruction throw

Présentation

Avant d'entamer la gestion (ou interception) des exceptions en elle-même, nous allons parler de l'instruction throw. Ce mot-clé, que nous avons déjà vaguement évoqué, sert en réalité à renvoyer une exception. Cette exception se présente alors sous la forme d'une instance de la classe Error ou de l'une de ses sous-classes.

Pour vous donner un exemple, voici comment renvoyer une exception :

var monErreur:Error = new Error();
throw monErreur;

En exécutant ce code, la console de sortie laisse alors apparaître ceci :

[Fault] exception, information=Error

Il faut avouer que dans le cas précédent, les informations concernant l'erreur étaient relativement minces. Pour remédier à ce problème, il est possible de renseigner des informations sur l'erreur dans le constructeur de la classe Error. Vous pouvez donc renseigner un message qui s'affichera dans la console de sortie ou qui pourra être utilisé lors de l'interception de l'erreur. Un second paramètre id peut également servir à identifier l'erreur avec précision.

Une erreur bien renseignée serait par exemple :

var monErreur:Error = new Error("Le paramètre child ne doit pas être nul.", 2007);
Exemple

À présent, nous allons réaliser une petite fonction renvoyant diverses erreurs que nous utiliserons dans la suite avec le bloc try...catch.

Voilà donc la fonction que je vous propose :

function testerErreur(objet:Object):void
{
    if (objet == null)
        throw new TypeError("Le paramètre objet ne doit pas être nul.", 4000);
    if (objet == stage)
        throw new Error("Le paramètre objet ne doit pas être la scène principale.", 4001);
}

En l'analysant, vous constaterez que celle-ci renvoie des exceptions différentes suivant la nature de l'objet passé en paramètre. Cela pourrait correspondre par exemple aux vérifications nécessaires en début d'une méthode quelconque.
Vérifions tout de même le bon fonctionnement de cette fonction. En premier lieu, passons la scène principale en paramètre et voyons comment la version de débogage du Flash Player réagit.

L'instruction correspondante est la suivante :

testerErreur(stage);

Aucun souci, l'exception non interceptée est bien affichée dans la console :

[Fault] exception, information=Error: Le paramètre objet ne doit pas être la scène principale.

La seconde erreur renvoyée par la fonction définie plus haut, correspond au passage en paramètre d'un objet quelconque non instancié :

var monObjet:Object;
testerErreur(monObjet);

L'erreur est également bien renvoyée et affichée dans la console de sortie :

[Fault] exception, information=TypeError: Le paramètre objet ne doit pas être nul.

L'instruction try...catch

Utilisation basique

Entrons maintenant dans le vif du sujet et découvrons le fonctionnement de l'instruction try...catch. Cette instruction permet d'isoler le code pouvant renvoyer une exception : cela se fait alors à l'intérieur du bloc try. Ce bloc ne peut être utilisé indépendamment du bloc catch qui sert à intercepter les exceptions renvoyées.

try
{
    // Instructions à isolées	
}
catch(e:Error)
{
    // Gestion de l'exception	
}

Par exemple, utilisons notre fonction testerErreur() pour comprendre comment l'ensemble try...catch fonctionne. En l'absence d'erreur, seul le bloc try est exécuté. Voyons cependant ce que cela donne lorsqu'une erreur apparaît. Tentons de renvoyer une exception de type Error :

try 
{
    testerErreur(stage);
    trace("Aucune exception renvoyée");	
}
catch(e:Error)
{
    trace("Exception interceptée : #" + e.errorID);	
}

Le résultat dans la console de sortie est le suivant :

Exception interceptée : #4001

Grâce à cet exemple, nous pouvons constater un certain nombre de choses. Tout d'abord, nous voyons que l'exception a bien été interceptée puisque qu'aucun message d'erreur n'est apparu dans la console de sortie. Ensuite, vous remarquerez que l'exécution du bloc try s'est stoppée lors de l'apparition de l'erreur. Enfin, le bloc catch a été exécuté intégralement et il possible de récupérer des informations

Utilisation avancée

Précédemment, nous avons vu comment mettre en place un bloc try...catch, permettant de gérer de façon basique les erreurs synchrones. Toutefois, il est possible de gérer de manière plus poussée les exceptions renvoyées à l'intérieur du bloc try.
D'une part, nous pouvons ajouter plusieurs blocs catch permettant d'identifier plus spécifiquement différents types d'erreurs. Chaque instruction catch est alors vérifiée dans l'ordre et est associée un type d'exception. Seul le premier bloc catch, correspondant au type d'exception renvoyée, est exécuté.
D'autre part, le mot-clé facultatif finally permet d'instroduire des instructions supplémentaires, exécutées au terme de la gestion de l'erreur, quel que soit le chemin parcouru à travers les différents blocs try ou catch.

Pour illustrer tout ce qui vient d'être dit, voici un exemple de code contenant un certain nombre de blocs :

try 
{
    testerErreur(null);
}
catch (e:TypeError) 
{
    trace("Exception interceptée de type TypeError");
}
catch(e:Error)
{
    trace("Exception interceptée de type Error");
}
finally 
{
    trace("Gestion terminée");
}

Après exécution du code, la console laisse apparaître ceci :

Exception interceptée de type TypeError
Gestion terminée

Comme prévu, les blocs try et finally ont été exécutés et le seul bloc catch associé au type TypeError a été exécuté.

Les erreurs asynchrones

Distribuer un objet ErrorEvent

Nous allons maintenant nous intéresser aux erreurs asynchrones et à leur gestion. Comme je vous l'ai introduit plus tôt, ce type d'erreurs est représenté par des événements héritant de la classe ErrorEvent. Pour générer une erreur asynchrone, nous pouvons donc nous servir de la méthode dispatchEvent() de la classe EventDispatcher.

Pour la suite, je vous propose donc de renvoyer des erreursvia la fonction suivante :

function testerErreur(objet:Object):void
{
    if (objet == null)
        dispatchEvent(new ErrorEvent(ErrorEvent.ERROR));
}

Ensuite, voici comment générer ladite erreur :

testerErreur(null);

Sans gestion directe de l'erreur, la version de débogage du Flash Player arrête l'exécution du code et renvoie l'erreur à l'intérieur de la console de sortie. Voilà le message affiché :

Error #2044: error non pris en charge : text=

Gérer des événements d'erreurs

Aussi surprenant que cela puisse paraître, vous n'allez rien apprendre ici. Effectivement, la gestion des événements est strictement identique quel que soit le type d'événements. Seul la classe d'événement devra être ajustée pour pouvoir répondre aux erreurs asynchrones.

Nous pouvons donc très facilement définir une fonction de rappel pour un objet de type ErrorEvent et enregistrer l'écouteur auprès de l'objet courant.
D'ailleurs en voici l'écriture :

function traiterErreur(error:ErrorEvent):void
{
    trace("Erreur traitée");
}
addEventListener(ErrorEvent.ERROR, traiterErreur);

Testons la gestion de l'erreur comme tout à l'heure :

testerErreur(null);

Aucune surprise, la fonction d'écouteur est exécutée lors de l'apparition de l'événement :

Erreur traitée

Bien comprendre les deux approches

Une classe utilisant les deux approches

Précédemment, nous avons vu comment gérer des erreurs d'exécution, qu'elles soient synchrones ou asynchrones. Cependant, il est fort probable que certains d'entre vous n'arrivent pas encore à bien distinguer les différences d'utilisation qui existent entre les deux types d'erreurs. Je vous propose ici une classe permettant d'illustrer ceci.

Présentation

Dans un premier temps, je vous laisse découvrir et vous familiariser avec le code de cette classe donné ci-dessous :

package  
{
    import flash.display.DisplayObject;
    import flash.events.ErrorEvent;
    import flash.events.Event;
    
    public class Animation 
    {
        private var _objet:DisplayObject;
        private var _vars:Object;
        
        public function Animation(objet:DisplayObject = null) 
        {
            _objet = objet;
            _vars = null;
        }
        
        public function incrementer(vars:Object):void
        {
            if (_objet == null) {
                throw new TypeError("Objet nul");
            }else {
                _vars = vars;
                _objet.addEventListener(Event.ENTER_FRAME, animer);
            }
        }
        
        private function animer(event:Event):void
        {
            for (var p:String in _vars) {
                try {
                    _objet[p]++;
                    if (_objet[p] >= _vars[p]) {
                        _objet.removeEventListener(Event.ENTER_FRAME, animer);
                    }
                }catch (e:Error) {
                    _objet.dispatchEvent(new ErrorEvent("AnimErrorEvent"));
                }
            }
        }
    	
    }

}
Fonctionnement

Rassurez-vous, si vous n'avez pas entièrement cerné le fonctionnement de la classe Animation, c'est normal !
C'est pourquoi, je vous propose maintenant quelques explications. Tout d'abord, et j'espère que vous l'aurez compris, cette classe va nous permettre d'animer un objet de type DisplayObject. Pour être plus précis, la méthode incrementer() va servir à lancer une animation visant à augmenter la valeur de certaines des propriétés de l'objet d'affichage jusqu'à une certaine valeur. Les propriétés à prendre en compte doivent être renseignées à travers le paramètre vars.

Revenons sur la définition de la méthode incrementer() :

public function incrementer(vars:Object):void
{
    if (_objet == null) {
        // Résultat immédiat permettant de générer une erreur synchrone
        throw new TypeError("Objet nul");
    }else {
        _vars = vars;
        _objet.addEventListener(Event.ENTER_FRAME, animer);
    }
}

Nous pouvons constater qu'un test est réalisé afin de vérifier que l'élément _objet est bien instancié, et donc différent de la valeur null. Si ce n'est pas le cas, nous ne pouvons pas démarrer l'animation et choisissons de renvoyer une exception. Dans cette situation, l'erreur est détectée immédiatement et est bien de type synchrone. Dans la classe parente, une instruction try...catch permettra de gérer l'erreur.
Si aucune erreur n'est détectée, l'animation peut être lancée en enregistrant un écouteur sur l'événement Event.ENTER_FRAME.

Je vous invite à présent à tester le bon fonctionnement de la classe Animation. Essayons par exemple d'animer la position d'un objet jusqu'à la position (100,100) :

var animation:Animation = new Animation(unObjet);
animation.incrementer( { x: 100, y:100 } );

Nous voyons ici que le programme ne pose aucun problème lors de l'exécution.

Penchons-nous maintenant sur l'animation en elle-même, traitée à l'intérieur de la fonction de rappel animer(). Voici le contenu de cette méthode :

private function animer(event:Event):void
{
    for (var p:String in _vars) {
        try {
            _objet[p]++;
            if (_objet[p] >= _vars[p]) {
                _objet.removeEventListener(Event.ENTER_FRAME, animer);
            }
        }catch (e:Error) {
            _objet.dispatchEvent(new ErrorEvent("AnimErrorEvent"));
        }
    }
}

Concernant le fonctionnement, la boucle for...in permet de récupérer chacune des propriétés de l'objet _vars sous forme de chaînes de caractères. À l'aide de ces dernières, nous avons alors accès aux propriétés correspondantes de l'objet d'affichage afin de pouvoir les incrémenter. Toutefois, il pourrait arriver que les propriétés renseignées ne soient pas de type numérique ou n'existent tout simplement pas. Il serait alors avisé de renvoyer une erreur informant l'utilisateur qu'il tente d'accéder à une propriété inexistante ou ne pouvant être incrémentée.

Pour réaliser ceci, commençons par isoler le code d'accès à la propriété en question. En effet, c'est cette instruction qui pourrait renvoyer une erreur et qui permettra de déterminer si une erreur doit être renvoyée. Si l'erreur est avérée, celle-ci doit être renvoyée à l'utilisateur sous forme d'erreur asynchrone.

Prenons l'exemple suivant afin de provoquer l'apparition d'une erreur :

var animation:Animation = new Animation(objet);
animation.incrementer( { a:100, b:100 } );

L'apparition de l'erreur asynchrone a bien lieu :

Error #2044: AnimErrorEvent non pris en charge : text=

Intérêts des erreurs

Les erreurs d'exécution ont principalement deux utilités.
D'une part, une erreur peut servir à informer le programmeur ou utilisateur d'une classe qu'il en fait un usage erroné. C'est notamment le cas lors de la conception de classes abstraites, comme nous l'avons vu, ou encore lorsque l'on souhaite avertir que l'un des paramètres d'une méthode n'est pas valide. Dans ce cas, le programmeur va revoir presque systématiquement son code afin de réparer l'erreur en question.
D'autre part, l'erreur peut être persistante et non réparable par le programmeur. Ce dernier peut alors contourner le problème afin de pouvoir poursuivre au mieux l'exécution du programme. C'est le genre de situation que vous pourrez rencontrer lorsque vous tenterez d'accéder à des données externes à l'application.

Dans tous les cas, la meilleure chose à faire est de ne pas traiter l'erreur vous-mêmes, mais de laisser la possibilité à l'utilisateur de la classe de gérer les erreurs comme il l'entend.

En résumé
  • La gestion des erreurs consiste à ajouter du code spécifique au traitement des erreurs d'exécution.

  • Les erreurs synchrones sont renvoyées immédiatement lors de l'exécution de l'instruction erronée.

  • Les erreurs asynchrones correspondent aux erreurs ne pouvant pas être générées dans l'immédiat.

  • L'instruction try...catch permet d'isoler un code erroné renvoyant une erreur synchrone ou exception.

  • Une erreur asynchrone est représentée par la classe ErrorEvent ou une de ses sous-classes et peut être gérée comme n'importe quel type d'événements.

Exemple de certificat de réussite
Exemple de certificat de réussite