Licence CC BY-NC-SA

La gestion des erreurs en VBA

Parce que ça peut arriver

En développant en VBA, vous avez déjà dû faire face à des erreurs d’exécution.

Ces erreurs, douloureuses, mais nécessaires, peuvent requérir une gestion bien particulière dans certains cas. Par exemple, nous pourrions imaginer une calculatrice où nous demanderions deux nombres ainsi qu’un opérateur binaire. Si l’utilisateur saisirait zéro en second nombre pour une division, le programme lèverait une erreur de division par zéro et nous pourrions capturer cette erreur pour demander un nouveau nombre à l’utilisateur.

Au cours de ce billet, nous allons voir comment fonctionne la gestion des erreurs en VBA.

C’est parti !

Pour ce billet, nous nous placerons dans l’environnement Microsoft Office.

Instructions

Le langage de macro de Microsoft met à disposition différentes instructions pour gérer les exceptions.

Si vous connaissez d’autres langages tels que Java, vous avez peut-être déjà utilisé des try, catch ou encore finally. Rien de tout cela en VBA, mais ce dernier offre tout de même des mécanismes afin de gérer les exceptions pour les capturer, mais aussi pour reprendre l’exécution du programme. Voyons cela.

Capturer l’erreur

Pour capturer une erreur, il faut identifier dans quelle instruction ou quel bloc d’instructions elle peut survenir. Si nous reprenons notre exemple de calculatrice, l’erreur surviendrait lors de l’évaluation du calcul, en divisant par zéro.

On Error GoTo étiquette

Dès lors, nous pouvons décider de capturer cette exception avec l’instruction On Error GoTo étiquette.

Le branchement de gestion d’exception doit se trouver au sein de la procédure, fonction ou propriété courante, sinon une erreur de compilation est levée.

Private Sub Exemple_On_Error_GoTo()
    On Error GoTo DivisionParZero
    Dim dResultat As Double
    dResultat = 1 / 1
    dResultat = 1 / 0 ' Affiche "Calcul impossible !"
    Exit Sub
DivisionParZero:
    Debug.Print ("Calcul impossible !")
End Sub

Il faut penser à ajouter une instruction pour quitter la fonction (Exit Function), la procédure (Exit Sub) ou encore la propriété (Exit Property) avant le bloc de gestion d’erreur sans quoi il sera aussi exécuté lorsqu’aucun problème n’a été rencontré.

On Error Resume Next

Une autre possibilité est de forcer le passage à l’instruction suivante en cas d’erreur. Nous avons alors le choix de gérer cette erreur de manière silencieuse ou non. Cela peut être utile pour des instructions qui peuvent lever des erreurs, mais pour qui c’est des comportements voulus que nous ne voulons pas traiter.

Voici un exemple avec la suppression du corps d’un tableau qui lève une erreur lorsque vide en VBA :

Private Sub Exemple_On_Error_Resume_Next()
    On Error Resume Next
    Sheets(1).ListObjects("Tableau1").DataBodyRange.Delete
    
    ' Gestion silencieuse ou non en décommentant les lignes suivantes
    'If Err.Number <> 0 Then
        'Debug.Print ("Une erreur est survenue lors de la suppression du contenu du tableau...")
    'End If
    On Error GoTo 0
End Sub

Dans la documentation, il est aussi mentionné que c’est cette approche qu’il faut privilégier plutôt que On Error GoTo étiquette pour accéder à un objet avec GetObject.

Le revers de la médaille d'On Error Resume Next, c’est le risque d’ignorer involontairement des exceptions sans s’en rendre compte ! Cela peut amener des comportements ou des problèmes difficiles à cerner. Par exemple, imaginons un programme qui lit le contenu d’un fichier et utilisant cette instruction tout au début. Si une erreur survient (avec un fichier non trouvé par exemple), l’exécution du programme pourrait nous faire croire que le contenu du fichier à lire est vide. Il faut donc être particulièrement vigilant en utilisant cette instruction et l’appliquer à des blocs bien délimités comme montré ci-dessus.

On Error GoTo 0

Cette dernière variante de On Error annule l’effet des deux précédentes. Nous revenons alors à la gestion par défaut et toute erreur d’exécution produira le plantage du programme avec une fenêtre.

Cette instruction est exécutée de manière implicite en sortie de procédure, fonction et propriété.

Private Sub Exemple_On_Error_GoTo_0()
    Dim dResultat As Double
    On Error Resume Next
    dResultat = 1 / 0 ' Rien
    dResultat = 1 / 0 ' Rien
    On Error GoTo 0
    dResultat = 1 / 0 ' Erreur d'exécution 11 : Division par zéro
End Sub
Fenêtre erreur d'exécution
Fenêtre erreur d’exécution

Reprendre l’exécution

