Koden nedenfor er ret selvforklarende: bare kopier og indsæt det hele i et modul og kør det , det giver et par brugssager og mange forklarende kommentarer i teksten. (Det virker, men jeg er interesseret i at vide, hvad andre mennesker gør af det, og for eventuelle forslag, du måske vil komme med.)

De vigtigste fakta at forstå er:

  1. Når du bruger på fejl under Label1, går proceduren ind i tilstanden “I” m håndtering af en fejl “, da en undtagelse er hævet. Når en anden “On Error Goto” label2-sætning udføres, når den er i denne tilstand, går den IKKE til label2, men hæver og fejl, der sendes til den kode, der kaldte proceduren.

  2. Du kan stoppe en procedure, der er i “Jeg håndterer en fejl” -tilstand ved at rydde undtagelsen (indstille err til intet, så egenskaben err.number bliver 0) ved ved hjælp af

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

(BEMÆRK at On Error Goto 0 er forskellig fra ovenstående)

Det er også vigtigt at bemærke, at Err.Clear nulstiller det til nul, men det svarer faktisk til:

On Error Goto -1 On Error Goto 0 

dvs. Err.Clear fjerner en “On Error Goto”, der i øjeblikket er på plads. Så derfor er det mest bedst at bruge:

On Error Goto -1 

som ved at bruge Err.clear Du bliver ofte nødt til skriv

Err.Clear On Error Goto MyErrorHandlerLabel 

Jeg bruger de ovennævnte teknikker med forskellige etiketter til at simulere den undertiden nyttige funktionalitet, som Visual basic TRY CATCH-blokke giver, som jeg synes har deres plads i at skrive læsbar kode.

Denne teknik skaber ganske vist et par flere kodelinjer end en dejlig VB-prøvefangsterklæring, men den er ikke for rodet og ret let at få dig r gå rundt.

PS. Også af interesse kan være proceduren ManageErrSource, som gør egenskaben Err.Source til at gemme proceduren, hvor fejlen opstod.

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 

Kommentarer

  • Kun to kommentarer: 1 Hvorfor i alverden ville du bruge dette? 2 On Error Goto -1
  • Try / catch kan også emuleres ved at indpakke den relevante kode med On Error Resume Next og On Error GoTo 0 og kontrollere Err.Number. Ovenstående er noget vanskeligt at følge, har lidt af en spaghetti struktur ..
  • Tak Rory, jeg ‘ har ændret det. Du ville bruge det eller af samme grund, at nogen ville bruge en TRY CATCH-sætning i VB eller SQL Server. dvs. det lader dig strukturere din kode forskelligt. dvs. du kan bruge den samme fejlhåndterer til mange kodelinjer uden at skulle sætte linjerne i deres egen procedure.
  • Loannis. ja, jeg har ‘ gjort det tidligere for enkeltlinjer med kode, der har brug for en fejlhåndtering. Dvs. en linje kode har en fejlhåndterer. TRY CATCH tillader, at en blok kode (med mange linjer) integreres i en procedure med den ‘ s egen fejlhåndtering. Jeg bruger TRY CATCH meget i SQL Server, og da den ‘ er tilgængelig i VB, så skal den også tjene et generelt nyttigt formål. Denne version er ganske vist lidt rodet.
  • @Loannis Hvad hvis du vil springe over flere linjer, når du får en fejl. Se mit svar for et forenklet eksempel. Selvfølgelig kan du også gøre det med regelmæssig fejlhåndtering.

Svar

Problemet er, at runtime-fejl i VBA er ikke undtagelser , og fejlhåndtering i VBA har meget lidt til fælles med undtagelseshåndtering.

RememberErrLine = Erl() 

Erl -funktionen er et skjult medlem af VBA.Information -modulet af en grund – det returnerer 0, medmindre fejlen opstod på en nummereret linje. Og hvis du bruger linjenumre i VBA, har du boet i en hule i 25 år og bruger sandsynligvis GoSub udsagn i stedet for at skrive procedurer. Linjenumre understøttes af ældre / bagudkompatible grunde fordi kode skrevet i 1980erne krævede dem.

Jeg kan godt lide, hvordan du selv sagde det:

" THEREFORE KEEP THE CODE HERE VERY SIMPLE! 

