Versuchen Sie die catch -Anweisung in VBA mit den standardmäßigen Anweisungen zur VBA-Fehlerbehandlung

100019
HarveyFrench

Der folgende Code ist ziemlich selbsterklärend: Kopieren Sie einfach alles, fügen Sie es in ein Modul ein und führen Sie es aus. Es enthält einige Anwendungsfälle und viele erklärende Kommentare im Text. (Es funktioniert, aber ich bin daran interessiert zu wissen, was andere Leute daraus machen und für Vorschläge, die Sie gerne machen könnten.)

Die wichtigsten zu realisierenden Fakten sind:

  1. Wenn Sie bei Fehler goto Label1 verwenden, geht die Prozedur in den Status "Ich verarbeite einen Fehler", da eine Ausnahme ausgelöst wurde. Wenn in diesem Status eine andere "On Error Goto" -Label2-Anweisung ausgeführt wird, wird sie NICHT zu Label2, sondern zu einem Fehler, der an den Code übergeben wird, der die Prozedur aufgerufen hat.

  2. Sie können eine Prozedur im Status "Ich behandele einen Fehler" anhalten, indem Sie die Ausnahme löschen (Err auf nichts setzen, damit die Eigenschaft err.number 0 wird), indem Sie verwenden

    Err.clear
    or
    
    On Error Goto -1    ' Which I think is less clear!
    

(HINWEIS, der On Error Goto 0 sich von den oben genannten unterscheidet)

Es ist auch wichtig zu beachten, dass Err.Clear es auf Null zurücksetzt, aber eigentlich gleichbedeutend ist mit:

On Error Goto -1 
On Error Goto 0

dh Err.Clear entfernt einen "On Error Goto", der aktuell vorhanden ist. Daher ist es am besten, Folgendes zu verwenden:

On Error Goto -1   

wie mit Err.clear Sie müssen oft schreiben

Err.Clear
On Error Goto MyErrorHandlerLabel

Ich verwende die obigen Techniken mit verschiedenen Labels, um die manchmal nützliche Funktionalität von Visual Basic TRY CATCH-Blöcken zu simulieren, die meiner Meinung nach ihren Platz beim Schreiben von lesbarem Code hat.

Zugegebenermaßen erzeugt diese Technik ein paar Zeilen mehr Code als eine nette VB-Try-Catch-Anweisung, aber es ist nicht zu unordentlich und ziemlich leicht, den Kopf herumzureißen.

PS. Interessant könnte auch die Prozedur ManageErrSource sein, mit der die Err.Source-Eigenschaft die Prozedur speichert, in der der Fehler aufgetreten ist.

Option Compare Database
Option Explicit

Dim RememberErrNumber As Long
Dim RememberErrDescription As String
Dim RememberErrSource As String
Dim RememberErrLine  As Integer

Private Sub RememberThenClearTheErrorObject()

    On Error Resume Next

    ' For demo purposes
    Debug.Print "ERROR RAISED"
    Debug.Print Err.Number
    Debug.Print Err.Description
    Debug.Print Err.Source
    Debug.Print " "


    ' This function has to be declared in the same scope as the variables it refers to
    RememberErrNumber = Err.Number
    RememberErrDescription = Err.Description
    RememberErrSource = Err.Source
    RememberErrLine = Erl()

    ' Note that the next line will reset the error object to 0, the variables above are used to remember the values
    ' so that the same error can be re-raised
    Err.Clear

    ' Err.Clear  is used to clear the raised exception and set the err object to nothing (ie err.number to 0)
    ' If Err.Clear has not be used, then the next "On Error GoTo ALabel" that is used in this or the procedure that called it
    ' will actually NOT pass execution to the ALabel: label BUT the error is paseed to the procedure that called this procedure.
    ' Using Err.Clear (or "On Error GoTo -1 ")  gets around this and facilitates the whole TRY CATCH block scenario I am using there.


    ' For demo purposes
    Debug.Print "ERROR RAISED is now 0 "
    Debug.Print Err.Number
    Debug.Print Err.Description
    Debug.Print Err.Source
    Debug.Print " "

    ' For demo purposes
    Debug.Print "REMEMBERED AS"
    Debug.Print RememberErrNumber
    Debug.Print RememberErrDescription
    Debug.Print RememberErrSource
    Debug.Print " "

End Sub

Private Sub ClearRememberedErrorObjectValues()

    ' This function has to be declared in the same scope as the variables it refers to
    RememberErrNumber = 0
    RememberErrDescription = ""
    RememberErrSource = ""
    RememberErrLine = 0

End Sub




Sub ExampleOfTryCatchBlockInVBA()

    On Error GoTo HandleError


    ' -----------------------------------------------------
    ' SubProcedure1 has the example of a multiple line TRY block with a block of code executed in the event of an error

    SubProcedure1



Exit Sub
HandleError:

    Select Case Err.Number
        Case 0
            ' This shold never happen as this code is an error handler!
            ' However if it does still allow the Err.raise to execute below. (In this case Err.raise will fail
            ' and itself will raise an error "Invalid procedure call or argument" indicating that 0 cannot be used to raise and error!

        Case 111111
            ' You might want to do special error handling for some predicted error numbers
            ' perhaps resulting in a exit sub with no error or
            ' perhaps using the Err.raise below

         Case Else
            ' Just the Err.raise below is used for all other errors

    End Select

    '
    ' I include the procedure ManageErrSource  as an exmple of how Err.Source can be used to maintain a call stack of procedure names
    ' and store the name of the procedure that FIRST raised the error.
    '
    Err.Raise Err.Number _
           , ManageErrSource("MyModuleName", Err.Source, Erl(), "tsub1_RaisesProcedureNotFoundError") _
           , Err.Number & "-" & Err.Description

    ' Note the next line never gets excuted, but I like to have resume in the code for when I am debugging.
    ' (When a break is active, by moving the next executable line onto it, and using step over, it moves the exection point to the line that actually raised the error)
    Resume

End Sub

Sub SubProcedure1()

    ' -----------------------------------------------------
    ' Example of a multiple line TRY block with a Case statement used to CATCH the error

    '
    ' It is sometimes better to NOT use this technique but to put the code in it's own procedure
    ' (ie I refer to the code below that is surrounded by the tag #OWNSUB) .
    ' However,sometimes using this technique makes code more readable or simpler!
    '

    Dim i As Integer

' This line puts in place the defualt error handler found at the very foot of the procedure
On Error GoTo HandleError


    '
    ' Perhaps lots of statements and code here
    '


    ' First an example with comments


    ' -----------------------------------------------------
    ' TRY BLOCK START

        ' This next line causes execution to "jump" to the "catch" block in the event an error is detected.
