Der folgende Code ist ziemlich selbsterklärend: Kopieren Sie ihn einfach, fügen Sie ihn in ein Modul ein und führen Sie ihn aus Es enthält einige Anwendungsfälle und viele erläuternde Kommentare im Text. (Es funktioniert, aber ich bin interessiert zu wissen, was andere Leute daraus machen und welche Vorschläge Sie machen möchten.)

Die wichtigsten Fakten, die zu realisieren sind, sind:

  1. Wenn Sie bei Fehler goto Label1 verwenden, wechselt die Prozedur in den Status „Ich behandle einen Fehler“, da eine Ausnahme ausgelöst wurde. Wenn in diesem Zustand eine andere label2-Anweisung „On Error Goto“ ausgeführt wird, geht sie NICHT zu label2, sondern löst einen Fehler aus, der an den Code übergeben wird, der die Prozedur aufgerufen hat.

  2. Sie können verhindern, dass sich eine Prozedur im Status „Ich behandle einen Fehler“ befindet, indem Sie die Ausnahme löschen (err auf nichts setzen, damit die Eigenschaft err.number 0 wird) Verwenden von

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

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

Es ist auch wichtig zu beachten, dass Err.Clear es auf Null zurücksetzt, aber es entspricht tatsächlich:

On Error Goto -1 On Error Goto 0 

dh Err.Clear entfernt Ein „On Error Goto“, das derzeit vorhanden ist. Daher ist es meistens am besten, Folgendes zu verwenden:

On Error Goto -1 

als Err.clear, das Sie häufig benötigen würden Schreiben

Err.Clear On Error Goto MyErrorHandlerLabel 

Ich verwende die oben genannten Techniken mit verschiedenen Beschriftungen, um die manchmal nützliche Funktionalität zu simulieren, die Visual Basic TRY CATCH-Blöcke bieten, von denen ich denke, dass sie ihren Platz beim Schreiben haben lesbarer Code.

Diese Technik erzeugt zwar ein paar mehr Codezeilen als eine nette VB-Try-Catch-Anweisung, aber es ist nicht zu chaotisch und ziemlich einfach, Sie zu finden r Kopf herum.

PS. Von Interesse 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 

Kommentare

  • Nur zwei Kommentare: 1 Warum um alles in der Welt würden Sie dies verwenden? 2 On Error Goto -1
  • Try / catch kann auch emuliert werden, indem der entsprechende Code mit On Error Resume Next und On Error GoTo 0 und Überprüfen der Err.Number. Das Obige ist etwas schwer zu befolgen, hat ein bisschen eine spaghetti Struktur.
  • Danke Rory, ich ‚ habe es geändert. Sie würden es verwenden oder aus demselben Grund würde jeder eine TRY CATCH-Anweisung in VB oder SQL Server verwenden. Das heißt, Sie können Ihren Code anders strukturieren. Das heißt, Sie können denselben Fehlerbehandler für viele Codezeilen verwenden, ohne die Zeilen in eine eigene Prozedur einfügen zu müssen.
  • Loannis. Ja, ich ‚ habe dies in der Vergangenheit für einzelne Codezeilen getan, für die eine Fehlerbehandlungsroutine erforderlich ist. Das heißt, eine Codezeile hat einen Fehlerbehandler. Mit TRY CATCH kann ein Codeblock (mit vielen Zeilen) in eine Prozedur mit dem eigenen Fehlerbehandler ‚ eingebettet werden. Ich verwende TRY CATCH häufig in SQL Server und da es ‚ auch in VB verfügbar ist, muss es einem allgemein nützlichen Zweck dienen. Zugegeben, diese Version ist etwas chaotisch.
  • @Loannis Was ist, wenn Sie mehrere Zeilen überspringen möchten, wenn Sie eine Fehlermeldung erhalten? Siehe meine Antwort für ein vereinfachtes Beispiel. Natürlich können Sie dies auch mit der regulären Fehlerbehandlung tun.

Antwort

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

RememberErrLine = Erl() 

Die Erl ist aus einem bestimmten Grund ein verstecktes Mitglied des Moduls VBA.Information. Sie gibt 0 zurück, es sei denn, der Fehler ist in einer nummerierten Zeile aufgetreten. Und wenn Sie in VBA Zeilennummern verwenden, leben Sie seit 25 Jahren in einer Höhle und verwenden wahrscheinlich GoSub -Anweisungen, anstatt Prozeduren zu schreiben. Zeilennummern werden unterstützt Aus Gründen der Legacy- / Abwärtskompatibilität , weil Code, der in den 1980er Jahren geschrieben wurde, diese erfordert .

Mir gefällt, wie Sie es selbst gesagt haben:

" THEREFORE KEEP THE CODE HERE VERY SIMPLE! 

