Codul de mai jos este destul de explicativ: copiați și lipiți totul într-un modul și rulați-l , oferă câteva cazuri de utilizare și multe comentarii explicative în text. (Funcționează, dar sunt interesat să știu ce fac alții despre asta și pentru orice sugestii pe care ați dori să le faceți.)

Cele mai importante fapte de realizat sunt:

  1. Când utilizați eroarea, mergeți la Label1, procedura intră într-o stare de „I” m care gestionează o eroare, deoarece a fost ridicată o excepție. Când se află în această stare, dacă se execută o altă instrucțiune „On Error Goto” label2, NU va trece la label2, ci crește și eroare care este trecută la codul care a apelat procedura.

  2. Puteți opri ca o procedură să se afle în starea „Eu” gestionez o eroare ”ștergând excepția (setând eroarea la nimic, astfel încât proprietatea err.number devine 0) prin folosind

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

(Rețineți că On Error Goto 0 este diferit de cele de mai sus)

De asemenea, este important să rețineți că Err.Clear îl resetează la zero, dar este de fapt echivalent cu:

On Error Goto -1 On Error Goto 0 

adică Err.Clear elimină un „On Error Goto” care este în prezent în vigoare. Prin urmare, este cel mai bine să folosiți:

On Error Goto -1 

ca folosind Err.clear De care ar trebui adesea să write

Err.Clear On Error Goto MyErrorHandlerLabel 

Folosesc tehnicile de mai sus cu diverse etichete pentru a simula funcționalitatea uneori utilă pe care o oferă blocurile TRY CATCH Visual basic, care cred că își au locul lor în scris cod lizibil.

Desigur, această tehnică creează câteva mai multe linii de cod decât o declarație drăguță VB try catch, dar nu este prea dezordonată și destul de ușor de obținut r cap în jur.

PS. De asemenea, ar putea fi de interes procedura ManageErrSource care face ca proprietatea Err.Source să stocheze procedura în care a apărut eroarea.

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 

Comentarii

  • Doar două comentarii: 1 De ce naiba ați folosi acest lucru? 2 On Error Goto -1
  • Try / catch poate fi, de asemenea, imitat prin împachetarea codului relevant cu On Error Resume Next și On Error GoTo 0 și verificarea Err.Number. Cele de mai sus sunt oarecum greu de urmărit, au o structură puțin spaghetti ..
  • Mulțumesc Rory, eu ‘ l-am schimbat. L-ați folosi sau același motiv pentru care oricine ar folosi o instrucțiune TRY CATCH în VB sau SQL Server. adică vă permite să vă structurați codul în mod diferit. adică puteți utiliza același handler de erori pentru mai multe linii de cod fără a fi nevoie să puneți liniile în propria lor procedură.
  • Loannis. da, ‘ am făcut asta în trecut, pentru liniile unice de cod care necesită un instrument de gestionare a erorilor. Adică o linie de cod are un singur gestionar de erori. TRY CATCH permite ca un bloc de cod (cu mai multe linii) să fie încorporat într-o procedură cu propriul său instrument de gestionare a erorilor ‘. Folosesc TRY CATCH foarte mult în SQL Server și deoarece ‘ este disponibil și în VB, trebuie să servească unor scopuri utile în general. Desigur, această versiune este puțin dezordonată.
  • @Loannis Ce se întâmplă dacă doriți să omiteți mai multe rânduri când primiți o eroare. Vedeți răspunsul meu pentru un exemplu simplificat. Desigur, puteți face acest lucru și cu gestionarea regulată a erorilor.

Răspuns

Problema este că erorile de execuție din VBA nu sunt excepții , iar gestionarea erorilor în VBA are foarte puțin în comun cu gestionarea excepțiilor.

RememberErrLine = Erl() 

Erl funcția este un membru ascuns al modulului VBA.Information dintr-un motiv – returnează 0, cu excepția cazului în care eroarea a apărut pe o linie numerotată. Și dacă utilizați numere de linie în VBA, locuiți într-o peșteră de 25 de ani și probabil că utilizați instrucțiuni GoSub în loc de a scrie proceduri. Numerele de linie sunt acceptate din motive vechi / de compatibilitate cu versiunile anterioare , deoarece codul scris în 1980 „le cerea .

Îmi place cum ați spus-o singur:

" THEREFORE KEEP THE CODE HERE VERY SIMPLE! 

