La gestion des erreurs

Ce contenu est obsolète. Il peut contenir des informations intéressantes mais soyez prudent avec celles-ci.

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, essayer d'être un maximum attentif à ce qui va suivre.

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, qui est souvent synonyme de prises de tête. Néanmoins, nous devons 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. Ces erreurs ne sont cependant pas concernées par ce chapitre, mais nous nous intéresserons plutôt aux erreurs d'exécution !

Les erreurs d'exécution, quant à elles, 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. Dans 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 :

1
2
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 :

1
[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 traitée l'erreur immédiatement, dans le même bloc que l'instruction incriminée. Pour cela, nous verrons comment isolé le code erroné à l'intérieur d'une instruction try...catch, afin de réagir tout de suite et 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és à 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 gra^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 :

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

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

1
[Fault] exception, information=Error

Il faut avouer que dans le cas précédent, les informations concernant l'erreur sont 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. Également, un second paramètre id peut servir à identifier avec précision l'erreur.

Cet identificateur sert généralement à rechercher les causes de l'erreur, principalement si vous n'êtes pas l'auteur de la classe l'ayant générée. Vous pouvez notamment trouver des informations précieuses suite à une recherche sur Internet.

Une erreur bien renseignée serait par exemple :

1
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 :

1
2
3
4
5
6
7
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 à quelques vérifications nécessaire 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 :

1
testerErreur(stage);

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

1
[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é :

1
2
var monObjet:Object;
testerErreur(monObjet);

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

1
[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'isolé 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.

1
2
3
4
5
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 :

1
2
3
4
5
6
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 :

1
Exception interceptée : #4001

Grâce à cet exemple, nous pouvons constater un certain nombre de chose. 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 bloc 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 bloc try ou catch.

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

1
2
3
4
5
6
7
8
9
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 :

1
2
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é.

Dans le cas de blocs catch associés à plusieurs type d'exceptions, ceux-ci doivent être ordonnés du type le plus spécifique au moins spécifique. Ainsi la classe Error, super-classe de toutes les autres, doit se être traitée en dernier. Dans l'exemple précédent, une exception de type ArgumentError aurait été interceptée par le dernier bloc catch.

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 erreurs via la fonction suivante :

1
2
3
4
5
function testerErreur(objet:Object):void
{
    if (objet == null)
        dispatchEvent(new ErrorEvent(ErrorEvent.ERROR));
}

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

1
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é :

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

Bien évidemment, il est préférable d'étendre la classe ErrorEvent afin de personnaliser l'erreur à son utilisation. Par exemple, la classe IOErrorEvent est spécifique aux erreurs générés par l'interaction avec des données externes au programme.

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 ajuster 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 :

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

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

1
testerErreur(null);

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

1
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 qu'il existe entre les deux types d'erreurs. Je vous propose alors 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 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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"));
                }
            }
        }

    }

}

Animation.as

Fonctionnement

Rassurez-vous, si vous n'avez pas entièrement cerner 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 correspondant à 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() :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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 ici qu'un test est réaliser 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) :

1
2
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.

Vous remarquerez le passage de la valeur null en paramètre de la méthode incrementer() génère bien une exception.

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 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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.

Il s'agit bien d'une exception qui est renvoyée en tant qu'erreur asynchrone à la classe de niveau supérieur. En effet, si cette erreur est synchrone à l'intérieur de la méthode animer(), celle-ci est néanmoins asynchrone par rapport à l'appel de la méthode incrementer() réalisé par l'utilisateur. Dites-vous bien qu'entre l'appel de cette dernière et l'apparition de l'erreur, il s'est écoulé un certain temps puisque nous sommes dans la fonction d'écouteur de l'événement Event.ENTER_FRAME.

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

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

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

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

Vous remarquerez qu'en diminuant la cadence d'animation du projet, il est possible de percevoir très nettement le retard de génération de l'erreur asynchrone. Essayez par exemple de donner la valeur 1 à la propriété frameRate de l'objet stage. Vous verrez alors l'erreur n'apparaître qu'au bout d'une seconde dans la console de sortie.

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 lorsqu'on souhaite avertir qu'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é comme n'importe quel type d'événements.