..aber warum gilt das nicht für den Rest des Codes? Keine Beleidigung, aber dies ist eine Spaghetti-Logik, die in Prozeduren geschrieben ist, die eindeutig und schamlos gegen die Prinzip der Einzelverantwortung . Kein SRP-kompatibler Code würde jemals zwei solcher “ try-catch “ -Blöcke benötigen.

Dies riecht:

Case 0: " No Error, do Nothing 

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

Dieser riecht stinkt :

GoTo CatchBlock2_End: CatchBlock2_Start: 

Erstens ac olon (:), das keine Zeilenbezeichnung angibt, ist ein Befehlstrennzeichen .Es stellt sich heraus, dass eine neue Zeile auch ein “ Befehlstrennzeichen “ ist, also der Doppelpunkt am Ende von GoTo CatchBlock2_End ist völlig nutzlos und verwirrend, , insbesondere angesichts der Einrückungsstufe der GoTo-Anweisung .

Apropos GoTo

Neil Stephenson findet es süß, seine Labels“ dengo „ zu nennen

Mir gefällt nicht, wie ich zwischen Beschriftungen springen muss, um dem Code zu folgen. IMO ist es chaotisch und unnötig spaghettifiziert.


Gut, smartypants . Wie geht man dann sauber mit Fehlern in VBA um?

1. Schreiben Sie zunächst sauberen Code.

Befolgen Sie die Best Practices und schreiben Sie kleine Prozeduren, die eine Sache tun und es gut machen.

2. Schreiben Sie objektorientierten Code.

Abstraktion und Kapselung sind zwei der vier Säulen von OOP und sie werden in VBA vollständig unterstützt. Polymorphismus ist ebenfalls eine Option; nur eine ordnungsgemäße Vererbung ist ausgeschlossen, aber dies hindert einen nicht daran, Konzepte in zu abstrahieren Klassenmodule und Instanziieren spezialisierter Objekte.

In Standardmodulen (.bas) geschriebener Prozedurcode sollte winzige kleine öffentliche Methoden sein (Makro “ Hooks „), die die zum Ausführen der Funktionalität erforderlichen Objekte erstellen.

Wie hängt das überhaupt mit der ordnungsgemäßen Fehlerbehandlung zusammen?

3. Umfassen Sie die idiomatische Fehlerbehandlung, bekämpfen Sie sie nicht.

Angesichts des Codes, der die oben genannten Punkte erfüllt, gibt es keinen Grund, die Fehlerbehandlung nicht zu implementieren der idiomatische VBA-Weg.

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 ist analog zu einem “ try-catch-finally “ folgendermaßen:

  • Der Body ist der “ try “ Teil, der das tut, was der Methodenname sagt, und nichts weiter

CleanFail ist der “ catch “ Teil, der nur ausgeführt wird, wenn ein Fehler ausgelöst wird

  • CleanExit ist der “ schließlich “ Teil , der unabhängig davon ausgeführt wird, ob ein Fehler ausgelöst wurde oder nicht … es sei denn, Sie „werfen erneut . Wenn Sie jedoch einen Fehler in die Luft sprudeln lassen müssen, damit der aufrufende Code verarbeitet wird, sollten Sie nicht viel bereinigen. c Ode zur Ausführung, und Sie sollten einen sehr, sehr guten Grund dafür haben.
  • Wenn Ihre Unterroutine zur Fehlerbehandlung einen Fehler auslösen kann, halten Sie sich nicht an SRP. Das Schreiben in eine Protokolldatei ist beispielsweise ein eigenständiges Problem, das in ein Logger -Objekt abstrahiert werden sollte, das sich mit Protokollierungsproblemen befasst und Methoden bereitstellt, die ihre eigenen Fehler behandeln . Der Fehlerbehandlungs-Unterprogrammcode sollte trivial sein.

    Kommentare

    • Vielen Dank an @mat ‚ selbstgefällig Die Zeit, Kommentare hinzuzufügen, die mir wirklich geholfen haben, ich ‚ bin bereit für gewalttätige, aber humorvolle Kritik. Ich ‚ habe meinen Code überprüft und ich ‚ freue mich sagen zu können, dass die überwiegende Mehrheit an den von Ihnen skizzierten Prinzipien festhält. Ihre Erklärung war jedoch nützlich und hat mich zum Nachdenken und zur Erkenntnis gebracht, dass ich ‚ nicht zu schätzen wusste, dass VB- und SQL Server-TRY-CATCH-Anweisungen in jeder Prozedur nur einmal verwendet werden (ich dachte, sie wären eine bedeutet, dass Code nicht abstrahiert werden muss, um ihn besser lesbar zu machen). Wenn Sie weitere Kommentare zur ManageErrSource-Prozedur hinzufügen möchten, bin ich ‚ ganz Ohr …
    • @HarveyFrench I ‚ füge noch etwas hinzu, wenn ich die Gelegenheit dazu bekomme – hatte ‚ dieses nicht angeschaut 😉 Das Referenzieren und Verwenden der VBIDE-API erfordert spezielle Sicherheitseinstellungen, was nicht cool ist. Ich ‚ verwende TypeName(Me) als Quelle für benutzerdefinierte Fehler in Klassenmodulen und als einzige Möglichkeit für einen Fehler, zu wissen, welche Prozedur ausgeführt wird Es trat auf, den Prozedurnamen in eine lokale const zu codieren, idealerweise nicht zu weit von der Signatur der Methode ‚ entfernt. Ich mag die Call-Stack-Idee, aber ein Nachteil ist, dass Sie “ “ und “ pop „, wenn Sie eine Prozedur eingeben / beenden, andernfalls wird es zu einer Lüge.
    • Der Code, den ich von fmsinc.com erhalten habe, kommt viel herum von den Problemen, die ich ‚ hatte.Ich ‚ würde Ihre Meinung schätzen. Siehe hier codereview.stackexchange.com/questions/94498/… Ich schätze Ihre Zeit, da mich das antreibt Nüsse.

    Antwort

    Hören Sie Mat „sMug , 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 zunächst an, wie wir so etwas in VB.Net tun 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 ResumeNext. Schreiben Sie dies auf, weil es das nur Mal ist, dass ich es jemals sagen werde Recht auf 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 

    Die alternative Möglichkeit besteht darin, diese Logik zu integrieren, die ich für etwas sauberer halte und näher an der Redewendung Try...Catch, aber es kann schnell hässlich werden, wenn es missbraucht wird.

    On Error Resume Next foo = SomeMethodLikelyToRaiseAnError If Err.Number = ConstantValueForErrorWeExpected Then foo = someDefaultValue End If On Error Goto 0 

    Entweder ist eine idiomatischer Weg, um mit erwarteten Fehlern umzugehen, aber was auch immer Sie tun. Kümmern Sie sich nicht um Resume Next, bis Sie vollständig verstehen, was es tut und wann es angemessen ist (Mehr eine Warnung für zukünftige Leser als für Sie. Sie scheinen die Fehlerbehandlung in VB6 gründlich zu verstehen. Vielleicht etwas zu gut für Ihr eigenes Wohl.)

    Kommentare

    • Vielen Dank an @RubberDuck für Ihre nützlichen Kommentare. Um ehrlich zu sein, verwende ich “ Bei Fehler als nächstes “ etliche Prozeduraufrufe nach r in der Regel gibt es einen SELECT CASE, der auf einen Fehler reagiert. Der große Fehler, den ich mache, besteht darin, dass ich eine benutzerdefinierte Ausnahme in der Unterprozedur auslöse, um auftretende Situationen zu kennzeichnen (z. B. den Benutzer, der die Verarbeitung abbrechen möchte). Ich denke, ich sollte mehr Funktionen verwenden. Dies ist ein Hinweis darauf, dass meine allgemeine Codestruktur “ nicht ideal ist “ / arm und ich denke und ich muss dies ansprechen. Vielen Dank.
    • Sie ‚ haben einen großartigen Punkt erreicht @HarveyFrench. Ausnahmen sind außergewöhnliches Verhalten, nicht Kontrollfluss. Willkommen bei CR.
    • Ich ‚ würde mich sehr für Ihre Meinung zu dieser SO-Frage interessieren: stackoverflow. com / question / 31007009 / …
    • Der Code, den ich von fmsinc.com erhalten habe, umgeht viele der Probleme, die ich ‚ haben gehabt. Ich ‚ würde Ihre Meinung schätzen. Siehe hier codereview.stackexchange.com/questions/94498/…

    Antwort

    Diese Antwort soll das Try / Catch-Muster so vereinfachen, dass es leicht verständlich ist.

    Dies unterscheidet sich nicht wesentlich von Regelmäßige Inline-Fehlerbehandlung, außer dass mehrere Zeilen gleichzeitig übersprungen, ein Fehler behandelt und die reguläre Ausführung fortgesetzt werden kann. Dies ist ein sehr sauber strukturiertes Muster für die Behandlung eines Fehlers. Der Fluss bewegt sich sehr sauber von oben nach unten; Hier gibt es keinen Spaghetti-Code.

    Traditionell befindet sich der Fehlerbehandler unten. Aber das Try / Catch-Konstrukt ist so elegant. Es ist eine sehr strukturierte Methode zur Behandlung von Fehlern und sehr einfach zu befolgen. Dieses Muster versucht, dies auf sehr saubere und präzise Weise zu reproduzieren. Der Fluss ist sehr konsistent 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 verwaltet funktioniert dies ganz gut. Es ist ein sehr sauberes fließendes Muster, das überall reproduzierbar ist, wo es benötigt wird.

    Kommentare

    • @D_Bester, Vielen Dank für die Links und das einfache Beispiel. Ich ‚ lerne noch und fand Ihr Feedback hilfreich. Sie müssen jedoch eine “ hinzufügen. Bei Fehler Gehe zu 0 “ nach dem “ bei Fehler gehe zu -1 „. Auch beim Nachdenken denke ich, dass es besser ist, Err.Clear anstelle von “ zu verwenden. Bei Fehler zu -1 „, da dies deutlicher zeigt, was es passiert. Ich ‚ finde diese ganze Fehlerbehandlung in VBA ein bisschen schwarz.
    • @D_Bester. Bei der Reflexion ist Ihr Code in Ordnung, wenn Sie dem Benutzer nur eine Nachricht senden möchten, wenn ein Fehler auftritt. Was ist jedoch, wenn Sie den Fehler erneut auslösen möchten? Welches wird ein sehr häufiges Szenario sein. Erwägen.Wenn Ihr Code versucht hat, die Details eines Kunden ‚ nachzuschlagen, und ‚ sie aus einem UNERWARTETEN Grund nicht erhalten konnte. Sie müssten den Fehler erneut auslösen und den Code, der Ihren Code für die Suche verwendet, entscheiden lassen, was zu tun ist.
    • @HarveyFrench Wenn Sie den Fehler erneut auslösen möchten, verwenden Sie einfach ‚ Err.Raise ‚. Kein Problem, wenn der Code gut strukturiert ist und die Fehlerbehandlung im aufrufenden Code aktiviert ist.
    • @HarveyFrench Err.Clear und On Error Goto -1 sind NICHT gleichwertig. Siehe stackoverflow.com/a/30994055/2559297
    • Sie ‚ haben Recht nicht das gleiche sorry. Ich denke jedoch, dass der obige Code bei Fehler GoTo -1 immer noch durch Err.Clear ersetzt werden muss, andernfalls wird “ ‚ mehr Code ohne Fehlerbehandlung “ springt zu ErrHandler1, wenn ein Fehler auftritt.

    Antwort

    In Bezug auf „CleanExit“ und das Thema „Endlich“.

    Mats Mug schrieb:

    CleanExit ist die “ endlich “ Teil, der unabhängig davon ausgeführt wird, ob ein Fehler ausgelöst wurde oder nicht … es sei denn, Sie „werfen erneut.


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

    Verfahrensansatz

    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 Methodenkörper ein Fehler auftritt, der in CleanFail erneut ausgeführt werden muss, wird CleanExit nicht ausgeführt. A. t all und daher kann die Ressource nicht ordnungsgemäß geschlossen werden.

    Sicher, Sie könnten die Ressource auch im Fehlerhandler selbst schließen, aber dies könnte dazu führen, dass mehrere Codefragmente vorhanden sind, in denen die Ressourcenbehandlung ausgeführt wird / wird


    Mein Vorschlag ist, für jede Ressourcenbindungsnotwendigkeit ein benutzerdefiniertes Objekt zu verwenden:

    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 nach dem Auslösen des Fehlers außerhalb des Gültigkeitsbereichs liegt, wird seine Terminate-Methode automatisch ausgeführt, wodurch die erworbene Ressource ordnungsgemäß geschlossen / bereinigt wird.

    Eine Notwendigkeit weniger für ein „finally“ -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 protokolliert oder gar verschluckt werden?

    Sicherlich ist dies diskutierbar.

    Aber es ist wichtig für Aktivieren Sie einen Fehlerbehandler in dieser Methode, da meines Wissens jeder nicht behandelte Fehler in dieser Methode dazu führt, dass VBA die Ausführung unterbricht und seine Standard-Laufzeitfehlermeldung anzeigt.

    Antwort

    Um meinen vorherigen Beitrag zu verdeutlichen, die folgende Zeile aus HarveyFrenchs Code:

    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 mithilfe eines Tools automatisch hinzufügen Es gibt einige Tools, die dies tun können. Ich verwende ein Tool namens CodeLiner.

    Hier ist der Code mit den Zeilennummern, mit dem 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 

    Kommentare

    • Hi! Willkommen bei Code Review. Bitte fügen Sie Ihrer Antwort mehr Kontext hinzu: Erklären Sie, warum Ihr Vorschlag den Code des OP ‚ verbessert, oder gehen Sie detaillierter auf das ein, was Sie sagen möchten.

    Schreibe einen Kommentar

    Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.