..men hvorfor var det ikke det, der gjaldt for resten af koden? Ingen lovovertrædelse, men dette er spaghettilogik, skrevet i procedurer, der klart og skamløst overtræder Enkelt ansvarsprincip . Ingen SRP-kompatibel kode ville nogensinde have brug for to af sådanne ” try-catch ” blokke.

Dette lugter:

Case 0: " No Error, do Nothing 

Det betyder en af to ting: enten har du fejlhåndteringskode, der kører i ikke-fejlkontekster, eller du har død kode, der skal slettes.

Dette lugter reeks :

GoTo CatchBlock2_End: CatchBlock2_Start: 

For det første ac olon (:), der ikke angiver en linielabel , er en instruktionsadskiller .Vises, at en ny linje også er en ” instruktionsskiller “, så kolon i slutningen af GoTo CatchBlock2_End er fuldstændig ubrugelig og forvirrende, især givet indrykningsniveauet i GoTo-udsagnet .

Apropos GoTo

Neil Stephenson synes det er sødt at navngive sine etiketter” dengo “

Jeg kan ikke lide, hvordan jeg skal springe mellem etiketter for at følge koden. IMO det er rodet og unødvendigt spaghettificeret.


Fin, smartypants . Så hvordan håndterer man rent fejl i VBA?

1. Skriv ren kode med det første.

Overhold de bedste fremgangsmåder, og skriv små procedurer, der gør én ting, og gør det godt.

2. Skriv objektorienteret kode.

Abstraktion og indkapsling er to af de 4 søjler i OOP , og de “understøttes fuldt ud i VBA. Polymorfisme er også noget af en mulighed; kun korrekt arv er udelukket, men det forhindrer ikke en i at abstrahere begreber i klassemoduler og instantierende specialiserede objekter.

Procedurekode skrevet i standardmoduler (.bas) skal være små små offentlige metoder (makro ” kroge “), der skaber de objekter, der kræves for at køre funktionaliteten.

Så hvordan er det endog eksternt relateret til korrekt fejlhåndtering?

3. Omfavn idiomatisk fejlhåndtering, kæmp ikke med den.

Givet kode, der overholder ovenstående punkter, er der ingen grund til ikke at implementere fejlhåndtering den idiomatiske VBA-måde.

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 

Dette mønster er analogt med et ” try-catch-finally ” på følgende måde:

  • Kroppen er ” prøv ” del, det gør, hvad metodenavnet siger, og intet mere
  • CleanFail er ” fanger ” del, der kun kører, hvis der opstår en fejl
  • CleanExit er ” endelig ” del, der kører uanset om der blev rejst en fejl eller ej … medmindre du “gentager” . Men hvis du har brug for at sprænge en fejl for at opkaldskoden skal håndtere, skal du ikke have meget oprydning c ode at udføre, og du burde have en meget meget god grund til at gøre det.

Hvis din fejlhåndteringsundervisning kan rejse en fejl, overholder du ikke SRP. For eksempel er skrivning til en logfil en bekymring for sig selv, der skal abstraheres i et Logger objekt, der lever til at håndtere logningsproblemer og udsætter metoder, der håndterer deres egne fejl . Fejlhåndtering af underrutinkode skal være triviel.

Kommentarer

  • Tak @mat ‘ smug for at tage tiden til at tilføje kommentarer, der virkelig hjalp mig ‘ til voldelig, men humoristisk kritik. Jeg ‘ har gennemgået min kode, og jeg ‘ er glad for at kunne sige, at langt størstedelen overholder de rektorer, du beskriver. Din forklaring var dog nyttig, og det fik mig til at reflektere og indse, at jeg ikke ‘ ikke værdsætter, at VB og SQL Server TRY CATCH-udsagn kun bruges én gang i hver procedure (jeg troede, de var en betyder ikke at skulle trække kode ud for at gøre det mere læsbart). Hvis du har lyst til at tilføje nogle flere kommentarer om ManageErrSource-proceduren I ‘ m alle ører …
  • @HarveyFrench I ‘ ll tilføje noget mere, når jeg får en chance – havde ikke ‘ ikke set på denne 😉 henvisning og brug af VBIDE API kræver specielle sikkerhedsindstillinger, hvilket ikke er sejt. Jeg ‘ er kommet til at bruge TypeName(Me) som kilde til brugerdefinerede fejl i klassemoduler, og den eneste måde, hvorpå en fejl kan vide, hvilken procedure det opstod i, er at hårdkode procedurenavnet til en lokal const, ideelt set ikke alt for langt fra metoden ‘ s signatur. Jeg kan godt lide idéen om opkaldsstak, men en ulempe er, at du konsekvent skal ” skubbe ” og ” pop ” hver gang du går ind / afslutter en procedure, ellers bliver det en løgn.
  • Koden jeg modtog fra fmsinc.com kommer meget rundt af de problemer, jeg har ‘.Jeg ‘ Jeg værdsætter din mening. Se her codereview.stackexchange.com/questions/94498/… Jeg sætter pris på din tid, da dette kører mig nødder.