Une fois l’erreur identifiée et gérée, nous pourrions souhaiter reprendre l’exécution. L’instruction Resume sert à cela avec trois possibilités.

Resume

Resume permet de reprendre à la ligne ayant généré l’exception.

Private Sub Exemple_Resume()
    On Error GoTo GestionErreur
    Dim dNombre2 As Double
    Dim dResultat As Double
    dNombre2 = 0
    dResultat = 1 / dNombre2 ' Ligne générant une erreur la première fois
    Debug.Print (dResultat) ' Affiche 1
    Exit Sub
GestionErreur:
    dNombre2 = 1
    Resume ' Reprise à la ligne ayant généré l'erreur
End Sub

Resume Next

Resume Next, quant à elle, permet de reprendre à partir de la ligne suivante celle ayant généré l’exception.

Private Sub Exemple_Resume_Next()
    On Error GoTo GestionErreur
    Dim dNombre2 As Double
    Dim dResultat As Double
    dNombre2 = 0
    dResultat = 1 / dNombre2 ' Ligne générant une erreur
    Debug.Print (dResultat) ' Affiche 'inf'
    Exit Sub
GestionErreur:
    dNombre2 = 1
    Resume Next ' Reprise à la ligne suivante celle ayant généré l'erreur
End Sub

Resume étiquette

Enfin, Resume étiquette permet de reprendre l’exécution à un branchement, pourvu que celui-ci se trouve bien dans la procédure ou fonction ou propriété en cours.

Private Sub Exemple_Resume_GoTo_Etiquette()
    On Error GoTo GestionErreur
    Dim dNombre2 As Double
    Dim dResultat As Double
SaisieNombre:
    dNombre2 = CDbl(InputBox("Merci de saisir un nombre valide")) ' Erreur 'Incompatibilité de type' possible
    dResultat = 1 / dNombre2 ' Erreur 'Division par zéro' possible
    Debug.Print (dResultat)
    Exit Sub
GestionErreur:
    dNombre2 = 1
    Resume SaisieNombre
End Sub

Dans ce dernier exemple, remarquons que la saisie de la valeur 0 comme d’une valeur qui n’est pas un nombre entraînent la réouverture de la fenêtre de saisie. Il y a donc deux types d’erreur possibles dans ce programme : la division par zéro et l’incompatibilité de type lorsque la conversion de type (la transformation de la saisie en nombre) n’est pas possible. Pour le moment, nous ne savons pas identifier précisément le type d’erreur qui est survenu, mais nous verrons cela un peu plus loin.

Laisser l’erreur se propager pour s’en occuper ailleurs

Nous ne sommes pas obligés de gérer ou d’ignorer l’erreur au sein même de la fonction ou procédure ou propriété où elle est levée.

En effet, l’erreur se propage dans la pile des appels jusqu’à trouver un endroit où elle est prise en compte ou ignorée (sinon le programme plante). Il est ainsi possible de déléguer la gestion de l’erreur en dehors de la fonction ou procédure ou propriété où elle a été levée :

Private Sub Exemple_Propagation_Erreur()
    On Error GoTo GestionErreur
    Exemple_Division_Erreur
    Exit Sub
GestionErreur:
    Debug.Print Err.Number, Err.Description ' 11 Division par zéro
End Sub

Private Function Exemple_Division_Erreur() As Double
    Dim dResultat As Double
    Exemple_Division_Erreur = dResultat = 1 / 0 ' Erreur levée
End Function

Nous pouvons mettre encore plus de distance :

Private Sub Exemple_SuperPropagationErreur()
    On Error GoTo GestionErreur
    Exemple_Propagation_Erreur
    Exit Sub
GestionErreur:
    Debug.Print Err.Number, Err.Description ' 11 Division par zéro
End Sub

Private Function Exemple_Propagation_Erreur() As Double
    Exemple_Propagation_Erreur = Exemple_Division_Erreur ' Erreur qui se propage
End Function

Private Function Exemple_Division_Erreur() As Double
    Dim dResultat As Double
    Exemple_Division_Erreur = 1 / 0 ' Erreur levée
End Function

Au cours de cette première section, nous avons étudié différentes instructions dédiées à la gestion des exceptions en VBA.

L'objet Err

L’objet Err est un objet accessible de manière globale pour gérer l’erreur survenue.

Les propriétés de cet objet peuvent être remplies de façon automatique ou manuelle. De même, ses méthodes peuvent être exécutées de manière automatique ou manuelle.

Propriétés

Vous avez peut-être remarqué dans la fenêtre d’erreur d’exécution ce numéro, ce message de description ou encore ce bouton "Aide" qui nous redirige vers la page de la documentation en ligne. En fait, ce sont des propriétés ! Celles-ci sont toutes accessibles en lecture et en écriture.