On Error GoTo CatchBlock1_Start

        ' #OWNSUB

        tsub_WillNotRaiseError_JustPrintsOk

        If vbYes = MsgBox("1. Do you want to raise an error in the try block? - (PRESS CTRL+BREAK now then choose YES, try no later.)", vbYesNo) Then
            i = 100 / 0
        End If

        '
        ' Perhaps lots of statements and code here
        '

        ' #OWNSUB

    ' TRY BLOCK END
    ' -----------------------------------------------------


    ' -----------------------------------------------------
    ' CATCH BLOCK START
CatchBlock1_Start:

    If Err.Number = 0 Then
        On Error GoTo HandleError
        ' Re-instates the procedure's generic error handler
        ' This is also done later, but I think putting it here reduces the likelyhood of a coder accidentally removing it.

    Else

        ' WARNING: BE VERY CAREFUL with any code that is written here as
        ' the "On Error GoTo CatchBlock1_Start" is still in effect and therefore any errors that get raised could goto this label
        ' and cause and infinite loop.
        ' NOTE that a replacement "On Error Goto" cannot be executed until Err.clear is used, otherwise the "On Error Goto"
        ' will itself raise and error.
        ' THEREFORE KEEP THE CODE HERE VERY SIMPLE!
        ' RememberThenClearTheErrorObject should be the only code executed and this called procedure must be tight!

        ' This saves the details of the error in variables so that the "On Error GoTo HandleError" can be used
        ' to determine how the next Err.Raise used below is handled (and also how any unexpected implicitly raised errors are handled)
        RememberThenClearTheErrorObject

        On Error GoTo HandleError   '#THISLINE#

        If vbYes = MsgBox("2. Do you want to raise an error in the erro handler? - (PRESS CTRL+BREAK now then try both YES and NO )", vbYesNo) Then
            i = 100 / 0
        End If

        Select Case RememberErrNumber
            Case 0:  ' No Error, do Nothing

            Case 2517
                Debug.Print "The coder has decided to just give a Warning: Procedure not found " & Err.Number & " - " & Err.Description
                ClearRememberedErrorObjectValues ' Not essential, but might save confusion if coding errors are made

            Case Else
                ' An unexepected error or perhaps an (user) error that needs re-raising occurred and should to be re-raised

                ' NOTE this is giving an example of what woudl happen if the CatchBlock1_ErrorElse is not used below
                If vbYes = MsgBox("3. Do you want to raise an error in the ELSE error handler? CatchBlock1_ErrorElse *HAS NOT*  been used? - (PRESS CTRL+BREAK now then try both YES and NO )", vbYesNo) Then
                    i = 100 / 0
                End If

     On Error GoTo CatchBlock1_ErrorElse


                ' SOME COMPLEX ERROR HANDLING CODE - typically error logging, email, text file, messages etc..
                ' Because the error objects values have been stored in variables, you can use
                ' code here that might itself raise an error and CHANGE the values of the error object.
                ' You might want to surround the code with the commented out CatchBlock1_ErrorElse lines
                ' to ignore these errors and raise the remembered error.  (or if calling a error handling module
                ' just use on error resume next).
                ' Without the CatchBlock1_ErrorElse lines any error raised in this "complex code" will be handled by the
                ' active error handler which was set by the "On Error GoTo HandleError" tagged as '#THISLINE#" above.

                If vbYes = MsgBox("4. Do you want to raise an error in the ELSE error handler when CatchBlock1_ErrorElse   HAS  been used? - (PRESS CTRL+BREAK now then try both YES and NO )", vbYesNo) Then
                    i = 100 / 0
                End If

CatchBlock1_ErrorElse:
     On Error GoTo HandleError
                ' This line must be preceeded by an new "On error goto" for obvious reasons
                Err.Raise RememberErrNumber, RememberErrSource, RememberErrDescription

        End Select

        On Error GoTo HandleError

    End If
    ' CATCH BLOCK END
    ' -----------------------------------------------------
On Error GoTo HandleError  ' Unnecessary but used to delimt the catch block




'
' lots of code here perhaps
'




    ' -----------------------------------------------------
    ' Example 2
    '
    ' In this example goto statements are used instead of the IF statement used in example 1
    ' and no explanitory comments are given (so you can see how simple it can look)
    '

    ' -----------------------------------------------------
    ' TRY BLOCK START

On Error GoTo CatchBlock2_Start

        tsub_WillNotRaiseError_JustPrintsOk

        If vbYes = MsgBox("Do you want to raise an error? - (PRESS CTRL+BREAK now then choose YES)", vbYesNo) Then
            i = 100 / 0
        End If

        '
        ' Perhaps lots of statements and code here
        '

    ' TRY BLOCK END
    ' -----------------------------------------------------


GoTo CatchBlock2_End:
CatchBlock2_Start:

        RememberThenClearTheErrorObject

        On Error GoTo HandleError

        Select Case RememberErrNumber
            Case 0:  ' No Error, do Nothing

            Case 2517
                Debug.Print "The coder has decided to just give a Warning: Procedure not found " & Err.Number & " - " & Err.Description
                ClearRememberedErrorObjectValues ' Not essential, but might save confusion if coding errors are made

            Case Else
                ' An unexepected error or perhaps an (user) error that needs re-raising occurred and should to be re-raised
                ' In this case the unexpecetd erro will be handled by teh code that called this procedure
                ' This line must be preceeded by an new "On error goto" for obvious reasons
                Err.Raise RememberErrNumber, RememberErrSource, RememberErrDescription

        End Select

        On Error GoTo HandleError

    End If

CatchBlock2_End:
    ' CATCH BLOCK END
    ' -----------------------------------------------------
On Error GoTo HandleError  ' Unnecessary but used to delimt the catch block




'
' Here you could add lots of lines of vba statements that use the generic error handling that is after the HandleError: label
'
'

'
' You could of course, alway add more TRY CATCH blocks like the above
'
'



Exit Sub
HandleError:

    Select Case Err.Number
        Case 0
            ' This shold never happen as this code isan error handler!
            ' However if it does still allow the Err.raise to execute below. (In this case Err.raise will fail
            ' and itself will raise an error "Invalid procedure call or argument" indicating that 0 cannot be used to raise and error!

        Case 111111
            ' You might watch to do special error handling for some predicted error numbers
            ' perhaps exit sub
            ' Perhaps using the Err.raise below
    End Select

    ' ie Otherwise
    '
    ' Note that I use the Err.Source to maintain a call stack of procedure names
    '
    Err.Raise Err.Number _
           , ManageErrSource("MyModuleName", Err.Source, Erl(), "tsub1_RaisesProcedureNotFoundError") _
           , Err.Number & "-" & Err.Description

    ' Note the next line never gets excuted, but I like to have resume in the code for when I am debugging.
    ' (By moving the next executable line onto it, and using step over, it moves the exection point to the line that actually raised the error)
    Resume