Svar

Lyt til Mat “sMug , men han dækkede ikke situationen, hvor du faktisk ved, hvordan man gendanner efter en fejl. For fuldstændighed, vil jeg gerne dække det.

Lad os se på, hvordan vi først ville gøre noget som dette i VB.Net.

Try foo = SomeMethodLikelyToThrowAnException Catch e As SomeException foo = someDefaultValue End Try " some more code 

Den idiomatiske måde at gøre dette på i VB6 er at ResumeNext. Skriv dette ned, fordi det er kun gang jeg nogensinde siger det “s ret til 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 

Den alternative måde er at integrere denne logik, som jeg synes er lidt renere og tættere på Try...Catch idiomet, men det kan blive grimt hurtigt, hvis det misbruges.

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

Enten er en idiomatisk måde at håndtere forventede fejl på, men uanset hvad du gør. Gør ikke med Resume Next, indtil du helt forstår, hvad det gør, og hvornår det er passende . (Mere en advarsel til fremtidige læsere end til dig. Du ser ud til at forstå grundigt fejlhåndtering i VB6. Måske lidt for godt til dit eget bedste.)

Kommentarer

  • Tak @RubberDuck for dine nyttige kommentarer. Når jeg er ærlig, finder jeg mig selv ved at bruge ” Ved fejl genoptages næste ” før en hel del procedureopkald afte r der der typisk er et SELECT CASE, der reagerer på enhver opstået fejl. Den store fejl, jeg indser, at jeg laver, er, at jeg rejser en brugerdefineret undtagelse i underproceduren for at markere situationer, der opstår (som brugeren, der anmoder om at annullere behandlingen). Jeg tynder, jeg skal bruge funktioner mere. Dette er en indikation af, at min generelle kodestruktur er ” ikke ideel ” / dårlig, og jeg tror, og jeg er nødt til at tage fat på dette. Tak.
  • Du ‘ har ramt et fantastisk punkt @HarveyFrench. Undtagelser er for ekstraordinær adfærd, ikke kontrol af flow. Velkommen til CR.
  • Jeg ‘ ville være meget interesseret i dine meninger om dette SO-spørgsmål: stackoverflow. com / spørgsmål / 31007009 / …
  • Koden, jeg modtog fra fmsinc.com, løser mange af de problemer, jeg ‘ har haft det. Jeg ‘ Jeg værdsætter din mening. Se her codereview.stackexchange.com/questions/94498/…

Svar

Dette svar er beregnet til at forenkle Try / Catch-mønsteret, så det er let forståeligt.

Dette er ikke meget forskelligt fra regelmæssig integreret fejlhåndtering bortset fra at den kan springe over flere linjer på én gang, håndtere en fejl og derefter genoptage regelmæssig udførelse. Dette er et meget rent struktureret mønster til håndtering af en fejl. Strømmen bevæger sig meget rent fra top til bund; ingen spaghetti-kode her.

Traditionelt er fejlhåndteringen placeret i bunden. Men Try / Catch-konstruktionen er så elegant. Det er en meget struktureret måde at håndtere fejl på og er meget let at følge. Dette mønster forsøger at gengive det på en meget ren kortfattet måde. Strømmen er meget ensartet og springer ikke fra sted til sted.

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 

Kilder:

Korrekt styret fungerer dette ganske pænt. Det er et meget rent flydende mønster, der kan reproduceres hvor som helst det er nødvendigt.

