VBA (Excel)에서 오류를 올바르게 처리
저는 VBA로 꽤 오랫동안 작업 해 왔지만 여전히 오류 처리에 대해 잘 모르겠습니다.
좋은 기사는 CPearson.com 중 하나입니다 .
그러나 ErrorHandling을 수행하는 데 사용했던 방식이 완전히 잘못되었는지 여전히 궁금합니다. 블록 1
On Error Goto ErrCatcher
If UBound(.sortedDates) > 0 Then
// Code
Else
ErrCatcher:
// Code
End If
if 절은 true이면 실행되고 실패하면 Goto는 Else 부분으로 이동합니다. 왜냐하면 Array의 Ubound는 오류없이 0 이하가되어서는 안되므로이 메서드는 매우 잘 작동했습니다. 멀리.
내가 올바르게 이해했다면 다음과 같아야합니다.
블록 2
On Error Goto ErrCatcher
If Ubound(.sortedDates) > 0 Then
// Code
End If
Goto hereX
ErrCatcher:
//Code
Resume / Resume Next / Resume hereX
hereX:
또는 이와 같이 : 블록 3
On Error Goto ErrCatcher
If Ubound(.sortedDates) > 0 Then
// Code
End If
ErrCatcher:
If Err.Number <> 0 then
//Code
End If
내가 보는 가장 일반적인 방법은 오류 "Catcher"가 서브의 끝에 있고 서브가 실제로 "Exit Sub"로 끝나기 전에 끝나는 것입니다. 그러나 Sub가 꽤 괜찮다면 약간 혼란스럽지 않습니다. 코드를 읽기 위해 반대로 점프하면 큰가요?
블록 4
다음 코드의 출처 : CPearson.com
On Error Goto ErrHandler:
N = 1 / 0 ' cause an error
'
' more code
'
Exit Sub
ErrHandler:
' error handling code'
Resume Next
End Sub
블록 3과 같아야합니까?
나는 확실히 Block1을 사용하지 않을 것입니다. 오류와 관련이없는 IF 문에 오류 블록이있는 것은 옳지 않은 것 같습니다.
블록 2, 3, 4는 테마의 변형 인 것 같습니다. 나는 GOTO 문이 싫어서 만 2보다 블록 3과 4를 사용하는 것을 선호합니다. 저는 일반적으로 Block4 방법을 사용합니다. 이것은 Microsoft ActiveX Data Objects 2.8 라이브러리가 추가되었는지 확인하고 2.8을 사용할 수없는 경우 이전 버전을 추가하거나 사용하지 않는지 확인하는 데 사용하는 코드의 한 예입니다.
Option Explicit
Public booRefAdded As Boolean 'one time check for references
Public Sub Add_References()
Dim lngDLLmsadoFIND As Long
If Not booRefAdded Then
lngDLLmsadoFIND = 28 ' load msado28.tlb, if cannot find step down versions until found
On Error GoTo RefErr:
'Add Microsoft ActiveX Data Objects 2.8
Application.VBE.ActiveVBProject.references.AddFromFile _
Environ("CommonProgramFiles") + "\System\ado\msado" & lngDLLmsadoFIND & ".tlb"
On Error GoTo 0
Exit Sub
RefErr:
Select Case Err.Number
Case 0
'no error
Case 1004
'Enable Trust Centre Settings
MsgBox ("Certain VBA References are not available, to allow access follow these steps" & Chr(10) & _
"Goto Excel Options/Trust Centre/Trust Centre Security/Macro Settings" & Chr(10) & _
"1. Tick - 'Disable all macros with notification'" & Chr(10) & _
"2. Tick - 'Trust access to the VBA project objects model'")
End
Case 32813
'Err.Number 32813 means reference already added
Case 48
'Reference doesn't exist
If lngDLLmsadoFIND = 0 Then
MsgBox ("Cannot Find Required Reference")
End
Else
For lngDLLmsadoFIND = lngDLLmsadoFIND - 1 To 0 Step -1
Resume
Next lngDLLmsadoFIND
End If
Case Else
MsgBox Err.Number & vbCrLf & Err.Description, vbCritical, "Error!"
End
End Select
On Error GoTo 0
End If
booRefAdded = TRUE
End Sub
ray023에서 진정으로 놀라운 답변을 얻었지만 아마도 과잉이라고 귀하의 의견은 적절합니다. "가벼운"버전을 위해 ....
블록 1 은 IMHO, 나쁜 습관입니다. osknows가 이미 지적했듯이 오류 처리와 정상 경로 코드를 혼합하는 것은 좋지 않습니다. 우선, 오류 조건이있는 동안 새 오류가 발생하면 오류 를 처리 할 기회 가 없습니다 (오류 처리기가있는 루틴에서 호출하지 않는 한 실행이 "버블 링"됩니다.) ).
블록 2 는 Try / Catch 블록의 모방처럼 보입니다. 괜찮아 야하지만 VBA 방식이 아닙니다. 블록 3 은 블록 2의 변형입니다.
Block 4 는 The VBA Way의 베어 본 버전입니다. 나는 것이 강하게 은 코드를 inherting 다른 VBA 프로그래머가 기대됩니다 무엇 때문에, 그것을, 또는 같은 것을 사용하는 것이 좋습니다. 그래도 작은 확장을 보여 드리겠습니다.
Private Sub DoSomething()
On Error GoTo ErrHandler
'Dim as required
'functional code that might throw errors
ExitSub:
'any always-execute (cleanup?) code goes here -- analagous to a Finally block.
'don't forget to do this -- you don't want to fall into error handling when there's no error
Exit Sub
ErrHandler:
'can Select Case on Err.Number if there are any you want to handle specially
'display to user
MsgBox "Something's wrong: " & vbCrLf & Err.Description
'or use a central DisplayErr routine, written Public in a Module
DisplayErr Err.Number, Err.Description
Resume ExitSub
Resume
End Sub
두 번째 Resume
. 이것은 내가 최근에 배운 속임수입니다 . 명령문이 다른 곳으로 실행을 보내기 때문에 정상적인 처리에서는 실행 되지 않습니다Resume <label>
. 하지만 디버깅을위한 신의 선물이 될 수 있습니다. 오류 알림을 받으면 디버그를 선택하십시오 (또는 Ctl-Break를 누른 다음 "실행이 중단되었습니다"메시지가 표시되면 디버그를 선택하십시오). 다음 (강조 표시된) 명령문은 MsgBox
또는 다음 명령문입니다. "Set Next Statement"(Ctl-F9)를 사용하여 베어를 강조 표시 Resume
한 다음 F8을 누릅니다. 오류가 발생한 위치 를 정확하게 보여줍니다 .
이 형식 "점프"에 대한 귀하의 반대에 관해서는 A) VBA 프로그래머가 앞서 언급 한 바와 같이 기대하는 것입니다. B) 루틴 은 점프하기에 멀지 않을 정도로 짧아야합니다.
오류 처리의 두 가지 주요 목적 :
- 예측할 수 있지만 사용자가 수행하지 못하도록 제어 할 수없는 트랩 오류 (예 : 썸 드라이브가 제거 된 경우 썸 드라이브에 파일 저장)
- 예기치 않은 오류의 경우 사용자에게 문제가 무엇인지 알려주는 양식을 제공합니다. 이렇게하면 해당 메시지를 사용자에게 전달할 수 있으며 수정 작업을하는 동안 해결 방법을 제공 할 수 있습니다.
그럼 어떻게 하시겠습니까?
먼저 예기치 않은 오류가 발생할 때 표시 할 오류 양식을 작성하십시오.
다음과 같이 보일 수 있습니다 (참고 : Mine은 frmErrors라고합니다).
다음 레이블을 확인하십시오.
- lblHeadline
- lblSource
- lblProblem
- lblResponse
또한 표준 명령 버튼 :
- 무시
- 다시 해 보다
- 취소
이 형식의 코드에는 놀라운 것이 없습니다.
Option Explicit
Private Sub cmdCancel_Click()
Me.Tag = CMD_CANCEL
Me.Hide
End Sub
Private Sub cmdIgnore_Click()
Me.Tag = CMD_IGNORE
Me.Hide
End Sub
Private Sub cmdRetry_Click()
Me.Tag = CMD_RETRY
Me.Hide
End Sub
Private Sub UserForm_Initialize()
Me.lblErrorTitle.Caption = "Custom Error Title Caption String"
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
'Prevent user from closing with the Close box in the title bar.
If CloseMode <> 1 Then
cmdCancel_Click
End If
End Sub
기본적으로 양식을 닫을 때 사용자가 어떤 버튼을 눌렀는지 알고 싶습니다.
다음으로 VBA 앱 전체에서 사용할 오류 처리기 모듈을 만듭니다.
'****************************************************************
' MODULE: ErrorHandler
'
' PURPOSE: A VBA Error Handling routine to handle
' any unexpected errors
'
' Date: Name: Description:
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'03/22/2010 Ray Initial Creation
'****************************************************************
Option Explicit
Global Const CMD_RETRY = 0
Global Const CMD_IGNORE = 1
Global Const CMD_CANCEL = 2
Global Const CMD_CONTINUE = 3
Type ErrorType
iErrNum As Long
sHeadline As String
sProblemMsg As String
sResponseMsg As String
sErrorSource As String
sErrorDescription As String
iBtnCap(3) As Integer
iBitmap As Integer
End Type
Global gEStruc As ErrorType
Sub EmptyErrStruc_S(utEStruc As ErrorType)
Dim i As Integer
utEStruc.iErrNum = 0
utEStruc.sHeadline = ""
utEStruc.sProblemMsg = ""
utEStruc.sResponseMsg = ""
utEStruc.sErrorSource = ""
For i = 0 To 2
utEStruc.iBtnCap(i) = -1
Next
utEStruc.iBitmap = 1
End Sub
Function FillErrorStruct_F(EStruc As ErrorType) As Boolean
'Must save error text before starting new error handler
'in case we need it later
EStruc.sProblemMsg = Error(EStruc.iErrNum)
On Error GoTo vbDefaultFill
EStruc.sHeadline = "Error " & Format$(EStruc.iErrNum)
EStruc.sProblemMsg = EStruc.sErrorDescription
EStruc.sErrorSource = EStruc.sErrorSource
EStruc.sResponseMsg = "Contact the Company and tell them you received Error # " & Str$(EStruc.iErrNum) & ". You should write down the program function you were using, the record you were working with, and what you were doing."
Select Case EStruc.iErrNum
'Case Error number here
'not sure what numeric errors user will ecounter, but can be implemented here
'e.g.
'EStruc.sHeadline = "Error 3265"
'EStruc.sResponseMsg = "Contact tech support. Tell them what you were doing in the program."
Case Else
EStruc.sHeadline = "Error " & Format$(EStruc.iErrNum) & ": " & EStruc.sErrorDescription
EStruc.sProblemMsg = EStruc.sErrorDescription
End Select
GoTo FillStrucEnd
vbDefaultFill:
'Error Not on file
EStruc.sHeadline = "Error " & Format$(EStruc.iErrNum) & ": Contact Tech Support"
EStruc.sResponseMsg = "Contact the Company and tell them you received Error # " & Str$(EStruc.iErrNum)
FillStrucEnd:
Exit Function
End Function
Function iErrorHandler_F(utEStruc As ErrorType) As Integer
Static sCaption(3) As String
Dim i As Integer
Dim iMCursor As Integer
Beep
'Setup static array
If Len(sCaption(0)) < 1 Then
sCaption(CMD_IGNORE) = "&Ignore"
sCaption(CMD_RETRY) = "&Retry"
sCaption(CMD_CANCEL) = "&Cancel"
sCaption(CMD_CONTINUE) = "Continue"
End If
Load frmErrors
'Did caller pass error info? If not fill struc with the needed info
If Len(utEStruc.sHeadline) < 1 Then
i = FillErrorStruct_F(utEStruc)
End If
frmErrors!lblHeadline.Caption = utEStruc.sHeadline
frmErrors!lblProblem.Caption = utEStruc.sProblemMsg
frmErrors!lblSource.Caption = utEStruc.sErrorSource
frmErrors!lblResponse.Caption = utEStruc.sResponseMsg
frmErrors.Show
iErrorHandler_F = frmErrors.Tag ' Save user response
Unload frmErrors ' Unload and release form
EmptyErrStruc_S utEStruc ' Release memory
End Function
응용 프로그램에만 적용되는 오류가있을 수 있습니다. 이는 일반적으로 애플리케이션에만 해당되는 짧은 오류 목록입니다. 상수 모듈이 아직없는 경우 사용자 지정 오류의 ENUM을 포함하는 모듈을 만듭니다. (참고 : Office '97은 ENUMS를 지원하지 않습니다.) ENUM은 다음과 같아야합니다.
Public Enum CustomErrorName
MaskedFilterNotSupported
InvalidMonthNumber
End Enum
사용자 지정 오류를 발생시킬 모듈을 만듭니다.
'********************************************************************************************************************************
' MODULE: CustomErrorList
'
' PURPOSE: For trapping custom errors applicable to this application
'
'INSTRUCTIONS: To use this module to create your own custom error:
' 1. Add the Name of the Error to the CustomErrorName Enum
' 2. Add a Case Statement to the raiseCustomError Sub
' 3. Call the raiseCustomError Sub in the routine you may see the custom error
' 4. Make sure the routine you call the raiseCustomError has error handling in it
'
'
' Date: Name: Description:
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'03/26/2010 Ray Initial Creation
'********************************************************************************************************************************
Option Explicit
Const MICROSOFT_OFFSET = 512 'Microsoft reserves error values between vbObjectError and vbObjectError + 512
'************************************************************************************************
' FUNCTION: raiseCustomError
'
' PURPOSE: Raises a custom error based on the information passed
'
'PARAMETERS: customError - An integer of type CustomErrorName Enum that defines the custom error
' errorSource - The place the error came from
'
' Returns: The ASCII vaule that should be used for the Keypress
'
' Date: Name: Description:
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'03/26/2010 Ray Initial Creation
'************************************************************************************************
Public Sub raiseCustomError(customError As Integer, Optional errorSource As String = "")
Dim errorLong As Long
Dim errorDescription As String
errorLong = vbObjectError + MICROSOFT_OFFSET + customError
Select Case customError
Case CustomErrorName.MaskedFilterNotSupported
errorDescription = "The mask filter passed is not supported"
Case CustomErrorName.InvalidMonthNumber
errorDescription = "Invalid Month Number Passed"
Case Else
errorDescription = "The custom error raised is unknown."
End Select
Err.Raise errorLong, errorSource, errorDescription
End Sub
이제 프로그램에서 오류를 잡을 수있는 준비가되었습니다. 하위 (또는 함수)는 다음과 같아야합니다.
Public Sub MySub(monthNumber as Integer)
On Error GoTo eh
Dim sheetWorkSheet As Worksheet
'Run Some code here
'************************************************
'* OPTIONAL BLOCK 1: Look for a specific error
'************************************************
'Temporarily Turn off Error Handling so that you can check for specific error
On Error Resume Next
'Do some code where you might expect an error. Example below:
Const ERR_SHEET_NOT_FOUND = 9 'This error number is actually subscript out of range, but for this example means the worksheet was not found
Set sheetWorkSheet = Sheets("January")
'Now see if the expected error exists
If Err.Number = ERR_SHEET_NOT_FOUND Then
MsgBox "Hey! The January worksheet is missing. You need to recreate it."
Exit Sub
ElseIf Err.Number <> 0 Then
'Uh oh...there was an error we did not expect so just run basic error handling
GoTo eh
End If
'Finished with predictable errors, turn basic error handling back on:
On Error GoTo eh
'**********************************************************************************
'* End of OPTIONAL BLOCK 1
'**********************************************************************************
'**********************************************************************************
'* OPTIONAL BLOCK 2: Raise (a.k.a. "Throw") a Custom Error if applicable
'**********************************************************************************
If not (monthNumber >=1 and monthnumber <=12) then
raiseCustomError CustomErrorName.InvalidMonthNumber, "My Sub"
end if
'**********************************************************************************
'* End of OPTIONAL BLOCK 2
'**********************************************************************************
'Rest of code in your sub
goto sub_exit
eh:
gEStruc.iErrNum = Err.Number
gEStruc.sErrorDescription = Err.Description
gEStruc.sErrorSource = Err.Source
m_rc = iErrorHandler_F(gEStruc)
If m_rc = CMD_RETRY Then
Resume
End If
sub_exit:
'Any final processing you want to do.
'Be careful with what you put here because if it errors out, the error rolls up. This can be difficult to debug; especially if calling routine has no error handling.
Exit Sub 'I was told a long time ago (10+ years) that exit sub was better than end sub...I can't tell you why, so you may not want to put in this line of code. It's habit I can't break :P
End Sub
위 코드의 복사 / 붙여 넣기는 즉시 작동하지 않을 수 있지만 요점을 확실히 제공해야합니다.
BTW, 회사 로고를 작성해야하는 경우 http://www.MySuperCrappyLogoLabels99.com 에서 저를 찾아보십시오 .
저는 간단하게 유지
합니다. 모듈 수준에서 두 개의 변수를 정의하고 하나를 모듈 자체의 이름으로 설정합니다.
Private Const ThisModuleName As String = "mod_Custom_Functions"
Public sLocalErrorMsg As String
모듈의 각 하위 / 기능 내에서 지역 변수를 정의합니다.
Dim ThisRoutineName As String
ThisRoutineName을 하위 또는 함수의 이름으로 설정했습니다.
' Housekeeping
On Error Goto ERR_RTN
ThisRoutineName = "CopyWorksheet"
그런 다음 모든 오류를 ERR_RTN으로 보냅니다. 오류가 발생하면 먼저 sLocalErrorMsg를 설정하여 오류가 실제로 무엇인지 정의하고 디버깅 정보를 제공합니다.
If Len(Trim(FromWorksheetName)) < 1 Then
sLocalErrorMsg = "Parameter 'FromWorksheetName' Is Missing."
GoTo ERR_RTN
End If
각 하위 / 기능 하단에서 다음과 같이 로직 흐름을 지시합니다.
'
' The "normal" logic goes here for what the routine does
'
GoTo EXIT_RTN
ERR_RTN:
On Error Resume Next
' Call error handler if we went this far.
ErrorHandler ThisModuleName, ThisRoutineName, sLocalErrorMsg, Err.Description, Err.Number, False
EXIT_RTN:
On Error Resume Next
'
' Some closing logic
'
End If
그런 다음 "mod_Error_Handler"라는 모든 프로젝트에 넣은 별도의 모듈이 있습니다.
'
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Subroutine Name: ErrorHandler '
' '
' Description: '
' This module will handle the common error alerts. '
' '
' Inputs: '
' ModuleName String 'The name of the module error is in. '
' RoutineName String 'The name of the routine error in in. '
' LocalErrorMsg String 'A local message to assist with troubleshooting.'
' ERRDescription String 'The Windows Error Description. '
' ERRCode Long 'The Windows Error Code. '
' Terminate Boolean 'End program if error encountered? '
' '
' Revision History: '
' Date (YYYYMMDD) Author Change '
' =============== ===================== =============================================== '
' 20140529 XXXXX X. XXXXX Original '
' '
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
Public Sub ErrorHandler(ModuleName As String, RoutineName As String, LocalErrorMsg As String, ERRDescription As String, ERRCode As Long, Terminate As Boolean)
Dim sBuildErrorMsg As String
' Build Error Message To Display
sBuildErrorMsg = "Error Information:" & vbCrLf & vbCrLf
If Len(Trim(ModuleName)) < 1 Then
ModuleName = "Unknown"
End If
If Len(Trim(RoutineName)) < 1 Then
RoutineName = "Unknown"
End If
sBuildErrorMsg = sBuildErrorMsg & "Module Name: " & ModuleName & vbCrLf & vbCrLf
sBuildErrorMsg = sBuildErrorMsg & "Routine Name: " & RoutineName & vbCrLf & vbCrLf
If Len(Trim(LocalErrorMsg)) > 0 Then
sBuildErrorMsg = sBuildErrorMsg & "Local Error Msg: " & LocalErrorMsg & vbCrLf & vbCrLf
End If
If Len(Trim(ERRDescription)) > 0 Then
sBuildErrorMsg = sBuildErrorMsg & "Program Error Msg: " & ERRDescription & vbCrLf & vbCrLf
If IsNumeric(ERRCode) Then
sBuildErrorMsg = sBuildErrorMsg & "Program Error Code: " & Trim(Str(ERRCode)) & vbCrLf & vbCrLf
End If
End If
MsgBox sBuildErrorMsg, vbOKOnly + vbExclamation, "Error Detected!"
If Terminate Then
End
End If
End Sub
The end result is a pop-up error message teling me in what module, what soubroutine, and what the error message specifically was. In addition, it also will insert the Windows error message and code.
Block 2 doesn't work because it doesn't reset the Error Handler potentially causing an endless loop. For Error Handling to work properly in VBA, you need a Resume
statement to clear the Error Handler. The Resume
also reactivates the previous Error Handler. Block 2 fails because a new error would go back to the previous Error Handler causing an infinite loop.
Block 3 fails because there is no Resume
statement so any attempt at error handling after that will fail.
Every error handler must be ended by exiting the procedure or a Resume
statement. Routing normal execution around an error handler is confusing. This is why error handlers are usually at the bottom.
But here is another way to handle an error in VBA. It handles the error inline like Try/Catch in VB.net There are a few pitfalls, but properly managed it works quite nicely.
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 has deactivated the previous error handler
MsgBox (Err.Description)
'Resume (or exit procedure) is the only way to get out of an error handling block
'otherwise the following On Error statements will have no effect
'CAUTION: it also reactivates the previous error handler
Resume EndTry1
End If
EndTry1:
'CAUTION: since the Resume statement reactivates the previous error handler
'you must ALWAYS use an On Error GoTo statement here
'because another error here would cause an endless loop
'use On Error GoTo 0 or On Error GoTo <Label>
On Error GoTo 0
'more code with or without error handling
End Sub
Sources:
- http://www.cpearson.com/excel/errorhandling.htm
- http://msdn.microsoft.com/en-us/library/bb258159.aspx
이 작업을 수행하는 열쇠는 Resume
바로 뒤에 다른 On Error
문장 을 사용하는 것 입니다. 는 Resume
오류 핸들러 내이며, 코드를 산란 EndTry1
레이블입니다. On Error
이전 오류 처리기가 "재개"되므로 문제를 방지 하려면 즉시 다른 문을 설정해야합니다 . 즉, 활성화되어 다른 오류를 처리 할 준비가됩니다. 이로 인해 오류가 반복되고 무한 루프에 들어갈 수 있습니다.
이전 오류 처리기를 다시 사용하지 않으려면 On Error
새 오류 처리기 로 설정 하거나를 사용 On Error Goto 0
하여 모든 오류 처리를 취소해야합니다.
참조 URL : https://stackoverflow.com/questions/6028288/properly-handling-errors-in-vba-excel
'IT박스' 카테고리의 다른 글
파이썬에서 배열 / 사전 목록을 어떻게 만들 수 있습니까? (0) | 2020.12.30 |
---|---|
파이썬에서 큰 json 파일을로드하는 메모리 효율적이고 빠른 방법이 있습니까? (0) | 2020.12.30 |
C / C ++ 파일에서 Android NDK의 컴파일을 감지하는 방법은 무엇입니까? (0) | 2020.12.30 |
스마트 포인터보다 원시 포인터를 언제 사용해야합니까? (0) | 2020.12.30 |
MongoDB-컬렉션 내 중첩 된 항목을 쿼리하는 방법은 무엇입니까? (0) | 2020.12.30 |