End Sub



Sub tsub_WillNotRaiseError_JustPrintsOk()

    Static i As Integer

    i = i + 1

    Debug.Print "OK " & i

End Sub



Public Function ManageErrSource(MyClassName As String, ErrSource As String, ErrLine As Integer, ProcedureName As String) As String

    ' This function would normally be in a global error handling module

    ' On Error GoTo err_ManageErrSource

    Const cnstblnRecordCallStack  As Boolean = True

    Select Case ErrSource

        Case Application.VBE.ActiveVBProject.Name

            ' Err.Source is set to this value when a VB statement raises and error. eg In Access by defualt it is set to "Database"

            ManageErrSource = Application.VBE.ActiveVBProject.Name & " " & MyClassName & "." & ProcedureName & ":" & ErrLine

        Case ""

            ' When writing code ouside of the error handling code, the coder can raise an error explicitly, often using a user error number.
            ' ie by using err.raise MyUserErrorNumber, "", "My Error descirption".
            ' The error raised by the coder will be handled by an error handler (typically at the foot of a procedure where it was raised), and
            ' it is this handler that calls the ManageErrSource function changing the Err.Source from "" to a meaningful value.

            ManageErrSource = Application.VBE.ActiveVBProject.Name & " " & MyClassName & "." & ProcedureName & ":" & ErrLine

        Case Else

            ' This code is executed when ManageErrSource has already been called.  The Err.Source will already have been set to hold the
            ' Details of where the error occurred.
            ' This option can be used to show the call stack, ie the names of the procdures that resulted in the prcedure with the error being called.

            If cnstblnRecordCallStack Then

                If InStr(1, ErrSource, ";") = 0 Then
                    ManageErrSource = ErrSource & ":: Called By: "
                End If
                ManageErrSource = ErrSource & ";" & ProcedureName & ":" & ErrLine

            Else
                ManageErrSource = ErrSource

            End If

    End Select

Exit Function
err_ManageErrSource:
    Err.Raise Err.Number, "MyModuleName.err_ManageErrSource", Err.Description
    Resume