Kommentarer

  • @D_Bester, tak for linkene og det enkle eksempel. Jeg ‘ lærer stadig og fandt din feedback nyttig, men du bliver nødt til at tilføje en ” On Error Goto 0 ” efter ” om fejl goto -1 “. Også efterhånden synes jeg det er bedre at bruge Err.Clear i stedet for ” On Error Goto -1 ” da det tydeligere viser, hvad Det sker. Jeg ‘ finder hele denne fejlhåndtering i VBA lidt sort.
  • @D_Bester. Ved refleksion koder du fint, hvis alt, hvad du vil give brugeren en besked, når der opstår en fejl, men hvad hvis du vil gentage fejlen? Hvilket vil være et meget almindeligt scenario. Overveje.Hvis du kode forsøgte at slå en kunde ‘ s detaljer op, og den kunne ikke ‘ ikke få dem af en uventet årsag. Du bliver nødt til at hæve fejlen igen og lade koden, der bruger din kode til at foretage opslaget, beslutte, hvad de skal gøre.
  • @HarveyFrench Hvis du vil genophøje fejlen, skal du bare bruge ‘ Err.Raise ‘. Intet problem der, forudsat at koden er struktureret godt, og fejlhåndtering er aktiveret i opkaldskoden.
  • @HarveyFrench Err.Clear og On Error Goto -1 er IKKE ækvivalente. Se stackoverflow.com/a/30994055/2559297
  • Du ‘ har ret, de har ret ikke det samme ked af det. Men jeg tror, at koden ovenfor stadig har brug for Fejl GoTo -1 erstattet med Err.Ryd ellers ” ‘ mere kode uden fejlhåndtering ” hopper til ErrHandler1, hvis der opstår en fejl.

Svar

Med hensyn til “CleanExit” og “Endelig” emne.

Mats s Mug skrev:

CleanExit er ” til sidst ” del, der kører, uanset om der er rejst en fejl eller ej … medmindre du genkaster.


En sådan situation kan f.eks. forekomme i denne procedurekode:

Proceduremetode

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 her : Hvis der opstår en fejl i metodekroppen, som skal genoprettes i CleanFail, udføres CleanExit ikke en t alle, og derfor kan ressourcen ikke lukkes ordentligt.

Sikker på, du kan lukke ressourcen også i selve fejlhåndteringen, men det kan føre til at have flere kodefragmenter, hvor ressourcehåndtering vil være / har skal gøres.


Mit forslag er at bruge et brugerdefineret objekt til hver ressourcebindende nødvendighed:

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 

Objektorienteret tilgang

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 

Mulighed : Da det brugerdefinerede objekt vil være uden for rækkevidde, efter fejlen er hævet, udføres dets afslutningsmetode automatisk, hvilket får den tilegnede ressource til at blive lukket / renset ordentligt.

En nødvendighed mindre for en “endelig” blok.


Fejlhåndtering i Afslut metode

Efter min mening er det kontekstafhængigt hvordan en fejl håndteres i afslutningsmetoden i den tilpassede klasse. Måske skal det logges et sted yderligere eller overhovedet sluges ned?

Dette kan bestemt diskuteres.

Men det er vigtigt for aktiver en fejlhåndterer i denne metode, for så vidt jeg ved, vil enhver uhåndteret fejl i denne metode få VBA til at bryde udførelsen og vise sin standard runtime error messagebox.

Svar

For at afklare mit tidligere indlæg, følgende linje fra HarveyFrenchs kode:

RememberErrLine = Erl() 

fungerer ikke, medmindre linjenumre er blevet føjet til hver linje kode. I stedet for manuelt at skrive linjenumre, hvilket er alt for kedeligt, kan du bruge et værktøj til automatisk at tilføje linjenumre. Der er et par værktøjer derude, der kan gøre dette, jeg bruger et kaldet CodeLiner.

Her er koden med linjenumre, som giver Erl() mulighed for at arbejde med succes :

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 

Kommentarer

  • Hej! Velkommen til Code Review. Tilføj venligst mere kontekst til dit svar: forklar, hvorfor dit forslag vil forbedre OP ‘ s kode, eller gå måske nærmere ind på, hvad du prøver at sige.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *