VBA
La gestion des erreurs
Recherche…
Éviter les conditions d'erreur
Lorsqu'une erreur d'exécution se produit, un code correct doit le gérer. La meilleure stratégie de gestion des erreurs consiste à écrire du code qui vérifie les conditions d'erreur et évite simplement d'exécuter du code entraînant une erreur d'exécution.
Un élément clé dans la réduction des erreurs d'exécution est la rédaction de petites procédures qui font une chose . Moins il y a de raisons pour lesquelles les procédures doivent échouer, plus le code dans son ensemble est facile à déboguer.
Éviter l'erreur d'exécution 91 - La variable Object ou With block n'est pas définie:
Cette erreur sera déclenchée lorsqu'un objet est utilisé avant que sa référence soit affectée. On peut avoir une procédure qui reçoit un paramètre d'objet:
Private Sub DoSomething(ByVal target As Worksheet)
Debug.Print target.Name
End Sub
Si la référence n'est pas affectée à une target
, le code ci-dessus générera une erreur facilement évitée en vérifiant si l'objet contient une référence d'objet réelle:
Private Sub DoSomething(ByVal target As Worksheet)
If target Is Nothing Then Exit Sub
Debug.Print target.Name
End Sub
Si la target
n'est pas attribuée à une référence, la référence non attribuée n'est jamais utilisée et aucune erreur ne se produit.
Cette méthode de sortie anticipée d'une procédure lorsqu'un ou plusieurs paramètres n'est pas valide s'appelle une clause de garde .
Éviter l'erreur d'exécution 9 - Indice en dehors des limites:
Cette erreur est déclenchée lorsqu'un tableau est accessible en dehors de ses limites.
Private Sub DoSomething(ByVal index As Integer)
Debug.Print ActiveWorkbook.Worksheets(index)
End Sub
Étant donné un index supérieur au nombre de feuilles de calcul dans ActiveWorkbook
, le code ci-dessus génère une erreur d'exécution. Une simple clause de garde peut éviter que:
Private Sub DoSomething(ByVal index As Integer)
If index > ActiveWorkbook.Worksheets.Count Or index <= 0 Then Exit Sub
Debug.Print ActiveWorkbook.Worksheets(index)
End Sub
La plupart des erreurs d'exécution peuvent être évitées en vérifiant soigneusement les valeurs que nous utilisons avant de les utiliser, et en les ramenant en conséquence à l'aide de simples clauses If
statement - in guard qui ne font aucune hypothèse et valident les paramètres d'une procédure. corps de procédures plus grandes.
En cas d'erreur
Même avec les clauses de garde , on ne peut pas toujours rendre compte de manière réaliste de toutes les conditions d'erreur possibles pouvant être soulevées dans le corps d'une procédure. L'instruction On Error GoTo
indique à VBA de passer à un libellé de ligne et de passer en "mode de traitement des erreurs" chaque fois qu'une erreur inattendue se produit au moment de l'exécution. Après avoir traité une erreur, le code peut reprendre une exécution "normale" à l'aide du mot-clé Resume
.
Les étiquettes de ligne indiquent des sous - routines : comme les sous-programmes proviennent du code BASIC hérité et utilisent des sauts GoTo
et GoSub
et des instructions Return
pour revenir à la routine "principale", il est assez facile d'écrire du code spaghetti difficile à suivre . Pour cette raison, il est préférable que:
- une procédure a un et un seul sous-programme de gestion des erreurs
- le sous-programme de gestion des erreurs ne s'exécute jamais que dans un état d'erreur
Cela signifie qu'une procédure qui gère ses erreurs doit être structurée comme suit:
Private Sub DoSomething()
On Error GoTo CleanFail
'procedure code here
CleanExit:
'cleanup code here
Exit Sub
CleanFail:
'error-handling code here
Resume CleanExit
End Sub
Stratégies de gestion des erreurs
Parfois, vous voulez gérer des erreurs différentes avec des actions différentes. Dans ce cas, vous inspecterez l'objet Err
global, qui contiendra des informations sur l'erreur qui a été générée - et agissez en conséquence:
CleanExit:
Exit Sub
CleanFail:
Select Case Err.Number
Case 9
MsgBox "Specified number doesn't exist. Please try again.", vbExclamation
Resume
Case 91
'woah there, this shouldn't be happening.
Stop 'execution will break here
Resume 'hit F8 to jump to the line that raised the error
Case Else
MsgBox "An unexpected error has occurred:" & vbNewLine & Err.Description, vbCritical
Resume CleanExit
End Select
End Sub
En règle générale, envisagez d'activer la gestion des erreurs pour l'ensemble de la sous-routine ou de la fonction, et gérez toutes les erreurs pouvant survenir dans sa portée. Si vous ne devez gérer que les erreurs dans la petite section du code - activez et désactivez la gestion des erreurs au même niveau:
Private Sub DoSomething(CheckValue as Long)
If CheckValue = 0 Then
On Error GoTo ErrorHandler ' turn error handling on
' code that may result in error
On Error GoTo 0 ' turn error handling off - same level
End If
CleanExit:
Exit Sub
ErrorHandler:
' error handling code here
' do not turn off error handling here
Resume
End Sub
Numéros de ligne
VBA prend en charge les numéros de ligne de style ancien (par exemple QBASIC). La propriété masquée Erl
peut être utilisée pour identifier le numéro de ligne qui a généré la dernière erreur. Si vous n'utilisez pas de numéros de ligne, Erl
ne retournera jamais que 0.
Sub DoSomething()
10 On Error GoTo 50
20 Debug.Print 42 / 0
30 Exit Sub
40
50 Debug.Print "Error raised on line " & Erl ' returns 20
End Sub
Si vous utilisez les numéros de ligne, mais pas toujours, alors Erl
retournera le dernier numéro de la ligne avant que l'instruction qui a soulevé l'erreur.
Sub DoSomething()
10 On Error GoTo 50
Debug.Print 42 / 0
30 Exit Sub
50 Debug.Print "Error raised on line " & Erl 'returns 10
End Sub
Gardez à l'esprit Erl
ne dispose que d'une précision Integer
et débordera silencieusement. Cela signifie que les numéros de ligne en dehors de la plage entière donneront des résultats incorrects:
Sub DoSomething()
99997 On Error GoTo 99999
99998 Debug.Print 42 / 0
99999
Debug.Print Erl 'Prints 34462
End Sub
Le numéro de ligne n'est pas tout à fait aussi pertinent que l'affirmation qui a provoqué l'erreur, et les lignes de numérotation deviennent rapidement fastidieuses et peu conviviales.
Reprendre le mot clé
Un sous-programme de gestion des erreurs sera:
- exécuter jusqu'à la fin de la procédure, auquel cas l'exécution reprend dans la procédure d'appel.
- ou, utilisez le mot-clé
Resume
pour reprendre l' exécution dans la même procédure.
Le mot-clé Resume
ne doit être utilisé que dans un sous-programme de gestion des erreurs, car si VBA rencontre Resume
sans être dans un état d'erreur, l'erreur d'exécution 20 "Reprendre sans erreur" est générée.
Un sous-programme de gestion des erreurs peut utiliser le mot-clé Resume
plusieurs manières:
-
Resume
utilisé seul, l'exécution continue sur l'instruction qui a provoqué l'erreur . Si l'erreur n'est pas réellement gérée avant cela, la même erreur sera à nouveau déclenchée et l'exécution pourra entrer dans une boucle infinie. -
Resume Next
continue l'exécution sur l'instruction immédiatement après l'instruction qui a provoqué l'erreur. Si l'erreur n'est pas réellement gérée avant cela, l'exécution peut continuer avec des données potentiellement invalides, ce qui peut entraîner des erreurs logiques et un comportement inattendu. -
Resume [line label]
continue l'exécution à l'étiquette de ligne spécifiée (ou au numéro de ligne, si vous utilisez des numéros de ligne hérités du style). Cela permet généralement d'exécuter du code de nettoyage avant de quitter la procédure proprement, par exemple en veillant à ce qu'une connexion à la base de données soit fermée avant de retourner à l'appelant.
On Error Resume Next
L'instruction On Error
elle-même peut utiliser le mot clé Resume
pour indiquer à l'environnement d'exécution VBA d' ignorer efficacement toutes les erreurs .
Si l'erreur n'est pas réellement gérée avant cela, l'exécution peut continuer avec des données potentiellement invalides, ce qui peut entraîner des erreurs logiques et un comportement inattendu .
L'emphase ci-dessus ne peut pas être assez soulignée. On Error Resume Next ignore effectivement toutes les erreurs et les place sous le tapis . Un programme qui explose avec une erreur d'exécution à cause d'une entrée invalide est un meilleur programme que celui qui continue à fonctionner avec des données inconnues / involontaires - que ce soit uniquement parce que le bogue est beaucoup plus facilement identifiable. On Error Resume Next
peut facilement masquer les bogues .
Le On Error
déclaration est scope procédure - c'est pourquoi il devrait normalement y avoir qu'un seul, unique comme On Error
instruction dans une procédure donnée.
Cependant, parfois, une condition d'erreur ne peut pas être complètement évitée, et passer à un sous-programme de gestion des erreurs uniquement pour Resume Next
ne semble pas correct. Dans ce cas spécifique, l'instruction known-to-event-fail peut être encapsulée entre deux instructions On Error
:
On Error Resume Next
[possibly-failing statement]
Err.Clear 'resets current error
On Error GoTo 0
L'instruction On Error GoTo 0
réinitialise la gestion des erreurs dans la procédure en cours, de sorte que toute instruction supplémentaire provoquant une erreur d'exécution ne serait pas gérée dans cette procédure et passait la pile d'appels jusqu'à ce qu'elle soit interceptée par un gestionnaire d'erreur actif. S'il n'y a pas de gestionnaire d'erreurs actif dans la pile d'appels, il sera traité comme une exception non gérée.
Public Sub Caller()
On Error GoTo Handler
Callee
Exit Sub
Handler:
Debug.Print "Error " & Err.Number & " in Caller."
End Sub
Public Sub Callee()
On Error GoTo Handler
Err.Raise 1 'This will be handled by the Callee handler.
On Error GoTo 0 'After this statement, errors are passed up the stack.
Err.Raise 2 'This will be handled by the Caller handler.
Exit Sub
Handler:
Debug.Print "Error " & Err.Number & " in Callee."
Resume Next
End Sub
Erreurs personnalisées
Souvent, lors de l'écriture d'une classe spécialisée, vous souhaiterez qu'il génère des erreurs spécifiques et que vous souhaitiez un moyen propre pour l'utilisateur / le code d'appel de gérer ces erreurs personnalisées. Pour ce faire, vous devez définir un type Enum
dédié:
Option Explicit
Public Enum FoobarError
Err_FooWasNotBarred = vbObjectError + 1024
Err_BarNotInitialized
Err_SomethingElseHappened
End Enum
L'utilisation de la vbObjectError
intégrée vbObjectError
garantit que les codes d'erreur personnalisés ne chevauchent pas les codes d'erreur réservés / existants. Seule la première valeur d'énumération doit être explicitement spécifiée, car la valeur sous-jacente de chaque membre Enum
est supérieure de 1
au membre précédent. La valeur sous-jacente de Err_BarNotInitialized
est donc implicitement vbObjectError + 1025
.
Augmenter vos propres erreurs d'exécution
Une erreur d'exécution peut être Err.Raise
l'aide de l'instruction Err.Raise
, afin que l'erreur Err_FooWasNotBarred
personnalisée puisse être Err_FooWasNotBarred
comme suit:
Err.Raise Err_FooWasNotBarred
La méthode Err.Raise
peut également prendre les paramètres Description
et Source
personnalisés. Pour cette raison, il est Err.Raise
définir également des constantes pour contenir la description de chaque erreur personnalisée:
Private Const Msg_FooWasNotBarred As String = "The foo was not barred."
Private Const Msg_BarNotInitialized As String = "The bar was not initialized."
Et ensuite, créez une méthode privée dédiée pour générer chaque erreur:
Private Sub OnFooWasNotBarredError(ByVal source As String)
Err.Raise Err_FooWasNotBarred, source, Msg_FooWasNotBarred
End Sub
Private Sub OnBarNotInitializedError(ByVal source As String)
Err.Raise Err_BarNotInitialized, source, Msg_BarNotInitialized
End Sub
L'implémentation de la classe peut alors simplement appeler ces procédures spécialisées pour générer l'erreur:
Public Sub DoSomething()
'raises the custom 'BarNotInitialized' error with "DoSomething" as the source:
If Me.Bar Is Nothing Then OnBarNotInitializedError "DoSomething"
'...
End Sub
Le code client peut alors gérer Err_BarNotInitialized
comme toute autre erreur, dans son propre sous-programme de gestion des erreurs.
Remarque: le mot clé Error
hérité peut également être utilisé à la place de Err.Raise
, mais il est obsolète / obsolète.