End Function
Antworten
14
Nur zwei Anmerkungen: 1 Warum in aller Welt würden Sie das verwenden? 2 `Bei Fehler Springen -1` vor 4 Jahren 1
Try / catch kann auch emuliert werden, indem der entsprechende Code mit "On Error Resume Next" und "On Error GoTo 0" umbrochen wird und die "Err.Number" überprüft wird. Das obige ist etwas schwer zu folgen, hat etwas 'Spaghetti'-Struktur. Ioannis vor 4 Jahren 4
Danke Rory, ich habe es geändert. Sie würden es verwenden oder den gleichen Grund, warum jemand eine TRY CATCH-Anweisung in VB oder SQL Server verwendet. dh Sie können Ihren Code anders strukturieren. Sie können also den gleichen Fehlerhandler für viele Codezeilen verwenden, ohne die Zeilen in eine eigene Prozedur einfügen zu müssen. vor 4 Jahren 0
Loannis Ja, ich habe das in der Vergangenheit für einzelne Codezeilen gemacht, die einen Fehlerbehandler benötigen. Dh eine Codezeile hat einen Fehlerbehandler. Mit TRY CATCH kann ein Codeblock (mit vielen Zeilen) mit einer eigenen Fehlerbehandlungsroutine in eine Prozedur eingebettet werden. Ich verwende oft TRY CATCH in SQL Server und da es auch in VB verfügbar ist, muss es einige allgemein nützliche Zwecke erfüllen. Zugegebenermaßen ist diese Version etwas unordentlich. vor 4 Jahren 0
@Loannis Was ist, wenn Sie mehrere Zeilen überspringen möchten, wenn Sie eine Fehlermeldung erhalten. Sehen Sie meine Antwort für ein vereinfachtes Beispiel. Natürlich können Sie dies auch mit der regelmäßigen Fehlerbehandlung tun. D_Bester vor 4 Jahren 0
Übrigens ist es Ioannis mit "i" :) Wenn Sie mehrere Zeilen überspringen möchten, würde ein Label ausreichen. Kennen Sie [MZTools] (http://www.mztools.com/v3/features.aspx) und die Fehlerbehandlung? Sie definieren eine Fehlerbehandlungsbezeichnung für jede Unterroutine und fügen dort eine globale Funktion hinzu, die die Fehlerbehandlung übernimmt. Dies kann alles tun, z. B. das Fortsetzen eines anderen Einstiegspunkts der Subroutine, das Instanzieren eines Fehlerobjekts, in dem die Fehlerzustände usw. gespeichert sind. Sie können also ein Sub haben, das den Fehler speichert und das `Err 'löscht und fortsetzt.} abhängig von seinem Typ. Fehlt mir etwas? Ioannis vor 4 Jahren 0

5 Antworten auf die Frage

22
Mathieu Guindon

Das Problem ist, dass Laufzeitfehler in VBA keine Ausnahmen sind und die Fehlerbehandlung in VBA mit der Ausnahmebehandlung wenig gemein hat.

RememberErrLine = Erl()

Die ErlFunktion ist VBA.Informationaus einem Grund ein verborgenes Mitglied des Moduls. Sie gibt 0 zurück, sofern der Fehler nicht in einer nummerierten Zeile aufgetreten ist. Wenn Sie in VBA Zeilennummern verwenden, leben Sie seit 25 Jahren in einer Höhle und verwenden wahrscheinlich GoSubAnweisungen, anstatt Prozeduren zu schreiben. Zeilennummern werden aus Gründen der älteren Version / Abwärtskompatibilität unterstützt, da in den 1980er Jahren geschriebener Code erforderlich war .

Ich mag es, wie du es selbst gesagt hast:

' THEREFORE KEEP THE CODE HERE VERY SIMPLE!

..aber warum wird das nicht auf den Rest des Codes angewendet? Keine Beleidigung, aber dies ist Spaghetti-Logik, geschrieben in Verfahren, die eindeutig und schamlos gegen das Prinzip der einheitlichen Verantwortung verstoßen . Kein SRP-kompatibler Code würde je zwei solcher "Try-Catch" -Blöcke benötigen.

Das riecht:

Case 0:  ' No Error, do Nothing

Dies bedeutet eines von zwei Dingen: Entweder haben Sie Fehlerbehandlungscode, der in nicht fehlerhaften Kontexten ausgeführt wird, oder Sie haben toten Code, der gelöscht werden soll.

Das riecht nach :

GoTo CatchBlock2_End:
CatchBlock2_Start:

Erstens ist ein Doppelpunkt ( :), der keine Zeilenbezeichnung angibt, ein Anweisungstrennzeichen . Es stellt sich heraus, dass eine neue Zeile auch ein "Anweisungstrennzeichen" ist, so dass der Doppelpunkt am Ende GoTo CatchBlock2_Endvöllig nutzlos und verwirrend ist, insbesondere angesichts des Einrückungsgrades der GoTo-Anweisung .

Apropos GoTo...

Neil Stephenson findet es süß, seinen Labels "Dengo" zu nennen

Ich mag es nicht, wie ich zwischen Labels springen muss, um dem Code zu folgen. IMO es ist chaotisch und unnötig spaghettified.


Fein, Smartypants . Wie behandelt man dann Fehler in VBA sauber?

1. Schreiben Sie an erster Stelle einen sauberen Code.

Halten Sie sich an bewährte Verfahren und schreiben Sie kleine Prozeduren, die eine Sache tun, und machen Sie es gut.

2. Schreiben Sie objektorientierten Code.

Abstraktion und Kapselung sind zwei der vier Säulen von OOP und werden in VBA vollständig unterstützt. Polymorphismus ist auch eine Option; Nur die richtige Vererbung ist ausgeschlossen, aber das hindert nicht daran, Konzepte in Klassenmodulen zu abstrahieren und spezialisierte Objekte zu instanziieren.

Prozeduraler Code, der in Standardmodule (.bas) geschrieben ist, sollte aus kleinen öffentlichen Methoden (Makro-Hooks) bestehen, die die für die Ausführung der Funktionalität erforderlichen Objekte erstellen.

Also, wie hängt das auch nur mit der richtigen Fehlerbehandlung zusammen?

3. Umarmen Sie idiomatische Fehlerbehandlung, bekämpfen Sie es nicht.

Angesichts des Codes, der den oben genannten Punkten entspricht, gibt es keinen Grund, die Fehlerbehandlung des idiomatischen VBA-Wegs nicht zu implementieren.

Public Sub DoSomething()
    On Error GoTo CleanFail

    'method body

CleanExit:
    'cleanup code goes here. runs regardless of error state.
    Exit Sub

CleanFail:
    'handle runtime error(s) here.
    'Raise Err.Number '"rethrow" / "bubble up"
    Resume CleanExit
    Resume 'for debugging - break above and jump to the error-raising statement
End Sub

Dieses Muster ähnelt einem "try-catch-finally" auf folgende Weise:

  • Der Rumpf ist der "try" -Teil, der das tut, was der Methodenname sagt, und nichts weiter
  • CleanFailist der "catch" -Teil, der nur ausgeführt wird, wenn ein Fehler auftritt
  • CleanExitist der "endgültige" Teil, der unabhängig davon läuft, ob ein Fehler aufgetreten ist oder nicht ... es sei denn, Sie werden erneut geworfen . Wenn Sie jedoch einen Fehler auslösen müssen, damit der aufrufende Code verarbeitet werden kann, sollten Sie nicht viel Bereinigungscode ausführen, und Sie sollten einen sehr sehr guten Grund dafür haben.

Wenn Ihre Fehlerbehandlungs-Subroutine einen Fehler auslösen kann, halten Sie sich nicht an SRP. Zum Beispiel ist das Schreiben in eine Protokolldatei ein Problem, das in ein LoggerObjekt abstrahiert werden sollte, das sich mit Protokollierungsproblemen befasst und Methoden zur Verfügung stellt, die ihre eigenen Fehler behandeln. Der Unterprogrammcode für die Fehlerbehandlung sollte trivial sein.

Thanks @ mat'smug für die Zeit, Kommentare hinzuzufügen, die mir wirklich geholfen haben Ich bin für gewalttätige, aber dennoch humorvolle Kritik. Ich habe meinen Kodex überprüft und ich freue mich, dass die große Mehrheit an den von Ihnen skizzierten Auftraggebern festhält. Ihre Erklärung war jedoch nützlich und ich habe darüber nachgedacht und festgestellt, dass ich nicht wusste, dass VB- und SQL Server-Anweisungen für TRY CATCH in jeder Prozedur nur einmal verwendet werden (ich dachte, sie waren ein Mittel, um nicht den Code abstrahieren zu müssen, um sie zu erstellen lesbarer). Wenn Sie weitere Kommentare zur ManageErrSource-Prozedur hinzufügen möchten, bin ich ganz Ohr ... HarveyFrench vor 4 Jahren 0
@HarveyFrench Ich füge noch ein paar hinzu, wenn ich eine Chance habe - ich habe mir diese nicht angesehen ;-) Das Referenzieren und Verwenden der VBIDE API erfordert besondere Sicherheitseinstellungen, was nicht cool ist. Ich benutze `TypeName (Me)` als Quelle für benutzerdefinierte Fehler in Klassenmodulen. Die einzige Möglichkeit, einen Fehler zu ermitteln, in welcher Prozedur er aufgetreten ist, besteht darin, den Prozedurnamen in einem lokalen `const festzukodieren `, idealerweise nicht zu weit von der Signatur der Methode entfernt. Ich mag die Call-Stack-Idee, aber ein Nachteil besteht darin, dass Sie beim Eintreten / Verlassen einer Prozedur durchgängig "Push" und "Pop" ausführen müssen, andernfalls wird dies zu einer Lüge. Mathieu Guindon vor 4 Jahren 0
Mit dem Code, den ich von fmsinc.com erhalten habe, werden viele Probleme umgangen, die ich hatte. Ich würde deine Meinung wertschätzen. Siehe hier http://codereview.stackexchange.com/questions/94498/passing-an-error-object-back-to-calling-code Ich schätze Ihre Zeit sehr, da dies mich verrückt macht. HarveyFrench vor 4 Jahren 0
10
D_Bester

Diese Antwort soll das Try / Catch-Muster einfacher verständlich machen.

Dies unterscheidet sich nicht wesentlich von der regulären Inline-Fehlerbehandlung, mit der Ausnahme, dass mehrere Zeilen gleichzeitig übersprungen werden können, ein Fehler behandelt und die reguläre Ausführung wieder aufgenommen werden kann. Dies ist ein sehr sauber strukturiertes Muster für die Fehlerbehandlung. Die Strömung bewegt sich sehr sauber von oben nach unten. Kein Spaghetti-Code hier.