..dar de ce nu se aplică restului codului? Fără supărare, dar aceasta este logica spaghetelor, scrisă în proceduri care încalcă în mod clar și nerușinat Principiul responsabilității unice . Niciun cod conform SRP nu ar avea nevoie de două dintre aceste blocuri ” try-catch „.

Miroase:

Case 0: " No Error, do Nothing 

Înseamnă unul dintre cele două lucruri: fie aveți un cod de gestionare a erorilor care rulează în contexte fără erori, sau aveți un cod mort care ar trebui șters.

Acest miroase reeks :

GoTo CatchBlock2_End: CatchBlock2_Start: 

În primul rând, ac olon (:) care nu specifică o etichetă de linie , este un separator de instrucțiuni .Se pare că o linie nouă este, de asemenea, un separator de instrucțiuni ” „, deci colonul de la sfârșitul lui GoTo CatchBlock2_End este complet inutil și confuz, având în vedere, în special, nivelul de indentare al declarației GoTo .

Apropo de GoTo

Neil Stephenson consideră că este drăguț să-i numim etichetele„ dengo ”

Nu-mi place cum trebuie să trec între etichete pentru a urma codul. IMO este este dezordonat și spaghetat inutil.


Bine, smartypants . Deci, cum se gestionează erorile în VBA atunci?

1. Scrieți cod curat în primul rând.

Respectați cele mai bune practici și scrieți mici proceduri care fac un lucru și o fac bine.

2. Scrieți cod orientat pe obiecte.

Abstracție și încapsulare sunt doi dintre cei 4 piloni ai POO , și sunt „pe deplin acceptate în VBA. Polimorfismul este, de asemenea, oarecum o opțiune; numai moștenirea adecvată este exclusă, dar asta nu împiedică pe cineva să abstractizeze concepte în module de clasă și instanțierea obiectelor specializate.

Codul de procedură scris în module standard (.bas) ar trebui să fie mici metode publice (macro ” cârlige „) care creează obiectele necesare pentru a rula funcționalitatea.

Deci, ce legătură există chiar și de la distanță cu gestionarea corectă a erorilor?

3. Îmbrățișați gestionarea erorilor idiomatice, nu vă combateți.

Având în vedere codul care respectă punctele de mai sus, nu există niciun motiv pentru a nu implementa gestionarea erorilor drumul VBA idiomatic.

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 

Acest model este analog cu un ” try-catch-end ” în modul următor:

  • Corpul este ” încercați ” partea, care face ceea ce spune numele metodei și nimic mai mult
  • CleanFail este ” catch ” parte, care se execută numai dacă apare o eroare
  • CleanExit este partea ” în cele din urmă „, care rulează indiferent dacă a fost ridicată sau nu o eroare … cu excepția cazului în care „re retrageți . Dar dacă trebuie să eliminați o eroare pentru codul de apel pe care să îl gestionați, nu ar trebui să aveți multă curățare oda de executat și ar trebui să aveți un motiv foarte foarte foarte bun pentru a face acest lucru.

Dacă subrutina dvs. de gestionare a erorilor poate ridica o eroare, atunci nu respectați SRP. De exemplu, scrierea într-un fișier jurnal este o preocupare proprie, care ar trebui să fie extrasă într-un obiect Logger care trăiește pentru a face față problemelor de jurnalizare și expune metode care gestionează propriile erori. . Codul subrutinei de gestionare a erorilor ar trebui să fie banal.

Comentarii

  • Mulțumim @mat ‘ smug pentru că ai luat timpul pentru a adăuga comentarii care m-au ajutat într-adevăr mă ‘ mă pregătesc pentru critici violente, dar pline de umor. ‘ am examinat codul meu și ‘ mă bucur să spun că marea majoritate aderă la directorii pe care îi descrieți. Explicația ta a fost utilă și mi-a făcut să reflectez și să realizez că nu ‘ am apreciat că instrucțiunile TRB CATCH VB și SQL Server sunt folosite o singură dată în fiecare procedură (am crezut că sunt înseamnă că nu trebuie să extrageți codul pentru a-l face mai ușor de citit). Dacă doriți să mai adăugați câteva comentarii despre procedura ManageErrSource I ‘ m toate urechile …
  • @HarveyFrench I ‘ Voi adăuga mai multe când am o șansă – nu m-am uitat la acesta 😉 pentru a face referință și a folosi API-ul VBIDE necesită setări speciale de securitate, ceea ce nu este grozav. Am ‘ am ajuns să folosesc TypeName(Me) ca sursă pentru erori personalizate în modulele de clasă și singura modalitate pentru o eroare de a ști ce procedură a avut loc în, este de a codifica în mod dur numele procedurii într-o const locală, în mod ideal nu prea departe de metoda ‘. Îmi place ideea stivei de apeluri, dar un dezavantaj este că trebuie să ” împingeți în mod constant ” și ” pop ” ori de câte ori introduceți / ieșiți dintr-o procedură, altfel devine o minciună.
  • Codul pe care l-am primit de la fmsinc.com se învârte mult dintre problemele pe care le-am avut ‘.’ vă apreciez părerea. Vedeți aici codereview.stackexchange.com/questions/94498/… Apreciez timpul acordat, deoarece acest lucru mă conduce nuci.

Răspuns

Ascultă Mat „sMug , dar nu a acoperit situația în care știți efectiv să vă recuperați după o eroare. Pentru completare, aș vrea să acoperi asta.

Să vedem cum am face mai întâi așa ceva în VB.Net.

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

Modul idiomatic de a face acest lucru în VB6 este ResumeNext. Scrieți acest lucru, pentru că este numai când îl voi spune vreodată dreptul la 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 

Modalitatea alternativă este de a încorpora această logică, care cred că este un pic mai curată și mai aproape de expresia Try...Catch, dar poate deveni urât rapid dacă o abuzezi.

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

Oricare este un mod idiomatic de a face față erorilor așteptate, dar orice ai face. Nu te deranja cu Resume Next până când nu înțelegi complet ceea ce face și când este potrivit . (Mai mult un avertisment pentru viitorii cititori decât pentru dvs. Se pare că înțelegeți bine gestionarea erorilor în VB6. Poate că este prea bine pentru binele dvs.)

Comentarii

  • Vă mulțumim @RubberDuck pentru comentariile dvs. utile. Fiind sincer, mă găsesc folosind ” On Error reluare următoare ” înainte destul de multe apeluri de procedură după r în care există de obicei un SELECT CASE care răspunde la orice eroare ridicată. Marea greșeală pe care mi-o dau seama că o fac este că ridic o excepție definită de utilizator în procedura secundară pentru a semnaliza situațiile care apar (cum ar fi utilizatorul care solicită anularea procesării). Subțire, ar trebui să folosesc mai multe funcții. Aceasta este o indicație că structura mea generală de cod este ” nu ideală ” / slabă și cred că trebuie să abordez acest lucru. Mulțumim.
  • Ați ‘ atins un punct excelent @HarveyFrench. Excepțiile sunt pentru comportamentul excepțional , nu pentru fluxul de control. Bine ați venit la CR.
  • Aș fi foarte interesat de ‘ părerile dvs. cu privire la această întrebare SO: stackoverflow. com / questions / 31007009 / …
  • Codul pe care l-am primit de la fmsinc.com cuprinde multe dintre problemele pe care le ‘ am avut. ‘ vă apreciez părerea. Vedeți aici codereview.stackexchange.com/questions/94498/…

Răspuns

Acest răspuns este destinat să simplifice modelul Try / Catch pentru a fi ușor de înțeles.

Acest lucru nu este foarte diferit de gestionarea regulată a erorilor în linie, cu excepția faptului că poate sări peste mai multe rânduri simultan, să gestioneze o eroare și apoi să reia executarea regulată. Acesta este un model foarte bine structurat pentru gestionarea unei erori. Debitul se mișcă foarte curat de sus în jos; aici nu există cod spaghetti.

În mod tradițional, gestionarul de erori este plasat în partea de jos. Dar construcția Try / Catch este atât de elegantă. Este „un mod foarte structurat de gestionare a erorilor și este foarte ușor de urmat. Acest model încearcă să reproducă acest lucru într-un mod foarte curat și concis. Fluxul este foarte consistent și nu sare dintr-un loc în altul.

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 

Surse:

Gestionat corect, acest lucru funcționează destul de frumos. Este un model foarte curat, care este reproductibil oriunde este nevoie.

Comentarii

  • @D_Bester, Vă mulțumim pentru linkuri și pentru exemplul simplu. ‘ Încă învăț și am găsit feedback-ul dvs. util, totuși va trebui să adăugați un ” la Eroare Goto 0 ” după ” la Eroare la -1 „. De asemenea, la reflecție, cred că este mai bine să utilizați Err.Clear în loc de ” La Eroare Mergeți -1 ” deoarece arată mai clar ce se întâmplă. ‘ găsesc că întreaga gestionare a erorilor în VBA este un pic negru.
  • @D_Bester. La reflecție, codul este în regulă dacă tot ceea ce doriți să transmiteți utilizatorului un mesaj atunci când apare o eroare, dar dacă doriți să ridicați din nou eroarea? Care va fi un scenariu foarte comun. Considera.Dacă codul încerca să caute detaliile unui client ‘ și nu ar putea ‘ să le obțină dintr-un motiv NEAȘTEPTAT. Trebuie să ridicați din nou eroarea și să lăsați codul care folosește codul dvs. să efectueze căutarea să decidă ce să faceți.
  • @HarveyFrench Dacă doriți să ridicați din nou eroarea, folosiți doar ‘ Err.Raise ‘. Nu există nicio problemă, presupunând că codul este bine structurat și că gestionarea erorilor este activată în codul de apel.
  • @HarveyFrench Err.Clear și On Error Goto -1 NU sunt echivalente. Vedeți stackoverflow.com/a/30994055/2559297
  • Aveți ‘ dreptate, au dreptate nu la fel îmi pare rău. Dar cred că codul de mai sus are încă nevoie de On Error GoTo -1 înlocuit cu Err. Ștergeți în caz contrar ” ‘ mai mult cod fără a gestiona erorile ” va trece la ErrHandler1 dacă apare o eroare.

Răspuns

În ceea ce privește subiectul „CleanExit” și subiectul „În sfârșit”.

Mugul lui Mat ”a scris:

CleanExit este ” în cele din urmă ” parte, care rulează indiferent dacă a fost ridicată sau nu o eroare … cu excepția cazului în care „retrimiteți.


O astfel de situație ar putea apărea, de exemplu, în acest cod procedural:

Abordare procedurală

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ă aici : În cazul în care apare o eroare în corpul metodelor care trebuie să fie reorientat în CleanFail, CleanExit nu va fi executat un t all și, prin urmare, resursa nu poate fi „închisă corect”.

Sigur, ați putea închide resursa și în gestionatorul de erori în sine, dar acest lucru ar putea duce la a avea mai multe fragmente de cod în care gestionarea resurselor va fi / are de făcut.


Sugestia mea este să folosiți un obiect personalizat pentru fiecare necesitate de legare a resurselor:

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 

Abordare orientată pe obiecte

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 

Oportunitate : Deoarece obiectul personalizat va fi în afara domeniului de aplicare după ce eroarea este ridicată, metoda sa de Terminare va fi executată automat, ceea ce face ca resursa achiziționată să fie închisă / curățată corect.

O necesitate mai mică pentru un bloc „în sfârșit”.


Tratarea erorilor în metoda Terminate

În opinia mea, este dependent de context cum va fi tratată o eroare în metoda Terminare a clasei personalizate. Poate că ar trebui să fie înregistrat undeva suplimentar sau chiar înghițit deloc?

Cu siguranță acest lucru este discutabil.

Dar este esențial pentru activați un instrument de gestionare a erorilor în această metodă, deoarece, din câte știu, orice eroare nesoluționată din această metodă va face ca VBA să întrerupă execuția și să afișeze caseta de mesaje standard de eroare în timpul rulării. p>

Răspuns

Pentru a clarifica postarea mea anterioară, următoarea linie din codul HarveyFrench:

RememberErrLine = Erl() 

nu va funcționa decât dacă numerele de linie au fost adăugate la fiecare linie de cod. În loc să tastați manual numerele de linie, ceea ce este mult prea obositor, puteți utiliza un instrument pentru a adăuga automat numerele de linie. Acolo sunt câteva instrumente care pot face acest lucru, folosesc unul numit CodeLiner.

Iată codul cu numere de linie, care va permite Erl() să funcționeze cu 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 

Comentarii

  • Bună ziua! Bine ați venit la Code Review. Vă rugăm să adăugați mai mult context la răspunsul dvs.: explicați de ce sugestia dvs. va îmbunătăți codul OP ‘ sau, poate, accesați mai multe detalii despre ceea ce încercați să spuneți.

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *