Apprendre à gérer les erreurs en VBA: On Error

Comment gérer les erreurs et les exceptions à l’aide de On Error en VBA : De la théorie à la « bonne » pratique

Pierre Fauconnier

Dans les codes fournis sur le forum, je vois régulièrement apparaître des On Error Resume Next ou des On Error Goto avec un Exit Sub.

- Titre : Apprendre à gérer les erreurs en VBA: On Error

- Auteur : Pierre Fauconnier

- Parution : 4 juillet 2019

- Mise à jour : 5 juillet 2019

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2019 Pierre Fauconnier. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.

Sommaire

- I. Introduction

- I-A. Constat

- I-B. A qui s’adresse ce tutoriel

- I-B-1. Niveau

- II. On ne programme pas « par l’erreur », ou comment éviter les erreurs

- II-A. On ne programme pas « par l’erreur »

- II-B. Quelques bonnes pratiques pour éviter les erreurs/exceptions

- II-B-1. S’obliger à déclarer ses variables grâce à Option Explicit

- II-B-2. Éviter les variables globales

- II-B-3. Bannir le « hard coding »

- II-B-4. Écrire des procédures et fonctions courtes qui ont une seule responsabilité

- II-B-5. Définir l’architecture de son code… et s’y tenir coûte que coûte

- II-B-6. Écrire du code qui s’auto-documente

- II-B-6-a. Nommer ses procédures, fonctions, variables et constantes de façon claire

- II-B-6-b. Utiliser les différents types de boucles à bon escient

- II-B-6-c. Bannir les Exit

- II-B-7. Paramètres de fonction ByRef ou ByVal

- II-B-8. Tester, tester et tester encore

- III. Un peu de théorie

- III-A. On Error ou « On Exception » ?

- III-B. Comment ça fonctionne ?

- III-B-1. Recherche d’un gestionnaire d’erreur

- III-B-1-a. Pile des appels

- III-B-1-b. Ou placer la gestion d’une erreur

- III-B-2. Try – Catch - Finally

- IV. Mise en pratique

- IV-A. Quand utiliser On Error ?

- IV-B. Quel On Error Utiliser ?

- IV-B-1. On Error Resume Next

- IV-B-2. On Error Goto

- IV-B-3. On Error Goto

- IV-C. Quand l’erreur survient

- IV-C-1. L’objet Err

- IV-C-2. Traitement de l’erreur

- IV-C-3. Après le traitement de l’erreur

- IV-C-3-a. On Error Resume

- IV-C-3-b. On Error Resume Next

- IV-C-3-c. Err.Clear

- IV-D. Définir les options de gestion des erreurs

- IV-D-1. Trois options pour la gestion des erreurs

- IV-D-2. On Error durant le développement

- IV-D-3. On Error « en production »

- IV-E. Tracer les erreurs et les exceptions

- IV-F. Conséquences de l’absence d’une gestion d’erreurs avec On Error

- V. Err.Raise

- VI. Conclusions

- VII. Remerciements

I - Introduction

I-A - Constat

I-B - A qui s’adresse ce tutoriel

I-B-1 - Niveau

II - On ne programme pas « par l’erreur », ou comment éviter les erreurs

II-A - On ne programme pas « par l’erreur »

II-B - Quelques bonnes pratiques pour éviter les erreurs/exceptions

II-B-1 - S’obliger à déclarer ses variables grâce à Option Explicit

En VBA, il est possible d’utiliser une variable sans devoir d’abord la déclarer. Il semble dès lors plus simple au débutant de ne pas déclarer ses variables et de les utiliser « à la volée » lorsque le besoin s’en fait sentir.

Trois problèmes découlent de cette mauvais pratique :

1. Les variables n’étant pas typées, elles vont acquérir le type de la valeur qu’elles reçoivent, et être transtypées au fur et à mesure du besoin ;

2. Lors de l’exécution, toute variable utilisée sans avoir été valorisée est Empty et va donc recevoir la valeur par défaut du type attendu de la variable manipulée ;

3. Certaines opérations sur les variables peuvent s’avérer surprenantes.

Exemple 1 : Le code suivant illustre le transtypage opéré sur la variable en fonction de la valeur qui lui est affectée.

Exemple 2 : A n’étant pas déclarée, elle est vide (Empty) et est transtypée en valeur numérique, prenant la valeur 0 par défaut.

Exemple 3 : a + a produit un résultat différent selon que a est Long ou String, alors que b + b renvoie un Long car "1" est transtypé au moment de son affectation. Il met donc en évidence la différence significative du résultat selon que la valeur est déclarée et typée ou non.

