아래 코드는 자명합니다. 모두 복사하여 모듈에 붙여넣고 실행하기 만하면됩니다. , 텍스트에 몇 가지 사용 사례와 많은 설명 주석을 제공합니다. (효과가 있지만 다른 사람들이 어떤 제안을하고 싶은지 알고 싶습니다.)

가장 중요한 사실은 다음과 같습니다.

  1. 오류에서 사용하면 Label1로 이동합니다. 프로시 저는 예외가 발생하여 “오류를 처리하고 있습니다”상태가됩니다. 이 상태에서 다른 “On Error Goto”label2 문이 실행되면 label2로 이동하지 않고 프로 시저를 호출 한 코드에 전달되는 오류가 발생합니다.

  2. 예외를 지우면 (err.number 속성이 0이되도록 err을 아무것도 설정하지 않음) “I”m handling an error “상태에있는 프로 시저를 중지 할 수 있습니다. 사용

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

(On Error Goto 0는 위와 다릅니다)

또한 중요한 점은 Err.Clear가 0으로 재설정하지만 실제로는 다음과 같습니다.

On Error Goto -1 On Error Goto 0 

ie Err.Clear는 현재 위치에있는 “On Error Goto”이므로 다음을 사용하는 것이 가장 좋습니다.

On Error Goto -1 

Err.clear를 사용할 때 쓰기

Err.Clear On Error Goto MyErrorHandlerLabel 

위의 기술을 다양한 레이블과 함께 사용하여 Visual basic TRY CATCH 블록이 제공하는 유용한 기능을 시뮬레이션합니다. 읽을 수있는 코드.

분명히이 기술은 멋진 VB try catch 문보다 몇 줄의 코드를 생성하지만 너무 지저분하지 않고 쉽게 얻을 수 있습니다. r 머리 주위.

PS. Err.Source 속성에 오류가 발생한 프로 시저를 저장하는 ManageErrSource 프로시 저도 관심을 가질 수 있습니다.

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 

Comments

  • 단 두 개의 댓글 : 1 도대체 왜 이것을 사용 하시겠습니까? 2 On Error Goto -1
  • 관련 코드를 On Error Resume NextOn Error GoTo 0Err.Number 확인. 위의 내용은 따르기가 다소 어렵고 약간의 spaghetti 구조가 있습니다.
  • 감사합니다. Rory, I ‘ 변경했습니다. 이를 사용하거나 다른 사람이 VB 또는 SQL Server에서 TRY CATCH 문을 사용하는 것과 동일한 이유를 사용합니다. 즉, 코드를 다르게 구성 할 수 있습니다. 즉, 자체 프로 시저에 줄을 넣지 않고도 여러 줄의 코드에 대해 동일한 오류 처리기를 사용할 수 있습니다.
  • Loannis. 예, ‘ 과거에 오류 처리기가 필요한 한 줄의 코드에 대해이 작업을 수행했습니다. 즉, 한 줄의 코드에는 하나의 오류 처리기가 있습니다. TRY CATCH를 사용하면 코드 블록 (여러 줄)을 자체 오류 처리기를 사용하여 프로 시저에 삽입 할 수 있습니다. ‘ 저는 SQL Server에서 TRY CATCH를 많이 사용하며 VB에서도 사용할 수있는 ‘이므로 일반적으로 유용한 목적을 제공해야합니다. 확실히이 버전은 약간 지저분합니다.
  • @Loannis 오류가 발생했을 때 여러 줄을 건너 뛰고 싶다면 어떨까요? 간단한 예는 내 대답을 참조하십시오. 물론 일반적인 오류 처리로도 그렇게 할 수 있습니다.

답변

문제는 VBA는 예외 가 아니며 VBA의 오류 처리는 예외 처리와 공통점이 거의 없습니다.

RememberErrLine = Erl() 

Erl 함수는 이유 때문에 VBA.Information 모듈의 숨겨진 멤버입니다. 번호가 매겨진 줄에 오류가 발생하지 않는 한 0을 반환합니다. 그리고 “VBA에서 줄 번호를 사용하는 경우 25 년 동안 동굴에서 살았으며 절차를 작성하는 대신 GoSub 문을 사용하고있을 것입니다. 줄 번호가 지원됩니다. 레거시 / 이전 버전과의 호환성을 위해 1980 년대에 작성된 코드가 필요 했기 때문입니다.

당신이 직접 말한 방식이 마음에 듭니다.

" THEREFORE KEEP THE CODE HERE VERY SIMPLE! 

.. 그러나 이것이 나머지 코드에 적용되지 않는 이유는 무엇입니까? 위법 행위는 아니지만 이것은 명확하고 뻔뻔스럽게 단일 책임 원칙 . SRP 준수 코드에는 이러한 ” try-catch ” 블록 중 두 개가 필요하지 않습니다.

냄새 :

Case 0: " No Error, do Nothing 

두 가지 중 하나를 의미합니다. 오류가 아닌 컨텍스트에서 실행되는 오류 처리 코드가 있거나 또는 삭제해야 할 죽은 코드가 있습니다.

냄새 좋아요 :

GoTo CatchBlock2_End: CatchBlock2_Start: 

먼저, ac “ 줄 레이블 을 지정하지 않는 olon (:)은 지침 구분자 입니다. 새 줄 은 ” 명령 구분자 “이기도하므로 GoTo CatchBlock2_End는 완전히 쓸모없고 혼란 스럽습니다. 특히 GoTo 문의 들여 쓰기 수준을 고려할 때 .

GoTo

Neil Stephenson은 자신의 레이블에”dengo “라는 이름을 지정하는 것이”귀엽다고 생각합니다. “

코드를 따르기 위해 레이블 사이를 이동하는 방법이 마음에 들지 않습니다. IMO는 지저분하고 불필요하게 스파게티 화되었습니다.


좋아요, 똑똑한 바지 . 그렇다면 VBA의 오류를 어떻게 깔끔하게 처리합니까?

1. 처음부터 깨끗한 코드를 작성하세요.

우수 사례를 준수하고 한 가지를 잘 수행하는 작은 절차를 작성하세요.

2. 객체 지향 코드 작성.

추상화 캡슐화 는 OOP의 4 가지 기둥 중 두 가지입니다. 이며 “VBA에서 완전히 지원됩니다. 다형성 도 다소 옵션입니다. 적절한 상속 만 배제되지만 이것이 개념을 추상화하는 것을 방해하지는 않습니다. 클래스 모듈 및 특수 개체 인스턴스화.

표준 모듈 (. bas)으로 작성된 절차 코드는 아주 작은 공용 메서드 여야합니다 (매크로 ” 후크 “) 기능을 실행하는 데 필요한 개체를 생성합니다.

그렇다면 원격으로도 적절한 오류 처리와 어떤 관련이 있을까요?

3. 관용적 인 오류 처리를 받아들이고 싸우지 마십시오.

위의 사항을 준수하는 코드가 주어지면 오류 처리를 구현하지 않을 이유가 없습니다. 관용적 VBA 방식입니다.

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 

이 패턴은 ” try-catch-finally “는 다음과 같습니다.

  • 본문은 ” 시도 ” 부분, 메소드 이름이 말하는대로 수행
  • CleanFail는 catch ” 부분, 오류가 발생한 경우에만 실행
  • CleanExit는 ” 마지막으로 ” 부분으로, 오류 발생 여부에 관계없이 실행됩니다.

em> … 다시 던지는 경우가 아니면 처리 할 호출 코드에 대한 오류를 표시해야하는 경우 정리를 많이하지 않아야합니다. 실행해야합니다. 실행해야 할 아주 좋은 이유가 있어야합니다.

오류 처리 서브 루틴이 오류를 일으킬 수 있다면 SRP를 준수하지 않는 것입니다. 예를 들어, 로그 파일에 쓰는 것은 그 자체의 문제이며 로깅 문제를 처리하기 위해 살아있는 일부 Logger 객체로 추상화되어야하며 자체 오류를 처리하는 메서드를 노출해야합니다. . 오류 처리 서브 루틴 코드는 사소한 것이어야합니다.

댓글

  • @mat에게 감사합니다. ‘ smug 정말 도움이 된 댓글을 추가 할 시간이되었습니다. ‘ 폭력적이지만 유머러스 한 비판을 받고 있습니다. 저는 ‘ 제 코드를 검토했으며 ‘ 대부분이 귀하가 설명하는 원칙을 준수한다고 말하게되어 기쁩니다. 귀하의 설명은 유용했으며 VB 및 SQL Server TRY CATCH 문이 각 절차에서 한 번만 사용된다는 것을 ‘ 감사하지 않았 음을 반영하고 깨달았습니다. 가독성을 높이기 위해 코드를 추상화 할 필요가 없음을 의미합니다.) ManageErrSource 프로 시저에 대한 의견을 더 추가하고 싶다면 ‘ m all ears …
  • @HarveyFrench I ‘ 나는 기회가있을 때 더 추가 할 것입니다. ‘ 이것을 보지 않았고 😉 VBIDE API를 참조하고 사용하려면 특별한 보안 설정이 필요합니다. 저는 ‘ TypeName(Me)를 클래스 모듈의 사용자 지정 오류에 대한 소스로 사용하고 오류가 어떤 절차를 알 수있는 유일한 방법입니다. 그것은 프로 시저 이름을 로컬 const에 하드 코딩하는 것입니다. 이상적으로는 메소드 ‘의 서명에서 그리 멀지 않습니다. 콜 스택 아이디어가 마음에 들지만 한 가지 단점은 지속적으로 ” 푸시 ” 및 ” pop ” 절차를 입력 / 종료 할 때마다 거짓말이됩니다.
  • fmsinc.com에서받은 코드가 많이 사용됩니다. 제가 ‘ 문제를 겪고 있습니다.저는 ‘ 당신의 의견을 소중하게 생각합니다. 여기를 참조하십시오. codereview.stackexchange.com/questions/94498/ … 시간을 내 주셔서 감사합니다. 견과류.

답변

Mat “sMug 듣기 그러나 그는 오류에서 복구하는 방법을 실제로 알고있는 상황을 다루지 않았습니다. 완전성을 위해 그것을 다루고 싶습니다.

먼저 VB.Net에서 이와 같은 작업을 수행하는 방법을 살펴 보겠습니다.

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

VB6에서이를 수행하는 관용적 인 방법은 ResumeNext입니다. 이 내용을 적어 두십시오. 시간이기 때문입니다. 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 

다른 방법은이 논리를 인라인하는 것입니다. Try...Catch 관용구에 더 가깝지만 남용하면 추악해질 수 있습니다.

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

둘 중 하나는 예상되는 오류를 처리하는 관용적 인 방법이지만 수행하는 작업은 무엇이든 상관 없습니다. 완전히 수행하는 작업과 적절한시기를 이해할 때까지 Resume Next에 신경 쓰지 마십시오. . (당신보다 미래의 독자들에게 더 많은 경고입니다. 당신은 VB6의 오류 처리를 완전히 이해하고있는 것 같습니다. 아마도 당신의 이익을 위해 너무 잘할 것입니다.)

댓글

  • 유용한 댓글에 대해 @RubberDuck에게 감사드립니다. 솔직히 저는 ” On Error resume next “를 사용하고 있습니다. 꽤 많은 절차 호출 후 r 일반적으로 발생한 오류에 응답하는 SELECT CASE가 있습니다. 내가 저지른 큰 실수는 발생하는 상황에 플래그를 지정하기 위해 하위 프로 시저에서 사용자 정의 예외를 발생시키는 것입니다 (예 : 사용자가 처리 취소를 요청하는 경우). 나는 기능을 더 많이 사용한다고 생각합니다. 이것은 내 일반 코드 구조가 ” 이상적이지 않음 ” / poor라는 표시이며이 문제를 해결해야한다고 생각합니다. 감사합니다.
  • ‘ @HarveyFrench에서 좋은 점수를 받았습니다. 예외는 제어 흐름이 아닌 예외 동작에 대한 것입니다. CR에 오신 것을 환영합니다.
  • ‘ SO 질문에 대한 귀하의 의견에 매우 관심이 있습니다. stackoverflow. com / questions / 31007009 / …
  • fmsinc.com에서받은 코드는 많은 문제를 해결합니다. ‘이 있습니다. 저는 ‘ 당신의 의견을 소중하게 생각합니다. 여기를 참조하십시오. codereview.stackexchange.com/questions/94498/ …

답변

이 답변은 Try / Catch 패턴을 쉽게 이해할 수 있도록 단순화하기위한 것입니다.

이것은 다음과 크게 다르지 않습니다. 한 번에 여러 줄을 건너 뛰고 오류를 처리 한 다음 일반 실행을 다시 시작할 수 있다는 점을 제외하고는 일반 인라인 오류 처리. 이것은 오류를 처리하기 위해 매우 깔끔하게 구조화 된 패턴입니다. 흐름은 위에서 아래로 매우 깨끗하게 이동합니다. 여기에는 스파게티 코드가 없습니다.

일반적으로 오류 핸들러는 하단에 배치됩니다. 그러나 Try / Catch 구조는 매우 우아합니다. “매우 구조화 된 오류 처리 방법이며 따라하기가 매우 쉽습니다.이 패턴은 매우 간결한 방식으로 오류를 재현하려고합니다. 흐름은 매우 일관 적이며 여기 저기 이동하지 않습니다.

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 

소스 :

올바른 관리가 매우 잘 작동합니다. 필요한 곳 어디에서나 재현 할 수있는 매우 깨끗한 흐르는 패턴입니다.