Normalerweise wird der Fehlerhandler unten platziert. Das Try / Catch-Konstrukt ist jedoch so elegant. Es ist eine sehr strukturierte Art, Fehler zu behandeln, und es ist sehr leicht zu folgen. Dieses Muster versucht, dies auf sehr saubere, prägnante Weise zu reproduzieren. Der Fluss ist sehr konstant und springt nicht von Ort zu Ort.

Sub InLineErrorHandling()

    'code without error handling

BeginTry1:

    'activate inline error handler
    On Error GoTo ErrHandler1

    'code block that may result in an error
    Dim a As String: a = "Abc"
    Dim c As Integer: c = a 'type mismatch

ErrHandler1:

    'handle the error
    If Err.Number <> 0 Then

        'the error handler is now active
        Debug.Print (Err.Description)

    End If

    'disable previous error handler (VERY IMPORTANT)
    On Error GoTo 0
    'exit the error handler
    Resume EndTry1

EndTry1:

    'more code with or without error handling

End Sub

Quellen:

Richtig gelungen funktioniert das ganz gut. Es ist ein sehr sauberes Fließmuster, das überall dort reproduzierbar ist, wo es benötigt wird.

@D_Bester, Thanks for the links and the simple example. I'm still learning and found your feedback useful, however you will need to add an "On Error Goto 0" after the "on Error goto -1". Also on reflection I think it is better to use Err.Clear instead of "On Error Goto -1" as it more clearly shows what is happening. I'm finding this whole error handling in VBA a bit of a black art. HarveyFrench vor 4 Jahren 1
@D_Bester. On reflection, you code is fine if all you want to give the user a message when an error occurs, but what if you want to re-raise the error? Which will be a very common scenario. Consider. If you code was trying to lookup a customer's details and it couldn't get them for an UNEXPECTED reason. You would need to re-raise he error and let the code that is using your code to do the lookup decide what to do. HarveyFrench vor 4 Jahren 0
@ HarveyFrench Wenn Sie den Fehler erneut auslösen möchten, verwenden Sie einfach 'Err.Raise'. Kein Problem dort, vorausgesetzt, der Code ist gut strukturiert und die Fehlerbehandlung ist im aufrufenden Code aktiviert. D_Bester vor 4 Jahren 0
@HarveyFrench `Err.Clear` und` On Error Goto -1` sind NICHT gleichwertig. Siehe http://stackoverflow.com/a/30994055/2559297 D_Bester vor 4 Jahren 0
Du hast recht, sie sind nicht gleich leid. Aber ich denke, der Code oben muss noch auf Fehler GoTo -1 mit Err.Clear ausgetauscht werden, andernfalls springt "Mehr Code ohne Fehlerbehandlung" zu ErrHandler1, wenn ein Fehler auftritt. HarveyFrench vor 4 Jahren 0
Wenn Sie den Fehler erneut ausgeben und "Err.Raise" wie vorgeschlagen verwenden möchten, springt Ihr Code zu ErrHandler1. Wenn Sie "on error goto AnotherLabel" verwenden, wird der Fehler auf 0 zurückgesetzt. Dies ist der gesamte Punkt meines Codes und der Grund, warum ich die Variablen "Remember" verwende. etc ... Ich schlage vor, Sie schauen sich meinen Code noch einmal an und überlegen sich ein wenig mehr. (Ich habe das Gefühl, dass ich mit dieser ganzen Fehlerbehandlung im Kreis herumgegangen bin, aber jetzt komme ich dorthin ... danke!) HarveyFrench vor 4 Jahren 0
@HarveyFrench Nach dem Fehler wird der Error-Handler aktiv. Ein `Err.Raise` springt dann nicht zu` ErrHandler1`, solange der Error-Handler aktiv ist. Ich schlage vor, dass Sie einfach den Code ausführen und sehen. D_Bester vor 4 Jahren 0
Ich habe meinen Code überarbeitet, um "On Error Goto -1" basierend auf dem Feedback von Chip Pearson zu eliminieren. D_Bester vor 4 Jahren 0
Ich fühle mich immer noch unwohl mit dem, was Sie geschrieben haben. Erstens sehe ich nicht die Notwendigkeit für die Zeile "Resume EndTry1", da die vorherige Zeile "on error goto 0" das Objekt err auf nichts setzt und alle Fehler, die danach auftreten, bewirken nicht mehr, dass der Code zu "springt". Errhandler1 ". Zweitens: Wenn durch den "echten" Code ein Fehler auftritt, den Sie nach der Zeile "If Err.Number <> 0 Then" hinzufügen, springt die Ausführung zurück zu Errhandler1. (Ich fange an, dieses Thema wirklich zu hassen!) HarveyFrench vor 4 Jahren 0
Ich würde mich freuen, darüber im Chat zu sprechen: http://chat.stackexchange.com/rooms/25228/discussion-between-harveyfrench-and-d-bester D_Bester vor 4 Jahren 0
10
RubberDuck

Hören Sie sich Mat'sMug an, aber er hat nicht die Situation behandelt, in der Sie tatsächlich wissen, wie Sie einen Fehler beheben können. Der Vollständigkeit halber möchte ich darauf eingehen.

Schauen wir uns zuerst an, wie wir so etwas in VB.Net machen würden.

Try
    foo = SomeMethodLikelyToThrowAnException
Catch e As SomeException
    foo = someDefaultValue
End Try

' some more code

Der idiomatische Weg, dies in VB6 zu tun, ist zu ResumeNext. Schreib das auf, denn es ist das einzige Mal, dass ich sage, dass es richtig ist ResumeNext.

On Error Goto ErrHandler

    foo = SomeMethodLikelyToRaiseAnError

    ' some more code

CleanExit: 
   ' clean up resources

    Exit Sub

ErrHandler:
    If Err.Number = ConstantValueForErrorWeExpected Then
        foo = someDefaultValue
        Resume Next
    End If

    Resume CleanExit 'or re-raise error

Exit Sub

Der alternative Weg ist, diese Logik zu integrieren, die meiner Meinung nach etwas sauberer und näher an der Try...CatchRedewendung ist, aber bei Missbrauch schnell hässlich werden kann.

On Error Resume Next 

    foo = SomeMethodLikelyToRaiseAnError

    If Err.Number = ConstantValueForErrorWeExpected Then 
        foo = someDefaultValue
    End If

On Error Goto 0

Entweder ist eine idiomatische Art, mit erwarteten Fehlern umzugehen, aber was auch immer Sie tun. Machen Sie sich nicht die Mühe, Resume Nextbis Sie vollständig verstanden haben, was es tut und wann es angemessen ist. (Eher eine Warnung für zukünftige Leser als für Sie. Sie scheinen die Fehlerbehandlung in VB6 gründlich zu verstehen. Vielleicht ein bisschen zu gut für Ihr eigenes Wohl.)

