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?

66
demandé sur Atif Aziz 2009-06-24 16:17:15

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
91
répondu guillermooo 2018-02-19 15:33:20

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
33
répondu Joel Goodwin 2009-06-24 12:48:04

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.

16
répondu Johnno Nolan 2012-09-20 08:03:10

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
11
répondu LimaNightHawk 2014-06-12 14:54:30

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.

3
répondu Dick Kusleika 2015-06-16 15:48:47

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
3
répondu whistle britches 2015-10-30 15:34:13

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
2
répondu Thiago Cardoso 2014-10-30 13:05:36

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)
1
répondu Jordi 2014-12-17 11:46:39

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:

entrez la description de l'image ici

1
répondu JoeRobbins 2016-01-14 13:47:00

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
1
répondu nickD 2017-10-12 03:16:08

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 par Erl.

  • 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 avec Erl.

  • 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édure ProcB et une erreur se produit dans {[17] } qui renvoie le contrôle à ProcA, Erl (dans ProcA) retournera le numéro d'étiquette de ligne Le plus récent encounterd dans ProcA avant d'appeler ProcB. Depuis ProcA, Vous ne pouvez pas obtenir les numéros d'étiquette de ligne qui peuvent apparaître dans ProcB.

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.

1
répondu Chip Pearson 2017-12-05 06:16:49

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
1
répondu igorsp7 2018-06-18 14:15:07