web-dev-qa-db-fra.com

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 être exécuté, quelles que soient les exceptions lancées précédemment. Comment puis-je atteindre ce résultat?

70
jwoolard

Traitement des erreurs dans VBA


  • On Error Goto ErrorHandlerLabel
  • Resume (Next | ErrorHandlerLabel )
  • On Error Goto 0 _ (désactive le gestionnaire d’erreurs 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 traitement des erreurs, mais cela peut également être fait explicitement avec Err.Clear.

Les erreurs dans la routine de traitement des erreurs se terminent.

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 la documentation MS sur Err.Raise et le 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
97
guillermooo

J'ajouterais aussi:

  • L’objet global Err est le plus proche d’un objet exception.
  • Vous pouvez effectivement "lancer une exception" avec Err.Raise

Et juste pour le plaisir:

  • On Error Resume Next est le diable incarné et à éviter, car il cache silencieusement les erreurs
35
Joel Goodwin

Pour que tu puisses 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 souhaitez utiliser des exceptions personnalisées. (par exemple, ceux qui enfreignent les règles de gestion), utilisez l'exemple ci-dessus, mais utilisez la commande goto pour modifier le flux de la méthode si nécessaire.

17
Johnno Nolan

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
LimaNightHawk

Développement professionnel Excel a un très bon schéma de traitement des erreurs . Si vous allez passer du temps en VBA, cela vaut probablement la peine de vous procurer le livre. VBA manque dans un certain nombre de domaines et ce livre propose de bonnes suggestions pour la gestion de ces domaines.

PED décrit deux méthodes de traitement 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 retournent des booléens.

La procédure de point d’entrée utilise les instructions On Error pour capturer les erreurs à peu près telles qu’elles ont été conçues. Les procédures de point de non-entrée renvoient True s'il n'y a pas d'erreur et False s'il y en a. Les procédures de point non-entrée utilisent également On Error.

Les deux types de procédures utilisent une procédure centrale de traitement des erreurs pour conserver l'erreur et la consigner.

4
Dick Kusleika

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
3
nickD

J'utilise un morceau de code que j'ai développé moi-même et c'est très bon pour mes codes:

Au début de la fonction ou sous, 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
3
Thiago Cardoso

La fonction relativement inconnue Erl 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 le dernier libellé de ligne numéroté. 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, puisque c’est l’étiquette de ligne la plus récemment rencontrée. Je trouve bon de placer une étiquette de ligne immédiatement au-dessus de votre bloc de traitement des erreurs. J'utilise typiquement 9999 pour indiquer que la partie principale de la procédure a abouti à sa conculsion attendue.

REMARQUES:

  • Les étiquettes de ligne DOIVENT être des entiers positifs - une étiquette comme MadeItHere: _ n'est pas reconnu par Erl.

  • Les libellés de ligne n'ont aucun rapport avec les numéros de ligne réels d'un VBIDE CodeModule. Vous pouvez utiliser tous les nombres positifs que vous voulez, dans l'ordre de votre choix. Dans l'exemple ci-dessus, il n'y a qu'une vingtaine de lignes de code, mais les numéros d'étiquette de ligne commencent par 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 ne doivent pas nécessairement figurer dans un ordre particulier. Toutefois, s’ils ne sont pas en ordre croissant, l’efficacité et les avantages de Erl sont considérablement diminués, mais Erl indique toujours le numéro correct.

  • Les étiquettes de ligne sont spécifiques à la procédure dans laquelle elles apparaissent. Si la procédure ProcA appelle la procédure ProcB et qu’une erreur se produit dans ProcB, elle renvoie le contrôle à ProcA, Erl (dans ProcA) renverra le dernier numéro d'étiquette de ligne de rencontred dans ProcA avant d'appeler ProcB. Dans ProcA, vous ne pouvez pas obtenir les numéros d'étiquette de ligne pouvant apparaître dans ProcB.

Faites attention lorsque vous insérez 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 survient à la 20ème itération de la boucle, Erl retournera 500, même si 600 a été rencontré avec succès lors des 19 dernières interactions de la boucle.

Le placement correct des étiquettes de ligne dans la procédure est essentiel pour utiliser la fonction Erl afin d’obtenir des informations vraiment utiles.

Il existe un grand nombre d'utilisations gratuites sur le réseau qui insèrent automatiquement une étiquette de ligne numérique dans une procédure. Vous disposez ainsi d'informations d'erreur détaillées lors du développement et du débogage, puis supprimez ces étiquettes une fois le code activé.

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 VASTLY plus simples que si la valeur de Erl ne l'est pas signalé.

3
Chip Pearson

Voici un modèle assez décent.

Pour le débogage: quand une erreur est levée, appuyez sur Ctrl-Pause (ou Ctrl-Pause), faites glisser le marqueur de rupture (ou le nom de votre choix) vers la ligne de reprise, appuyez sur F8 et vous passerez à la ligne qui "jeta" l'erreur.

ExitHandler est votre "Enfin".

Le 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 intercepte également les erreurs DAO et VBA. Vous pouvez placer un cas de sélection dans la section d'erreur VBA si vous souhaitez intercepter des numéros Err 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
whistle britches

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 CLASS est déterminée par une option configurable:

Editeur de code VBA> Outils> Options> Général> Détection d’erreurs:

enter image description here

2
JoeRobbins

Je trouve que les solutions suivantes fonctionnent le mieux, appelée approche de traitement des erreurs centralisée.

Avantages

Vous avez 2 modes d'exécution de votre application: Debug et Production. Dans le mode Debug, le code s’arrêtera à toute erreur imprévue et vous permettra de déboguer facilement en sautant sur la ligne où elle s’est produite en appuyant deux fois sur F8. En 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

La mise en oeuvre

Vous devez "encapsuler" tous les sous-programmes et fonctions avec une quantité importante de code avec les en-têtes et les pieds de page suivants, en veillant à spécifier ehCallTypeEntryPoint dans tous vos points d'entrée. Notez également la constante msModule qui doit être mise 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 de gestion des erreurs 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

Pour vous définir en mode Debug, exécutez ce qui suit dans la fenêtre Immediate:

SetDebugMode True
2
igorsp7

Mon point de vue personnel sur une déclaration faite précédemment dans ce fil de discussion:

Et juste pour le plaisir:

On Error Resume Next est le diable incarné et à éviter car il cache les erreurs en silence.

J'utilise le On Error Resume Next _ sur des procédures pour lesquelles je ne souhaite pas qu'une erreur arrête mon travail et où une déclaration ne dépende pas du résultat des déclarations précédentes.

Quand je le fais, j'ajoute une variable globale debugModeOn et la fixe à True. Ensuite, je l'utilise de cette façon:

If not debugModeOn Then On Error Resume Next

Lorsque je transmets mon travail, je mets la variable à false, masquant ainsi les erreurs uniquement à l'utilisateur et les affichant lors des tests.

Vous pouvez également l'utiliser lorsque vous rencontrez un problème qui pourrait échouer, par exemple appeler le DataBodyRange d'un ListObject pouvant ê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érifier l'existence d'un article 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
Jordi