Number

La propriété Number (qui est celle par défaut de l’objet) permet d’identifier le type erreur. Dans certains cas, plusieurs types d’erreur peuvent survenir, cela est donc très utile d’avoir son identifiant.

Private Sub Exemple_Propriete_Number()
    On Error GoTo GestionErreur
    Sheets(1).ListObjects("Tableau2").DataBodyRange.Delete
    Exit Sub
GestionErreur:
    Select Case Err.Number: ' Équivalent de Select Case Err:
        Case 9
            Debug.Print ("Le tableau n'a pas été trouvé")
        Case 91
            Debug.Print ("Le tableau est déjà vide")
        Case Else:
            Debug.Print ("Une autre erreur est survenue : " & Err.Description)
    End Select
End Sub

La valeur zéro correspond à l’absence d’erreur.

Pour lever une erreur personnalisée pour une classe, il faut prendre la constante vbObjectError pour base :

Err.Raise Number:=vbObjectError + 1001, Source:="clsUneClasse"

Description

La description de l’erreur est contenue dans sa propriété Description.

HelpFile & HelpContext

Ce sont les valeurs qui permettent d’accéder à l’aide pour l’erreur rencontrée. La première correspond au fichier tandis que la seconde correspond à la rubrique.

Source

La Source correspond à là où a eu lieu l’erreur. Cela peut-être un nom de classe ou encore un identificateur tel que le nom du projet.

Méthodes

En plus de ces propriétés, l’objet Err possède différentes méthodes.

Lever une erreur

Il est possible de lever une erreur à travers la méthode Raise en fournissant à minima un numéro d’erreur, les autres valeurs sont alors automatiquement déduites lorsque c’est une erreur standard.

Voici un programme où nous levons une erreur standard et une erreur personnalisée :

Private Sub Exemple_Raise()
    On Error GoTo GestionErreur
    Err.Raise 5
    Err.Raise Number:=vbObjectError + 1001, Description:="Erreur survenue classe clsUneClasse", Source:="clsUneClasse"
    Exit Sub
GestionErreur:
    Debug.Print (Err.Number)
    Debug.Print (Err.Description)
    Debug.Print (Err.HelpFile)
    Debug.Print (Err.HelpContext)
    Debug.Print (Err.Source)
    Debug.Print ("________")
    ' 1ère erreur :
    ' 5
    ' Argument ou appel de procédure incorrect
    ' C:\Program Files\Common Files\Microsoft Shared\VBA\VBA7.1\1036\VbLR6.chm
    ' 1000005
    ' VBAProject
    
    ' 2ème erreur :
    ' -2147220503
    ' Erreur survenue classe clsUneClasse
    ' C:\Program Files\Common Files\Microsoft Shared\VBA\VBA7.1\1036\VbLR6.chm
    ' 1000440
    ' clsUneClasse
    Resume Next
End Sub

Notons que nous pouvons aussi créer une erreur à partir de celle en cours. De cette manière, nous pouvons gérer l’exception à différents niveaux, par exemple :

Private Function Exemple_Division_Avec_Gestion_Et_Nouvelle_Erreur(ByVal dNombre1 As Double, ByVal dNombre2 As Double) As Double
    On Error GoTo GestionErreur
    Exemple_Division_Avec_Propagation = dNombre1 / dNombre2
    Exit Function
GestionErreur:
    Debug.Print (Err.Number)
    Err.Raise Number:=Err.Number, Description:=Err.Description, HelpFile:=Err.HelpFile, HelpContext:=Err.HelpContext, Source:=Err.Source
End Function

Private Sub Main()
    On Error GoTo GestionErreur
    Exemple_Division_Avec_Gestion_Et_Nouvelle_Erreur 1, 0
    ' 11
    ' Division par zéro
    Exit Sub
GestionErreur:
    Debug.Print (Err.Description)
End Sub

Vider l’erreur

L’objet est vidé de manière implicite en fin de procédure, fonction ou propriété, ou encore lors d’une reprise d’exécution (Resume) après gestion de l’erreur dans une étiquette.

Il peut aussi être vidé de manière explicite à travers sa méthode Clear.

Au cours de cette seconde section, nous vu comment tirer profit de l’objet global Err.


C’est déjà la fin de ce billet.

Au cours de celui-ci, nous avons vu comment fonctionne la gestion des erreurs en VBA, avec les différentes instructions d’abord et l’objet Err ensuite.

Toutes les erreurs ne sont pas faites pour être gérées ainsi (et d’ailleurs ce serait difficile :-° ). Il faut parfois chercher à comprendre le dysfonctionnement à l’origine du bogue ou du problème via le débogage par exemple.

À bientôt !

Quelques ressources :

Merci à Sarkas pour ses retours.

1 commentaire

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte