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
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 :
- La documentation sur l’instruction On Error
- La documentation sur l’instruction Resume
- La documentation sur l’objet Err
Merci à Sarkas pour ses retours.