Bons modèles pour la gestion des erreurs VBA
Quels sont les bons modèles pour la gestion des erreurs dans VBA?
, En particulier, que dois-je faire dans cette situation:
... some code ...
... some code where an error might occur ...
... some code ...
... some other code where a different error might occur ...
... some other code ...
... some code that must always be run (like a finally block) ...
Je veux gérer les deux erreurs et reprendre l'exécution après le code où l'erreur peut se produire. En outre, le code finally à la fin doit toujours s'exécuter-quelles que soient les exceptions levées plus tôt. Comment puis-je obtenir ce résultat?
12 réponses
Gestion des erreurs dans VBA
-
On Error Goto
ErrorHandlerLabel -
Resume
(Next
| ErrorHandlerLabel) -
On Error Goto 0
(désactive le gestionnaire d'erreur actuel) -
Err
objet
Les propriétés de l'objet Err
sont normalement réinitialisées à zéro ou à une chaîne de longueur nulle dans la routine de gestion des erreurs, mais cela peut également être fait explicitement avec Err.Clear
.
Les erreurs dans la routine de gestion des erreurs sont la résiliation.
La plage 513-65535 est disponible pour les erreurs utilisateur.
Pour les erreurs de classe personnalisées, vous ajoutez vbObjectError
au numéro d'erreur.
Voir MME de la documentation sur Err.Raise
et la liste des numéros d'erreur.
Pour les membres d'interface non implémentés dans une classe dérivée , Vous devez utiliser la constante E_NOTIMPL = &H80004001
.
Option Explicit
Sub HandleError()
Dim a As Integer
On Error GoTo errMyErrorHandler
a = 7 / 0
On Error GoTo 0
Debug.Print "This line won't be executed."
DoCleanUp:
a = 0
Exit Sub
errMyErrorHandler:
MsgBox Err.Description, _
vbExclamation + vbOKCancel, _
"Error: " & CStr(Err.Number)
Resume DoCleanUp
End Sub
Sub RaiseAndHandleError()
On Error GoTo errMyErrorHandler
' The range 513-65535 is available for user errors.
' For class errors, you add vbObjectError to the error number.
Err.Raise vbObjectError + 513, "Module1::Test()", "My custom error."
On Error GoTo 0
Debug.Print "This line will be executed."
Exit Sub
errMyErrorHandler:
MsgBox Err.Description, _
vbExclamation + vbOKCancel, _
"Error: " & CStr(Err.Number)
Err.Clear
Resume Next
End Sub
Sub FailInErrorHandler()
Dim a As Integer
On Error GoTo errMyErrorHandler
a = 7 / 0
On Error GoTo 0
Debug.Print "This line won't be executed."
DoCleanUp:
a = 0
Exit Sub
errMyErrorHandler:
a = 7 / 0 ' <== Terminating error!
MsgBox Err.Description, _
vbExclamation + vbOKCancel, _
"Error: " & CStr(Err.Number)
Resume DoCleanUp
End Sub
Sub DontDoThis()
' Any error will go unnoticed!
On Error Resume Next
' Some complex code that fails here.
End Sub
Sub DoThisIfYouMust()
On Error Resume Next
' Some code that can fail but you don't care.
On Error GoTo 0
' More code here
End Sub
J'ajouterais aussi:
- l'objet global
Err
est le plus proche d'un objet d'exception - Vous pouvez effectivement "lancer une exception" avec
Err.Raise
Et juste pour le plaisir:
- {[2] } le diable est-il incarné et à éviter, car il cache silencieusement les erreurs
Donc vous pourriez faire quelque chose comme ça
Function Errorthingy(pParam)
On Error GoTo HandleErr
' your code here
ExitHere:
' your finally code
Exit Function
HandleErr:
Select Case Err.Number
' different error handling here'
Case Else
MsgBox "Error " & Err.Number & ": " & Err.Description, vbCritical, "ErrorThingy"
End Select
Resume ExitHere
End Function
Si vous voulez faire cuire dans des exceptions personnalisées. (par exemple ceux qui violent les règles métier) utilisez l'exemple ci-dessus mais utilisez le goto pour modifier le flux de la méthode si nécessaire.
Voici mon implémentation standard. J'aime que les étiquettes soient auto-descriptives.
Public Sub DoSomething()
On Error GoTo Catch ' Try
' normal code here
Exit Sub
Catch:
'error code: you can get the specific error by checking Err.Number
End Sub
Ou, avec un bloc Finally
:
Public Sub DoSomething()
On Error GoTo Catch ' Try
' normal code here
GoTo Finally
Catch:
'error code
Finally:
'cleanup code
End Sub
Le développement Excel professionnel a un très bon schéma de gestion des erreurs . Si vous allez passer du temps dans VBA, il vaut probablement la peine d'obtenir le livre. Il y a un certain nombre de domaines où VBA fait défaut et ce livre a de bonnes suggestions pour gérer ces domaines.
PED décrit deux méthodes de gestion des erreurs. Le principal est un système où toutes les procédures de point d'entrée sont des sous-procédures et toutes les autres procédures sont des fonctions qui renvoient des booléens.
Le procédure de point d'entrée utilisez sur les instructions D'erreur pour capturer les erreurs à peu près comme prévu. Les procédures de point de non-entrée renvoient True s'il n'y a pas eu d'erreurs et False s'il y a eu des erreurs. Les procédures de point de Non-entrée utilisent également sur L'erreur.
Les deux types de procédures utilisent une procédure centrale de gestion des erreurs pour garder l'erreur dans l'état et pour enregistrer l'erreur.
Voici un modèle assez décent.
Pour le débogage: Lorsqu'une erreur est déclenchée, appuyez sur Ctrl-Break (ou Ctrl-Pause), faites glisser le marqueur de rupture (ou tout ce qu'il s'appelle) vers la ligne de reprise, appuyez sur F8 et vous passerez à la ligne qui "a jeté" l'erreur.
L'ExitHandler est votre "enfin".
Sablier sera tué à chaque fois. Le texte de la barre d'état sera effacé à chaque fois.
Public Sub ErrorHandlerExample()
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
On Error GoTo ErrHandler
Dim varRetVal As Variant
Set dbs = CurrentDb
Set rst = dbs.OpenRecordset("SomeTable", dbOpenDynaset, dbSeeChanges + dbFailOnError)
Call DoCmd.Hourglass(True)
'Do something with the RecordSet and close it.
Call DoCmd.Hourglass(False)
ExitHandler:
Set rst = Nothing
Set dbs = Nothing
Exit Sub
ErrHandler:
Call DoCmd.Hourglass(False)
Call DoCmd.SetWarnings(True)
varRetVal = SysCmd(acSysCmdClearStatus)
Dim errX As DAO.Error
If Errors.Count > 1 Then
For Each errX In DAO.Errors
MsgBox "ODBC Error " & errX.Number & vbCrLf & errX.Description
Next errX
Else
MsgBox "VBA Error " & Err.Number & ": " & vbCrLf & Err.Description & vbCrLf & "In: Form_MainForm", vbCritical
End If
Resume ExitHandler
Resume
End Sub
Select Case Err.Number
Case 3326 'This Recordset is not updateable
'Do something about it. Or not...
Case Else
MsgBox "VBA Error " & Err.Number & ": " & vbCrLf & Err.Description & vbCrLf & "In: Form_MainForm", vbCritical
End Select
Il piège également les erreurs DAO et VBA. Vous pouvez mettre un cas Select dans l'erreur VBA section si vous voulez piéger des numéros D'erreur spécifiques.
Select Case Err.Number
Case 3326 'This Recordset is not updateable
'Do something about it. Or not...
Case Else
MsgBox "VBA Error " & Err.Number & ": " & vbCrLf & Err.Description & vbCrLf & "In: Form_MainForm", vbCritical
End Select
J'utilise un morceau de code que j'ai développé moi-même et c'est assez bon pour mes codes:
Au début de la fonction ou sub, je définis:
On error Goto ErrorCatcher:
Et puis, je gère les erreurs possibles
ErrorCatcher:
Select Case Err.Number
Case 0 'exit the code when no error was raised
On Error GoTo 0
Exit Function
Case 1 'Error on definition of object
'do stuff
Case... 'little description here
'do stuff
Case Else
Debug.Print "###ERROR"
Debug.Print " • Number :", Err.Number
Debug.Print " • Descrip :", Err.Description
Debug.Print " • Source :", Err.Source
Debug.Print " • HelpCont:", Err.HelpContext
Debug.Print " • LastDLL :", Err.LastDllError
Stop
Err.Clear
Resume
End Select
Mon point de vue personnel sur une déclaration faite plus tôt dans ce fil:
Et juste pour le plaisir:
Sur Erreur reprendre suivant est le diable incarné et à éviter, car il cache silencieusement les erreurs.
J'utilise le On Error Resume Next
sur les procédures où je ne veux pas qu'une erreur arrête mon travail et où toute instruction ne dépend pas du résultat des instructions précédentes.
Quand je fais cela, j'ajoute une variable globale debugModeOn
et je la mets à True
. Ensuite je l'utilise ceci façon:
If not debugModeOn Then On Error Resume Next
Quand je Livre mon travail, je mets la variable à false, cachant ainsi les erreurs uniquement à l'utilisateur et les montrant pendant les tests.
Aussi l'utiliser lorsque vous faites quelque chose qui peut échouer comme appeler la DataBodyRange D'un ListObject qui peut être vide:
On Error Resume Next
Sheet1.ListObjects(1).DataBodyRange.Delete
On Error Goto 0
Au Lieu de:
If Sheet1.ListObjects(1).ListRows.Count > 0 Then
Sheet1.ListObjects(1).DataBodyRange.Delete
End If
Ou vérification de l'existence d'un élément dans une collection:
On Error Resume Next
Err.Clear
Set auxiliarVar = collection(key)
' Check existence (if you try to retrieve a nonexistant key you get error number 5)
exists = (Err.Number <> 5)
Attention au piège à Éléphants:
Je n'ai vu aucune mention de cela dans cette discussion. [Accès 2010]
La façon dont ACCESS/VBA gère les erreurs dans les objets de classe est déterminée par une option configurable:
Éditeur de Code VBA > Outils > Options > Général > piégeage D'erreur:
Le code ci-dessous montre une alternative qui garantit qu'il n'y a qu'un seul point de sortie pour la sous/fonction.
sub something()
on error goto errHandler
' start of code
....
....
'end of code
' 1. not needed but signals to any other developer that looks at this
' code that you are skipping over the error handler...
' see point 1...
err.clear
errHandler:
if err.number <> 0 then
' error handling code
end if
end sub
La fonction Erl
relativement inconnue est également pertinente pour la discussion. Si vous avez des étiquettes numériques dans votre procédure de code, par exemple,
Sub AAA()
On Error Goto ErrorHandler
1000:
' code
1100:
' more code
1200:
' even more code that causes an error
1300:
' yet more code
9999: ' end of main part of procedure
ErrorHandler:
If Err.Number <> 0 Then
Debug.Print "Error: " + CStr(Err.Number), Err.Descrption, _
"Last Successful Line: " + CStr(Erl)
End If
End Sub
La fonction Erl
renvoie l'étiquette de ligne numérique la plus récente rencontrée. Dans l'exemple ci-dessus, si une erreur d'exécution se produit après label 1200:
mais avant 1300:
, la fonction Erl
retournera 1200
, car il s'agit du label de ligne Le plus récent rencontré avec succès. Je trouve que c'est une bonne pratique à mettre une étiquette de ligne immédiatement au-dessus de votre bloc de gestion des erreurs. J'utilise typiquement 9999
pour indiquer que la partie principale de la procuedure a couru à sa conculsion attendue.
NOTES:
Les étiquettes de ligne doivent être des entiers positifs - une étiquette comme
MadeItHere:
n'est pas reconnue parErl
.Les étiquettes de ligne sont complètement indépendantes des numéros de ligne réels d'un
VBIDE CodeModule
. Vous pouvez utiliser tous les nombres positifs que vous voulez, dans l'ordre que vous voulez. Dans l'exemple ci-dessus, il n'y a que 25 lignes de code, mais les numéros d'étiquette de ligne commencent à1000
. Il n'y a pas de relation entre les numéros de ligne de l'éditeur et les numéros d'étiquette de ligne utilisés avecErl
.Les numéros D'étiquette de ligne n'ont pas besoin d'être dans un ordre particulier, bien que s'ils ne sont pas dans l'ordre ascendant et descendant, l'efficacité et le bénéfice de
Erl
sont grandement diminués, mais {[2] } signalera toujours le nombre correct.Les étiquettes de Ligne, sont spécifiques à la procédure dans laquelle ils apparaissent. Si la procédure
ProcA
appelle procédureProcB
et une erreur se produit dans {[17] } qui renvoie le contrôle àProcA
,Erl
(dansProcA
) retournera le numéro d'étiquette de ligne Le plus récent encounterd dansProcA
avant d'appelerProcB
. DepuisProcA
, Vous ne pouvez pas obtenir les numéros d'étiquette de ligne qui peuvent apparaître dansProcB
.
Faites attention lorsque vous placez des étiquettes de numéro de ligne dans une boucle. Par exemple,
For X = 1 To 100
500:
' some code that causes an error
600:
Next X
Si le code suivant l'étiquette de ligne 500
mais avant 600
provoque une erreur, et cette erreur se produit sur la 20ème itération de la boucle, {[2] } retournera 500
, même si 600
a été rencontré avec succès dans les 19 interactions précédentes de la boucle.
Le placement correct des étiquettes de ligne dans la procédure est essentiel à l'utilisation de la fonction Erl
pour obtenir des informations vraiment significatives.
Il y a n'importe quel nombre d'utilitaires gratuits sur le net qui inséreront automatiquement une étiquette de ligne numérique dans une procédure, de sorte que vous avez des informations d'erreur précises lors du développement et du débogage, et puis retirez ces étiquettes une fois le code mis en ligne.
Si votre code affiche des informations d'erreur à l'utilisateur final si une erreur inattendue se produit, fournir la valeur de Erl
dans cette information peut rendre la recherche et la résolution du problème beaucoup plus simple que si la valeur de Erl
n'est pas signalée.
Je trouve que ce qui suit fonctionne le mieux, appelé l'approche centrale de gestion des erreurs.
Avantages
, Vous avez 2 modes de fonctionnement de votre application: Debug et Production. Dans le mode Debug , le code s'arrêtera à toute erreur inattendue et vous permettra de déboguer facilement en sautant à la ligne où il s'est produit en appuyant deux fois sur F8. Dans le mode Production , un message d'erreur significatif sera affiché à l'utilisateur.
Vous pouvez lancer des erreurs intentionnelles comme celle-ci, qui arrêteront l'exécution du code avec un message à l'Utilisateur:
Err.Raise vbObjectError, gsNO_DEBUG, "Some meaningful error message to the user"
Err.Raise vbObjectError, gsUSER_MESSAGE, "Some meaningful non-error message to the user"
'Or to exit in the middle of a call stack without a message:
Err.Raise vbObjectError, gsSILENT
Mise en œuvre
Vous devez "envelopper" tous les sous-Programmes et fonctions avec une quantité significative de code avec les en-têtes et pieds de page suivants, en vous assurant de spécifier ehCallTypeEntryPoint
dans tous vos points d'entrée. Notez également la constante msModule
, qui doit être placée dans tous les modules.
Option Explicit
Const msModule As String = "<Your Module Name>"
' This is an entry point
Public Sub AnEntryPoint()
Const sSOURCE As String = "AnEntryPoint"
On Error GoTo ErrorHandler
'Your code
ErrorExit:
Exit Sub
ErrorHandler:
If CentralErrorHandler(Err, ThisWorkbook, msModule, sSOURCE, ehCallTypeEntryPoint) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Sub
' This is any other subroutine or function that isn't an entry point
Sub AnyOtherSub()
Const sSOURCE As String = "AnyOtherSub"
On Error GoTo ErrorHandler
'Your code
ErrorExit:
Exit Sub
ErrorHandler:
If CentralErrorHandler(Err, ThisWorkbook, msModule, sSOURCE) Then
Stop
Resume
Else
Resume ErrorExit
End If
End Sub
Le contenu du module central error handler est le suivant:
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Comments: Error handler code.
'
' Run SetDebugMode True to use debug mode (Dev mode)
' It will be False by default (Production mode)
'
' Author: Igor Popov
' Date: 13 Feb 2014
' Licence: MIT
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Option Explicit
Option Private Module
Private Const msModule As String = "MErrorHandler"
Public Const gsAPP_NAME As String = "<You Application Name>"
Public Const gsSILENT As String = "UserCancel" 'A silent error is when the user aborts an action, no message should be displayed
Public Const gsNO_DEBUG As String = "NoDebug" 'This type of error will display a specific message to the user in situation of an expected (provided-for) error.
Public Const gsUSER_MESSAGE As String = "UserMessage" 'Use this type of error to display an information message to the user
Private Const msDEBUG_MODE_COMPANY = "<Your Company>"
Private Const msDEBUG_MODE_SECTION = "<Your Team>"
Private Const msDEBUG_MODE_VALUE = "DEBUG_MODE"
Public Enum ECallType
ehCallTypeRegular = 0
ehCallTypeEntryPoint
End Enum
Public Function DebugMode() As Boolean
DebugMode = CBool(GetSetting(msDEBUG_MODE_COMPANY, msDEBUG_MODE_SECTION, msDEBUG_MODE_VALUE, 0))
End Function
Public Sub SetDebugMode(Optional bMode As Boolean = True)
SaveSetting msDEBUG_MODE_COMPANY, msDEBUG_MODE_SECTION, msDEBUG_MODE_VALUE, IIf(bMode, 1, 0)
End Sub
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Comments: The central error handler for all functions
' Displays errors to the user at the entry point level, or, if we're below the entry point, rethrows it upwards until the entry point is reached
'
' Returns True to stop and debug unexpected errors in debug mode.
'
' The function can be enhanced to log errors.
'
' Date Developer TDID Comment
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' 13 Feb 2014 Igor Popov Created
Public Function CentralErrorHandler(ErrObj As ErrObject, Wbk As Workbook, ByVal sModule As String, ByVal sSOURCE As String, _
Optional enCallType As ECallType = ehCallTypeRegular, Optional ByVal bRethrowError As Boolean = True) As Boolean
Static ssModule As String, ssSource As String
If Len(ssModule) = 0 And Len(ssSource) = 0 Then
'Remember the module and the source of the first call to CentralErrorHandler
ssModule = sModule
ssSource = sSOURCE
End If
CentralErrorHandler = DebugMode And ErrObj.Source <> gsNO_DEBUG And ErrObj.Source <> gsUSER_MESSAGE And ErrObj.Source <> gsSILENT
If CentralErrorHandler Then
'If it's an unexpected error and we're going to stop in the debug mode, just write the error message to the immediate window for debugging
Debug.Print "#Err: " & Err.Description
ElseIf enCallType = ehCallTypeEntryPoint Then
'If we have reached the entry point and it's not a silent error, display the message to the user in an error box
If ErrObj.Source <> gsSILENT Then
Dim sMsg As String: sMsg = ErrObj.Description
If ErrObj.Source <> gsNO_DEBUG And ErrObj.Source <> gsUSER_MESSAGE Then sMsg = "Unexpected VBA error in workbook '" & Wbk.Name & "', module '" & ssModule & "', call '" & ssSource & "':" & vbCrLf & vbCrLf & sMsg
MsgBox sMsg, vbOKOnly + IIf(ErrObj.Source = gsUSER_MESSAGE, vbInformation, vbCritical), gsAPP_NAME
End If
ElseIf bRethrowError Then
'Rethrow the error to the next level up if bRethrowError is True (by Default).
'Otherwise, do nothing as the calling function must be having special logic for handling errors.
Err.Raise ErrObj.Number, ErrObj.Source, ErrObj.Description
End If
End Function
À définissez-vous dans le mode Debug , exécutez ce qui suit dans la fenêtre immédiate:
SetDebugMode True