Vielen Dank @RubberDuck für Ihre nützlichen Kommentare. Um ehrlich zu sein, benutze ich "On Error resume next" vor einigen Prozeduraufrufen, nach denen normalerweise ein SELECT-CASE vorhanden ist, der auf einen aufgetretenen Fehler reagiert. Der große Fehler, den ich verwirkliche, ist, dass ich in der Subprozedur eine benutzerdefinierte Ausnahme auslöse, um auftretende Situationen zu kennzeichnen (wie der Benutzer, der die Verarbeitung abbrechen möchte). Ich meine, ich sollte Funktionen mehr verwenden. Dies ist ein Hinweis darauf, dass meine allgemeine Codestruktur "nicht ideal" / schlecht ist und ich denke, und ich muss mich damit befassen. Vielen Dank. HarveyFrench vor 4 Jahren 1
@HarveyFrench hat einen tollen Punkt erreicht. Ausnahmen gelten für * außergewöhnliches * Verhalten, nicht für den Kontrollfluss. Herzlich willkommen bei CR. RubberDuck vor 4 Jahren 0
Ich wäre sehr an Ihren Meinungen zu dieser SO-Frage interessiert: http://stackoverflow.com/questions/31007009/can-you-help-me-understand-more-about-good-practise-with-vba-error- Handling-Pleas HarveyFrench vor 4 Jahren 0
Mit dem Code, den ich von fmsinc.com erhalten habe, werden viele Probleme umgangen, die ich hatte. Ich würde deine Meinung wertschätzen. Siehe hier http://codereview.stackexchange.com/questions/94498/passing-an-error-object-back-to-calling-code HarveyFrench vor 4 Jahren 0
0
Michael R

Um meinen vorherigen Beitrag zu verdeutlichen, die folgende Zeile aus dem Code von HarveyFrench:

RememberErrLine = Erl()

funktioniert nur, wenn jeder Codezeile Zeilennummern hinzugefügt wurden. Anstatt Zeilennummern manuell einzugeben, was viel zu langwierig ist, können Sie die Zeilennummern mit einem Werkzeug automatisch hinzufügen. Es gibt ein paar Tools, die das können, ich benutze einen CodeLiner.

Hier ist der Code mit Zeilennummern, der Erl()erfolgreich arbeiten kann:

Option Compare Database
Option Explicit

Dim RememberErrNumber As Long
Dim RememberErrDescription As String
Dim RememberErrSource As String
Dim RememberErrLine  As Integer

Private Sub RememberThenClearTheErrorObject()
10
11     On Error Resume Next
12
    ' For demo purposes
14     Debug.Print "ERROR RAISED"
15     Debug.Print Err.Number
16     Debug.Print Err.Description
17     Debug.Print Err.Source
18     Debug.Print " "
19
20
    ' This function has to be declared in the same scope as the variables it refers to
22     RememberErrNumber = Err.Number
23     RememberErrDescription = Err.Description
24     RememberErrSource = Err.Source
25     RememberErrLine = Erl()
26
    ' Note that the next line will reset the error object to 0, the variables above are used to remember the values
    ' so that the same error can be re-raised
29     Err.Clear
30
    ' Err.Clear  is used to clear the raised exception and set the err object to nothing (ie err.number to 0)
    ' If Err.Clear has not be used, then the next "On Error GoTo ALabel" that is used in this or the procedure that called it
    ' will actually NOT pass execution to the ALabel: label BUT the error is paseed to the procedure that called this procedure.
    ' Using Err.Clear (or "On Error GoTo -1 ")  gets around this and facilitates the whole TRY CATCH block scenario I am using there.
35
36
    ' For demo purposes
38     Debug.Print "ERROR RAISED is now 0 "
39     Debug.Print Err.Number
40     Debug.Print Err.Description
41     Debug.Print Err.Source
42     Debug.Print " "
43
    ' For demo purposes
45     Debug.Print "REMEMBERED AS"
46     Debug.Print RememberErrNumber
47     Debug.Print RememberErrDescription
48     Debug.Print RememberErrSource
49     Debug.Print " "
50
End Sub

Private Sub ClearRememberedErrorObjectValues()
54
    ' This function has to be declared in the same scope as the variables it refers to
56     RememberErrNumber = 0
57     RememberErrDescription = ""
58     RememberErrSource = ""
59     RememberErrLine = 0
60
End Sub




Sub ExampleOfTryCatchBlockInVBA()
67
68     On Error GoTo HandleError
69
70
    ' -----------------------------------------------------
    ' SubProcedure1 has the example of a multiple line TRY block with a block of code executed in the event of an error
73
74     SubProcedure1
75
76
77
78 Exit Sub
79 HandleError:
80
81     Select Case Err.Number
82         Case 0
            ' This shold never happen as this code is an error handler!
            ' However if it does still allow the Err.raise to execute below. (In this case Err.raise will fail
            ' and itself will raise an error "Invalid procedure call or argument" indicating that 0 cannot be used to raise and error!
86
87         Case 111111
            ' You might want to do special error handling for some predicted error numbers
            ' perhaps resulting in a exit sub with no error or
            ' perhaps using the Err.raise below
91
92          Case Else
            ' Just the Err.raise below is used for all other errors
94
95     End Select
96
    '
    ' I include the procedure ManageErrSource  as an exmple of how Err.Source can be used to maintain a call stack of procedure names
    ' and store the name of the procedure that FIRST raised the error.
    '
101     Err.Raise Err.Number _
           , ManageErrSource("MyModuleName", Err.Source, Erl(), "tsub1_RaisesProcedureNotFoundError") _
           , Err.Number & "-" & Err.Description
104
    ' Note the next line never gets excuted, but I like to have resume in the code for when I am debugging.
    ' (When a break is active, by moving the next executable line onto it, and using step over, it moves the exection point to the line that actually raised the error)
107     Resume
108
End Sub

Sub SubProcedure1()
112
    ' -----------------------------------------------------
    ' Example of a multiple line TRY block with a Case statement used to CATCH the error
115
    '
    ' It is sometimes better to NOT use this technique but to put the code in it's own procedure
    ' (ie I refer to the code below that is surrounded by the tag #OWNSUB) .
    ' However,sometimes using this technique makes code more readable or simpler!
    '
121
122     Dim i As Integer
123
' This line puts in place the defualt error handler found at the very foot of the procedure
125 On Error GoTo HandleError
126
127
    '
    ' Perhaps lots of statements and code here
    '
131
132
    ' First an example with comments
134
135
    ' -----------------------------------------------------
    ' TRY BLOCK START