댓글

  • @D_Bester, 링크와 간단한 예제에 감사드립니다. 저는 ‘ 아직 배우는 중이며 귀하의 피드백이 유용하다고 생각했지만 ” On Error Goto 0 후 ” 오류로 이동 -1 “. 또한 리플렉션에서 ” On Error Goto -1 ” 대신 Err.Clear를 사용하는 것이 더 낫다고 생각합니다. 이 일어나고있다. 저는 ‘ VBA에서이 전체 오류 처리가 약간의 검은 예술임을 발견했습니다.
  • @D_Bester. 리플렉션에서 오류가 발생했을 때 사용자에게 메시지를주고 싶다면 코드를 작성하는 것이 좋지만 오류를 다시 발생시키고 싶다면 어떻게해야할까요? 이것은 매우 일반적인 시나리오가 될 것입니다. 중히 여기다.코드에서 고객의 세부 정보를 ‘ 조회하려고했지만 ‘ 예기치 않은 이유로 가져올 수없는 경우입니다. 오류를 다시 발생시키고 코드를 사용하여 조회를 수행하는 코드가 수행 할 작업을 결정하도록해야합니다.
  • @HarveyFrench 오류를 다시 발생 시키려면

    Err.Raise ‘. 코드가 잘 구성되어 있고 호출 코드에서 오류 처리가 활성화되어 있다고 가정하면 문제 없습니다.

  • @HarveyFrench Err.ClearOn Error Goto -1는 동일하지 않습니다. stackoverflow.com/a/30994055/2559297
  • 당신이 ‘ 맞습니다. 미안해. 하지만 위의 코드는 여전히 On Error GoTo -1을 Err.Clear로 대체해야한다고 생각합니다. 그렇지 않으면 ” ‘ 오류 처리없이 추가 코드 “는 오류가 발생하면 ErrHandler1로 이동합니다.

Answer

“CleanExit”및 “Finally”주제 관련.

Mat “s Mug는 다음과 같이 썼습니다.

CleanExit는 ” 마지막으로 ” 부분, 오류 발생 여부에 관계없이 실행됩니다. “다시 던지지 않는 한 …


예를 들어 다음 절차 코드에서 다음과 같은 상황이 발생할 수 있습니다.

절차 적 접근 방식

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 

여기에 문제가 있음 : CleanFail에서 재발생해야하는 메서드 본문에 오류가 발생하면 CleanExit가 실행되지 않습니다. 따라서 리소스를 제대로 닫을 수 없습니다.

물론 오류 처리기 자체에서도 리소스를 닫을 수 있지만 리소스 처리가 가능한 여러 코드 조각이 생길 수 있습니다. 완료됩니다.


제 제안은 각 리소스 바인딩 필요성에 대해 사용자 지정 개체를 사용하는 것입니다.

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 

객체 지향 접근 방식

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 

기회 : 오류가 발생한 후 사용자 지정 개체가 범위를 벗어나기 때문에 해당 Terminate 메서드가 자동으로 실행되어 필요한 리소스가 제대로 닫히거나 정리됩니다.

“마지막”블록.


종료 메서드의 오류 처리

내 생각에는 컨텍스트 의존적입니다. 어떻게 사용자 정의 클래스의 Terminate 메서드에서 오류가 처리됩니다. 추가로 어딘가에 기록해야하거나 아예 삼켜야할까요?

물론 논의 할 수 있습니다.

하지만 이 메서드에서 오류 처리기를 활성화합니다 . 제가 아는 한이 메서드에서 처리되지 않은 오류로 인해 VBA가 실행을 중단하고 표준 런타임 오류 메시지 상자를 표시하기 때문입니다.

p>

답변

이전 게시물을 명확히하기 위해 HarveyFrench의 코드에서 가져온 다음 줄 :

RememberErrLine = Erl() 

코드의 모든 줄에 줄 번호를 추가하지 않으면 작동하지 않습니다. 너무 지루한 줄 번호를 수동으로 입력하는 대신 도구를 사용하여 줄 번호를 자동으로 추가 할 수 있습니다. 이를 수행 할 수있는 몇 가지 도구가 있습니다. 저는 CodeLiner라는 도구를 사용합니다.

다음은 Erl()가 성공적으로 작동하도록하는 줄 번호가있는 코드입니다. :

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 

댓글

  • 안녕하세요! Code Review에 오신 것을 환영합니다. 답변에 더 많은 맥락을 추가하세요. 제안이 OP ‘의 코드를 개선하는 이유를 설명하거나 말하려는 내용에 대해 더 자세히 설명하세요.

답글 남기기

이메일 주소를 발행하지 않을 것입니다. 필수 항목은 *(으)로 표시합니다