VBA: Gestion des erreurs

Tout développeur sait que quoi qu’il fasse, son code peut tomber en erreur. Lorsqu’il s’agit de votre propre développement et qu’il est uniquement destiné à votre usage personnel, pas de problème, lorsque l’erreur survient, vous la traitez. Par contre, si votre développement est destiné à être diffusé, c’est un utilisateur lambda qui verra l’erreur, sera t’il en mesure de vous fournir toutes les informations nécessaires à sa correction? La réponse est fréquemment non. C’est pour cette raison qu’il est important de traiter les erreurs et de les afficher de façon à ce que la simple lecture du message d’erreur vous fournisse toutes les indications nécessaires à sa correction.

La méthode décrite dans cet article est une méthode que j’ai utilisé à de nombreuses reprises dans l’industrie et qui c’est avérée des plus efficace.

Références VBA à connaitre

Tout d’abord, voici un petit résumé des instructions à connaitre. Pour chaque instruction, vous pourrez trouver d’avantage d’information sur le site de Microsoft.

InstructionDescription
label:Positionné en début de ligne, un identifiant suivit de « : » étiquette une position dans le code
On Error GoTo labelEn cas d’erreur, saute à l’instruction suivant l’étiquette label
On Error GoTo 0Annule tout type de traitement d’erreur et revient à une gestion standard.
ResumeSuite à une erreur, ré-exécute le code ayant provoqué l’erreur comme si rien ne s’était passé. Cette instruction est généralement utilisée à la fin d’un bloc de gestion d’erreur, bloc appelé via un On Error Goto.
Resume NextComme Resume, sauf que c’est l’instruction suivant celle qui à provoquée l’erreur qui est exécutée.
On Error Resume NextEn cas d’erreur, ne s’arrête pas et exécute l’instruction suivante.
Exit Sub
Exit Function
Instruction signalant une fin de procédure ou fonction. Est généralement placée avant les blocs de traitement d’erreurs situés en fin de code.

En cas d’erreur, l’objet Err est automatiquement instancié. Il contiendra toutes les informations sur la nature de l’erreur observée.

Gestion des erreurs inattendues

Pour intercepter toutes les erreurs dans un code VBA, il vous faut utiliser des instruction On Error. Vous pouvez vous limiter aux seules procédures et fonctions directement appelées par les utilisateurs, mais si une erreur survient dans une sous procédure, vous n’en saurez rien car l’objet Err ne renvoie pas la pile d’appel des fonctions. Pour connaitre la portion de code à l’origine de l’erreur, il faut donc gérer cette pile d’appel à la place du système et traiter les erreurs dans chaque procédure et fonction de votre projet.

Private Sub maProcedure()
    
    On Error GoTo GestionErr
    
    'mon code
    
    Exit Sub
GestionErr:
    If err.Source <> "" Then
        err.Raise err.Number, "monModule.maProcedure/" & err.Source, err.Description
    Else
        err.Raise err.Number, "monModule.maProcedure", err.Description
    End If
End Sub

Dans l’exemple ci-dessus, le code de la procédure est entouré par une instruction de gestion des erreur et une instruction de fin de procédure, ainsi si aucune erreur n’est déclenchée, le bloc débutant au label GestionErr n’est pas exécuté.

En cas d’erreur, le code présent dans le bloc de gestion des erreurs permet d’empiler dans la propriété Source de l’objet Err les noms de la procédure et du module en cours d’exécution. La procédure Raise, quand à elle, propage l’erreur vers la procédure ou fonction appelante.

En procédant ainsi dans chaque procédure ou fonction du projet, nous aurons la pile d’appel des procédures et fonctions correspondant à l’erreur détectée.

Affichage des erreurs

Du fait que nous prenons en charge la gestion des erreurs, il nous faut également en afficher le contenu à la place du système afin de permettre un affichage correcte de la pile d’appel.

Sub procedureAppeleeParLutilisateur()
    On Error GoTo GestionErr
    
    'mon code
    
    Exit Sub
GestionErr:
    MsgBox "ERROR NUMBER: " & err.Number & vbCrLf & vbCrLf & _
           err.Description & vbCrLf & vbCrLf & _
           "Source : " & "monModule.procedureAppeleeParLutilisateur/" & err.Source, vbCritical + vbOKOnly, "ERROR"
End Sub

Pour toutes les procédures et fonctions exécutables par l’utilisateur, il convient donc de modifier le gestionnaire d’erreur comme indiqué ci-dessus. Ainsi aucune erreur n’est réellement traitée par le système, mais nous avons une information claire et détaillée du problème rencontré.

NB: vous pouvez également améliorer l’ergonomie en utilisant un formulaire personnalisé pouvant également par exemple vous transmettre l’erreur via le web.

Bloc de fin de procédure

Nous venons de voir comment faire l’équivalent d’une instruction try … catch tel qu’en C, mais que faire si nous avons besoin du finaly?

L’instruction finaly en C permet l’exécution d’un bloc de code qu’il y ait eu une erreur ou non. Ce bloc est généralement utilisé pour faire du nettoyage. Nous avons également ce besoin en VBA pour repositionner des propriétés globales que nous aurions modifié lors de nos traitements ou pour nettoyer la mémoire (set monObj = Nothing).