138
        ' This next line causes execution to "jump" to the "catch" block in the event an error is detected.
140 On Error GoTo CatchBlock1_Start
141
        ' #OWNSUB
143
144         tsub_WillNotRaiseError_JustPrintsOk
145
146         If vbYes = MsgBox("1. Do you want to raise an error in the try block? - (PRESS CTRL+BREAK now then choose YES, try no later.)", vbYesNo) Then
147             i = 100 / 0
148         End If
149
        '
        ' Perhaps lots of statements and code here
        '
153
        ' #OWNSUB
155
    ' TRY BLOCK END
    ' -----------------------------------------------------
158
159
    ' -----------------------------------------------------
    ' CATCH BLOCK START
162 CatchBlock1_Start:
163
164     If Err.Number = 0 Then
165         On Error GoTo HandleError
        ' Re-instates the procedure's generic error handler
        ' This is also done later, but I think putting it here reduces the likelyhood of a coder accidentally removing it.
168
169     Else
170
        ' WARNING: BE VERY CAREFUL with any code that is written here as
        ' the "On Error GoTo CatchBlock1_Start" is still in effect and therefore any errors that get raised could goto this label
        ' and cause and infinite loop.
        ' NOTE that a replacement "On Error Goto" cannot be executed until Err.clear is used, otherwise the "On Error Goto"
        ' will itself raise and error.
        ' THEREFORE KEEP THE CODE HERE VERY SIMPLE!
        ' RememberThenClearTheErrorObject should be the only code executed and this called procedure must be tight!
178
        ' This saves the details of the error in variables so that the "On Error GoTo HandleError" can be used
        ' to determine how the next Err.Raise used below is handled (and also how any unexpected implicitly raised errors are handled)
181         RememberThenClearTheErrorObject
182
183         On Error GoTo HandleError   '#THISLINE#
184
185         If vbYes = MsgBox("2. Do you want to raise an error in the erro handler? - (PRESS CTRL+BREAK now then try both YES and NO )", vbYesNo) Then
186             i = 100 / 0
187         End If
188
189         Select Case RememberErrNumber
190             Case 0:  ' No Error, do Nothing
191
192             Case 2517
193                 Debug.Print "The coder has decided to just give a Warning: Procedure not found " & Err.Number & " - " & Err.Description
194                 ClearRememberedErrorObjectValues ' Not essential, but might save confusion if coding errors are made
195
196             Case Else
                ' An unexepected error or perhaps an (user) error that needs re-raising occurred and should to be re-raised
198
                ' NOTE this is giving an example of what woudl happen if the CatchBlock1_ErrorElse is not used below
200                 If vbYes = MsgBox("3. Do you want to raise an error in the ELSE error handler? CatchBlock1_ErrorElse *HAS NOT*  been used? - (PRESS CTRL+BREAK now then try both YES and NO )", vbYesNo) Then
201                     i = 100 / 0
202                 End If
203
204      On Error GoTo CatchBlock1_ErrorElse
205
206
                ' SOME COMPLEX ERROR HANDLING CODE - typically error logging, email, text file, messages etc..
                ' Because the error objects values have been stored in variables, you can use
                ' code here that might itself raise an error and CHANGE the values of the error object.
                ' You might want to surround the code with the commented out CatchBlock1_ErrorElse lines
                ' to ignore these errors and raise the remembered error.  (or if calling a error handling module
                ' just use on error resume next).
                ' Without the CatchBlock1_ErrorElse lines any error raised in this "complex code" will be handled by the
                ' active error handler which was set by the "On Error GoTo HandleError" tagged as '#THISLINE#" above.
215
216                 If vbYes = MsgBox("4. Do you want to raise an error in the ELSE error handler when CatchBlock1_ErrorElse   HAS  been used? - (PRESS CTRL+BREAK now then try both YES and NO )", vbYesNo) Then
217                     i = 100 / 0
218                 End If
219
220 CatchBlock1_ErrorElse:
221      On Error GoTo HandleError
                ' This line must be preceeded by an new "On error goto" for obvious reasons
223                 Err.Raise RememberErrNumber, RememberErrSource, RememberErrDescription
224
225         End Select
226
227         On Error GoTo HandleError
228
229     End If
    ' CATCH BLOCK END
    ' -----------------------------------------------------
232 On Error GoTo HandleError  ' Unnecessary but used to delimt the catch block
233
234
235
236
'
' lots of code here perhaps
'
240
241
242
243
    ' -----------------------------------------------------
    ' Example 2
    '
    ' In this example goto statements are used instead of the IF statement used in example 1
    ' and no explanitory comments are given (so you can see how simple it can look)
    '
250
    ' -----------------------------------------------------
    ' TRY BLOCK START
253
254 On Error GoTo CatchBlock2_Start
255
256         tsub_WillNotRaiseError_JustPrintsOk
257
258         If vbYes = MsgBox("Do you want to raise an error? - (PRESS CTRL+BREAK now then choose YES)", vbYesNo) Then
259             i = 100 / 0
260         End If
261
        '
        ' Perhaps lots of statements and code here
        '
265
    ' TRY BLOCK END
    ' -----------------------------------------------------
268
269
270 GoTo CatchBlock2_End:
271 CatchBlock2_Start:
272
273         RememberThenClearTheErrorObject
274
275         On Error GoTo HandleError
276
277         Select Case RememberErrNumber
278             Case 0:  ' No Error, do Nothing
279
280             Case 2517
281                 Debug.Print "The coder has decided to just give a Warning: Procedure not found " & Err.Number & " - " & Err.Description
282                 ClearRememberedErrorObjectValues ' Not essential, but might save confusion if coding errors are made
283
284             Case Else
                ' An unexepected error or perhaps an (user) error that needs re-raising occurred and should to be re-raised
                ' In this case the unexpecetd erro will be handled by teh code that called this procedure
                ' This line must be preceeded by an new "On error goto" for obvious reasons
288                 Err.Raise RememberErrNumber, RememberErrSource, RememberErrDescription
289
290         End Select
291
292         On Error GoTo HandleError
293
294     End If
295
296 CatchBlock2_End:
    ' CATCH BLOCK END
    ' -----------------------------------------------------
299 On Error GoTo HandleError  ' Unnecessary but used to delimt the catch block
300
301
302
303
'
' Here you could add lots of lines of vba statements that use the generic error handling that is after the HandleError: label
'
'
308
'
' You could of course, alway add more TRY CATCH blocks like the above
'
'
313
314
315
316 Exit Sub
317 HandleError:
318
319     Select Case Err.Number
320         Case 0
            ' This shold never happen as this code isan error handler!
            ' However if it does still allow the Err.raise to execute below. (In this case Err.raise will fail
            ' and itself will raise an error "Invalid procedure call or argument" indicating that 0 cannot be used to raise and error!
