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.

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.

Progager une erreur (1/2)

En VBA, les exceptions ne se propagent pas. Par défaut, si une erreur survient et qu’elle n’est pas gérée ou ignorée dans la procédure ou fonction ou propriété en cours, le programme s’arrête.

Il n’y a donc pas moyen de déléguer la gestion de l’erreur en dehors ? :-°

En fait si ! Il est possible de ruser et nous allons voir une première possibilité.

Indiquer une erreur via une valeur de retour ou par référence

Les fonctions retournent une valeur. Nous pourrions donc envisager de retourner un code spécifique ou bien une valeur booléenne pour indiquer si une erreur est survenue.

Private Function Exemple_Propagation_Erreur(ByVal sNomListObject As String) As Boolean
    On Error GoTo GestionErreur
    ActiveSheet.ListObjects(sNomListObject).DataBodyRange.Delete
    Exemple_Propagation_Code_Erreur = True
    Exit Function
GestionErreur:
    Exemple_Propagation_Code_Erreur = False ' Valeur d'erreur 
End Function

Bien que cette possibilité fonctionne, elle a quelques limites.

Déjà, cette gestion fonctionnerait bien avec les fonctions qui retournent une valeur, mais pas avec les procédures par exemple. Pour ces dernières, il serait envisageable de stocker l’information dans une variable globale ou encore de mettre le code erreur dans une valeur passée par référence. Autre inconvénient, cela peut détourner l’usage de retour de la fonction, et il faut alors envoyer le résultat à travers un paramètre par référence ou vice versa.

Private Function Exemple_Propagation_Erreur2(ByVal dNombre1 As Double, ByVal dNombre2 As Double, ByRef dResultat As Double) As Boolean
...
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 deux exemples 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

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.

Err.Clear ' Vide l'objet

Propager une erreur (2/2)

Maintenant que nous connaissons un peu mieux l’objet d’exception, nous pouvons aborder une seconde solution par rapport à la propagation d’erreur.

Lever une nouvelle erreur à partir de la première

Le principe est de lever une nouvelle erreur à partir de celle capturée, puis de gérer cette dernière dans le code appelant. De cette manière, la nouvelle erreur est immédiatement interceptée par le gestionnaire d’erreurs du code appelant.

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

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

Cette solution permet de résorber les défauts de la première vue un peu plus tôt.

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 :

Aucun 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