Sub Test2()
Dim b As Long

a = 1
Debug.Print "Si a = 1, alors, a + a = " & a + a
a = "1"
Debug.Print "Si a = ""1"", alors, a + a = " & a + a
b = "1"
Debug.Print "b est transtypé en long et donc b + b = " & b + b
End Sub

On observera au passage que l’opérateur + est d’abord un opérateur d’addition puis de concaténation, car "1" + 1 => 2 et pas "11"

Pour que la déclaration des variables soit obligatoire dans un module, il faut que celui-ci commence par la ligne Option Explicit avant toute procédure ou fonction. Afin de s’assurer de la présence de cette ligne en début de module, on modifiera les options du VBE (Visual Basic Editor) de manière à que la ligne soit présente à l’entame d’un nouveau module.

La modification de cette option dans le VBE ne joue que pour les nouveaux modules. Cette ligne n’est pas ajoutée au début des modules existants. Il conviendra donc de l’ajouter manuellement.

II-B-2 - Éviter les variables globales

On pense souvent que les variables globales facilitent la programmation car elles permettent à toute procédure de les utiliser, dans n’importe quel module. C’est bien sûr exact, mais le piège est là, justement. Derrière cette apparente facilité se cachent plusieurs problèmes.

Car si une variable peut être utilisée partout dans le code, elle peut aussi être modifiée partout dans le code. Il serait donc possible qu’une procédure appelée modifie la valeur d’une variable, rendant l’exécution de la suite de la procédure appelante caduque.

L’utilisation des variables globales rend les tests des procédures et fonctions qui les utilisent compliqués à réaliser, puisque les variables globales auront dû être valorisées au préalable. Il est plus intéressant d’écrire des procédures, fonctions ou propriétés utilisant des arguments car ces dernières sont alors réutilisables et testables plus facilement.

Fuyez comme la peste les variables globales. Il y a a priori toujours moyen de s’en passer.

II-B-3 - Bannir le « hard coding »

Cela semble une évidence, mais le hard coding est à bannir systématiquement de vos pratiques de programmation.

Qu’est-ce que le hard coding ? il s’agit d’écrire en dur des valeurs dans le code, au lieu d’affecter ces valeurs à des variables.

II-B-4 - Écrire des procédures et fonctions courtes qui ont une seule responsabilité

II-B-5 - Définir l’architecture de son code… et s’y tenir coûte que coûte

II-B-6 - Écrire du code qui s’auto-documente

II-B-6-a - Nommer ses procédures, fonctions, variables et constantes de façon claire

II-B-6-b - Utiliser les différents types de boucles à bon escient

Il y a boucle et boucle. Une boucle For… Next n’est pas équivalente à une boucle Do While…Loop. Même s’il est possible dans certains cas d’utiliser l’une à la place de l’autre, elles diffèrent par l’intention du programmeur et donc, partant, de ce qu’elles sont censées exécuter dans le programme.

Comme leur nom l’indique, les boucles For… Next ou For Each… Next vont boucler un certain nombre de fois (For…) ou sur chaque élement d’une collection (For Each). On est donc en droit d’attendre que le programme bouche sur chaque élément, du premier au dernier. La finalité d’une boucle For est donc de boucler X fois ou de boucler sur tous les éléments d’une collection.

La boucle Do et ses dérivées vont boucler en tenant compte d’une condition qui imposera la sortie de la boucle. Cette condition peut être déclarée en entrée ou en sortie de boucle, selon que la boucle peut ne jamais être exécutée ou doit l’être au moins une fois. Il existe plusieurs écritures possibles pour les boucles DO, mais elles ont la même finalité : Boucler en tenant compte d’une condition.

II-B-6-c - Bannir les Exit

Nous l’avons vu au point précédent, il faut à mon estime bannir Exit des boucles, puisqu’il y a moyen très simplement de s’en passer en rendant son code plus lisible et plus sécurisé.

Il faut également bannir Exit des procédures et fonctions, notamment en ce qui concerne la gestion des erreurs ou des exceptions. Or, je vois souvent des codes utilisant On Error écrits de la manière suivante :

Cette structure de code pose problème car elle propose deux sorties de procédure. Le problème est alors qu’il faut s’assurer que ces sorties sont réalisées de façon correcte. Observons les codes suivants.

Dans le premier cas, la procédure neutralise la gestion des évènements dans Excel. La sortie normale de la procédure restaure la gestion des évènements, alors que la sortie sur erreur la laisse désactivée, amenant à ce que les évènements ne soient plus capturés par la suite.