Le plus simple est tout bonnement de recopier les lignes de code dans le gestionnaire d’erreur.

Sub maProcedure()
    Dim oldCalculation As Integer
    Dim oldScreenUpdating As Boolean
    Dim oldEnableEvents As Boolean
    
    On Error GoTo GestionErr

    oldCalculation = Application.Calculation
    oldScreenUpdating = Application.ScreenUpdating
    oldEnableEvents = Application.EnableEvents

    ...
    
    If oldCalculation <> xlCalculationManual Then Application.Calculation = oldCalculation
    If Not Application.ScreenUpdating = True And oldScreenUpdating Then Application.ScreenUpdating = True
    If Not Application.EnableEvents = True And oldEnableEvents Then Application.EnableEvents = True
    
    Exit Sub
GestionErr:
    If oldCalculation <> xlCalculationManual Then Application.Calculation = oldCalculation
    If Not Application.ScreenUpdating = True And oldScreenUpdating Then Application.ScreenUpdating = True
    If Not Application.EnableEvents = True And oldEnableEvents Then Application.EnableEvents = True
    
    If err.Source <> "" Then
        err.Raise err.Number, "monModule.maProcedure/" & err.Source, err.Description
    Else
        err.Raise err.Number, "monModule.maProcedure", err.Description
    End If
End Sub

Dans l’exemple ci-dessus, nous gelons Excel pour améliorer la rapidité du traitement, il convient donc de tout remettre en ordre avant de sortir de la procédure, même en cas d’erreur, sans quoi nous bloquerions Excel.

Gestion des erreurs prévisibles

Nous avons parlé des erreurs imprévisibles (les fameux bugs), mais que faire des erreurs que nous avons déjà identifiées et que nous ne pouvons pas traiter par de simples tests en amont (par exemple, une division par zéro peut être testée, mais il sera difficile de tester l’existence d’une méthode sur un objet externe).

Dans un tel cas, il faut encore une fois utiliser l’instruction On Error, soit avec un second bloc de gestion des erreurs se terminant par un Resume ou Resume Next

Private Sub maProcedure()
    
    On Error GoTo GestionErr
    
    ...
    
    On Error GoTo ErreurAttendue
    'mon code
    Err.Clear
    On Error GoTo GestionErr
    
    ...
    
    Exit Sub
ErreurAttendue:
    'gestion de l'erreur
    Resume Next
    
GestionErr:
    If Err.Source <> "" Then
        Err.Raise Err.Number, "monModule.maProcedure/" & Err.Source, Err.Description
    Else
        Err.Raise Err.Number, "monModule.maProcedure", Err.Description
    End If
End Sub

, soit en utilisant On Error Resume Next et en testant l’objet Err

Private Sub maProcedure()
    
    On Error GoTo GestionErr
    
    ...
    
    On Error Resume Next
    'mon code
    If Err.Number > 0 Then
        'gestion de l'erreur
    End If
    Err.Clear
    On Error GoTo GestionErr
    
    ...
    
    Exit Sub
GestionErr:
    If Err.Source <> "" Then
        Err.Raise Err.Number, "monModule.maProcedure/" & Err.Source, Err.Description
    Else
        Err.Raise Err.Number, "monModule.maProcedure", Err.Description
    End If
End Sub

Dans les deux cas il convient de nettoyer l’objet Err afin de pouvoir poursuivre l’exécution, et de rebrancher le gestionnaire d’erreur sur notre gestionnaire principal.

Cette méthode peut également être utilisée pour désactiver tout bonnement le traitement des erreurs. Dans ce cas, aucun bloc de traitement n’est nécessaire, il suffit juste en cas d’erreur de l’annuler avec Err.Clear.

    On Error Resume Next
    'mon code
    If Err.Number > 0 Then Err.Clear
    On Error GoTo GestionErr

Les risques du Resume Next

L’instruction On Error Resume Next utilisée conjointement à un test de l’objet Err est très pratique pour gérer les erreurs prévisibles « sans sortir la grosse artillerie ». Il faut toutefois y faire attention car il m’est arrivé que cela provoque une boucle infinie que le système ne sait pas gérée.

Dim i as integer
Dim monTab() as string

On Error Resume Next
For i = UBound(monTab) To 1 Step -1
  ...
Next i

Dans le code ci-dessus, nous signalons qu’en cas d’erreur nous souhaitons passer à l’instruction suivante, une erreur se déclenche à l’intérieur de l’instruction For, ubound renvoie une erreur. Le système passe à l’instruction suivante qui se trouve dans le bloc For et arrivé à l’instruction Next, nous bouclons sur l’instruction For avec son erreur. Le système ne peut pas s’arrêter car nous gérons les erreurs à sa place.

Il faut donc être très vigilant sur les Resume Next. Il faut à mon avis les utiliser le moins possible et sur des blocs de codes très courts.

Le mot de la fin

J’espère que cet article a pu vous être utile. Si vous avez des suggestions ou des commentaires, merci de compléter le formulaire ci-dessous, je les regarderai avec plaisir.

Commenter cet article