324
325         Case 111111
            ' You might watch to do special error handling for some predicted error numbers
            ' perhaps exit sub
            ' Perhaps using the Err.raise below
329     End Select
330
    ' ie Otherwise
    '
    ' Note that I use the Err.Source to maintain a call stack of procedure names
    '
335     Err.Raise Err.Number _
           , ManageErrSource("MyModuleName", Err.Source, Erl(), "tsub1_RaisesProcedureNotFoundError") _
           , Err.Number & "-" & Err.Description
338
    ' Note the next line never gets excuted, but I like to have resume in the code for when I am debugging.
    ' (By moving the next executable line onto it, and using step over, it moves the exection point to the line that actually raised the error)
341     Resume
342
End Sub



Sub tsub_WillNotRaiseError_JustPrintsOk()
348
349     Static i As Integer
350
351     i = i + 1
352
353     Debug.Print "OK " & i
354
End Sub



Public Function ManageErrSource(MyClassName As String, ErrSource As String, ErrLine As Integer, ProcedureName As String) As String
360
    ' This function would normally be in a global error handling module
362
    ' On Error GoTo err_ManageErrSource
364
365     Const cnstblnRecordCallStack  As Boolean = True
366
367     Select Case ErrSource
368
369         Case Application.VBE.ActiveVBProject.Name
370
            ' Err.Source is set to this value when a VB statement raises and error. eg In Access by defualt it is set to "Database"
372
373             ManageErrSource = Application.VBE.ActiveVBProject.Name & " " & MyClassName & "." & ProcedureName & ":" & ErrLine
374
375         Case ""
376
            ' When writing code ouside of the error handling code, the coder can raise an error explicitly, often using a user error number.
            ' ie by using err.raise MyUserErrorNumber, "", "My Error descirption".
            ' The error raised by the coder will be handled by an error handler (typically at the foot of a procedure where it was raised), and
            ' it is this handler that calls the ManageErrSource function changing the Err.Source from "" to a meaningful value.
381
382             ManageErrSource = Application.VBE.ActiveVBProject.Name & " " & MyClassName & "." & ProcedureName & ":" & ErrLine
383
384         Case Else
385
            ' This code is executed when ManageErrSource has already been called.  The Err.Source will already have been set to hold the
            ' Details of where the error occurred.
            ' This option can be used to show the call stack, ie the names of the procdures that resulted in the prcedure with the error being called.
389
390             If cnstblnRecordCallStack Then
391
392                 If InStr(1, ErrSource, ";") = 0 Then
393                     ManageErrSource = ErrSource & ":: Called By: "
394                 End If
395                 ManageErrSource = ErrSource & ";" & ProcedureName & ":" & ErrLine
396
397             Else
398                 ManageErrSource = ErrSource
399
400             End If
401
402     End Select
403
404 Exit Function
405 err_ManageErrSource:
406     Err.Raise Err.Number, "MyModuleName.err_ManageErrSource", Err.Description
407     Resume
408
End Function
Hallo! Willkommen bei Code Review. Bitte fügen Sie Ihrer Antwort mehr Kontext hinzu: Erklären Sie, warum Ihr Vorschlag den Code des OP verbessern wird, oder gehen Sie detaillierter auf das ein, was Sie sagen wollen. TheCoffeeCup vor 4 Jahren 3
0
Unhandled Exception

In Bezug auf "CleanExit" und das Thema "Endlich".

Mats Mug schrieb:

CleanExit ist der endgültige Teil, der unabhängig davon ausgeführt wird, ob ein Fehler aufgetreten ist oder nicht, es sei denn, Sie werden erneut geworfen.


Eine solche Situation könnte beispielsweise in diesem Verfahrenscode auftreten:

Verfahrensweise

Public Sub DoSomething()
    On Error GoTo CleanFail

    ' Open any resource

    ' Use the resource

CleanExit:
    ' Close/cleanup the resource
    Exit Sub

CleanFail:
    Raise Err.Number
    Resume CleanExit
End Sub

Problem hier : Wenn im Methodenhauptteil ein Fehler auftritt, der in CleanFail erneut ausgeführt werden muss, wird CleanExit nicht ausgeführt und die Ressource kann daher nicht ordnungsgemäß geschlossen werden.

Sicher, Sie können die Ressource auch im Fehlerbehandlungsprogramm selbst schließen. Dies kann jedoch dazu führen, dass mehrere Codefragmente vorhanden sind, an denen die Ressourcenbehandlung erfolgen muss / muss.


Mein Vorschlag ist die Verwendung eines benutzerdefinierten Objekts für jede Ressourcenbindungsnotwendigkeit:

AnyResourceBindingClass

Private Sub Class_Initialize() 'Or even use Mats 'Create method' approach here instead.
    'Open/acquire the resource here
End Sub

Private Sub Class_Terminate()
    On Error GoTo CleanFail

    'Close/clean up the resource here properly

CleanExit:
    Exit Sub

CleanFail:
    MsgBox Err.Source & " : " & Err.Number & " : " & Err.Description
    Resume CleanExit
End Sub

Public Sub UseResource()
    'Do something with the resource
End Sub

Objektorientierter Ansatz

Public Sub DoSomething()
    On Error GoTo CleanFail

    ' Use custom object which acquires the resource
    With New AnyResourceBindingClass
        .UseResource
    End With

CleanExit:
    Exit Sub

CleanFail:
    Raise Err.Number
    Resume CleanExit
End Sub

Opportunity : Da das benutzerdefinierte Objekt nicht in den Gültigkeitsbereich fällt, nachdem der Fehler ausgelöst wurde, wird die Terminate-Methode automatisch ausgeführt, wodurch die erworbene Ressource ordnungsgemäß geschlossen / bereinigt wird.

Eine Notwendigkeit weniger für einen "endlich" Block.


Fehlerbehandlung in der Terminate-Methode

Meiner Meinung nach ist es kontextabhängig, wie ein Fehler in der Terminate-Methode der benutzerdefinierten Klasse behandelt wird. Vielleicht sollte es irgendwo zusätzlich angemeldet werden oder gar verschluckt werden?

Sicher ist das besprechbar.

Es ist jedoch unbedingt erforderlich, einen Fehlerbehandler in dieser Methode zu aktivieren, da meines Erachtens jeder unbehandelte Fehler in dieser Methode meines Erachtens dazu führt, dass VBA die Ausführung unterbricht und die standardmäßige Laufzeitfehlermeldungsbox anzeigt.