Un second cas est celui de l’ouverture d’une connexion à une DB pour traiter de l’information. Si tout se passe bien, la sortie normale de la procédure referme la connexion, alors que la sortie sur erreur la laisse ouverte.

Dans le troisième cas, le déroulement normal de la procédure ferme proprement le fichier Excel qui a été ouvert alors que la sortie sur erreur le laisse ouvert en invisible

II-B-7 - Paramètres de fonction ByRef ou ByVal

II-B-8 - Tester, tester et tester encore

On ne le répètera jamais assez. Il faut tester, tester, tester, encore et encore. Oserais-je dire que l’on ne teste jamais assez ? Et lors des tests, il faut se méfier des « évidences ». Vous placez un contrôle qui attend une date et vous supposez que l’utilisateur, peut-être vous !, va saisir une date ? Erreur ! Selon la Loi de Murphy, si un utilisateur peut saisir autre chose qu’une date, alors, à un moment donné, il saisira autre chose qu’une date.

Cet exemple illustre qu’il faudrait un « Monsieur Loyal » pour vos tests. Quelqu’un qui va, justement, tenter de rentrer du texte là où votre code attend une date. Faites-vous des procédures de test que vous généralisez pour envisager, petit à petit, les cas de figure qui peuvent se présenter durant l’exécution de votre code. Je ne vais pas énumérer tous les tests possibles, mais m’arrêter sur certains problèmes qui surviennent fréquemment :

- Votre code doit accéder à un fichier ? Testez-le avec le fichier présent mais aussi en supprimant le fichier pour observer le comportement du code ;

- Vous utilisez une feuille de calcul dans votre code ? Et si l’utilisateur la supprime, que se passe-t-il ? S’il la renomme ? S’il la déplace ? Méfiez-vous des index qui peuvent être modifiés par un déplacement de feuille, de dia, d’image,… ;

- Vous placez une condition ? Testez les « valeurs limite » pour débusquer les « effets de bord ». Si vous recevez une variable qui doit se trouver dans une plage donnée, testez les valeurs aux limites de cette plage. Pensez que l’opposé de « > » n’est pas « < » mais bien « <= », par exemple, et testez toutes les combinaisons possibles pour observer le comportement de votre code ;

- Testez ce qui se passe si votre code reçoit une date au format MM/DD/YY alors que vous l’attendez au format DD/MM/YY. Et si l’utilisateur saisit YYYY, que se passe-t-il ? Et si le séparateur des dates n’est pas celui attendu, que se passe-t-il ?

- Testez le séparateur décimal. Comment se comporte votre code s’il reçoit 5,23 alors qu’il devrait recevoir 5.23 ?

- Vous placez une mise en forme conditionnelle sur des cellules Excel par VBA ? En quelle langue la rédigez-vous ? Que se passera-t-il si un utilisateur lance votre code sur un ordinateur fonctionnant dans une autre langue (Office et/ou Windows) ?

Si j’ai placé de point en dernier lieu, c’est parque vos tests seront facilités et bien plus pertinents si vous respectez les points précédents. Une petite procédure recevant des arguments est plus facile à tester que 300 lignes de code utilisant des variables globales. Des variables bien nommées et des noms de procédures/fonctions explicites faciliteront vos tests. Vos codes, regroupés par module au sein d’une architecture applicative cohérente seront plus faciles à tester, à maintenir et à faire évoluer.

III - Un peu de théorie

III-A - On Error ou « On Exception » ?

III-B - Comment ça fonctionne ?

III-B-1 - Recherche d’un gestionnaire d’erreur

III-B-1-a - Pile des appels

III-B-1-b - Ou placer la gestion d’une erreur

III-B-2 - Try – Catch - Finally

IV - Mise en pratique

IV-A - Quand utiliser On Error ?

IV-B - Quel On Error Utiliser ?

IV-B-1 - On Error Resume Next

IV-B-2 - On Error Goto

IV-B-3 - On Error Goto

IV-C - Quand l’erreur survient

IV-C-1 - L’objet Err

IV-C-2 - Traitement de l’erreur

IV-C-3 - Après le traitement de l’erreur

IV-C-3-a - On Error Resume

IV-C-3-b - On Error Resume Next

IV-C-3-c - Err.Clear

IV-D - Définir les options de gestion des erreurs

IV-D-1 - Trois options pour la gestion des erreurs

IV-D-2 - On Error durant le développement

IV-D-3 - On Error « en production »

IV-E - Tracer les erreurs et les exceptions

IV-F - Conséquences de l’absence d’une gestion d’erreurs avec On Error

V - Err.Raise

VI - Conclusions

VII - Remerciements