developer tip

VBA 오류 처리를위한 좋은 패턴

copycodes 2020. 10. 31. 09:50
반응형

VBA 오류 처리를위한 좋은 패턴


VBA에서 오류 처리를위한 좋은 패턴은 무엇입니까?

특히이 상황에서 어떻게해야합니까?

... some code ...
... some code where an error might occur ...
... some code ...
... some other code where a different error might occur ...
... some other code ...
... some code that must always be run (like a finally block) ...

두 오류를 모두 처리하고 오류가 발생할 수있는 코드 이후에 실행을 재개하고 싶습니다. 또한 마지막에있는 finally 코드는 이전에 어떤 예외가 발생하더라도 항상 실행되어야합니다. 이 결과를 어떻게 얻을 수 있습니까?


VBA의 오류 처리


  • On Error Goto ErrorHandlerLabel
  • Resume( Next| ErrorHandlerLabel )
  • On Error Goto 0 (현재 오류 핸들러 비활성화)
  • Err 목적

Err개체의 속성은 일반적으로 0 개 또는 오류 처리 루틴의 길이가 0 인 문자열로 재설정됩니다뿐만 아니라 명시 적으로 수행 할 수 있습니다 Err.Clear.

오류 처리 루틴의 오류가 종료됩니다.

사용자 오류에는 513-65535 범위를 사용할 수 있습니다. 사용자 정의 클래스 오류의 vbObjectError경우 오류 번호에 추가 합니다. 에 대한 MS 설명서 Err.Raise오류 번호 목록을 참조하십시오 .

파생 클래스 에서 구현되지 않은 인터페이스 멤버의 경우 상수를 사용해야합니다 E_NOTIMPL = &H80004001.


Option Explicit

Sub HandleError()
  Dim a As Integer
  On Error GoTo errMyErrorHandler
    a = 7 / 0
  On Error GoTo 0

  Debug.Print "This line won't be executed."

DoCleanUp:
  a = 0
Exit Sub
errMyErrorHandler:
  MsgBox Err.Description, _
    vbExclamation + vbOKCancel, _
    "Error: " & CStr(Err.Number)
Resume DoCleanUp
End Sub

Sub RaiseAndHandleError()
  On Error GoTo errMyErrorHandler
    ' The range 513-65535 is available for user errors.
    ' For class errors, you add vbObjectError to the error number.
    Err.Raise vbObjectError + 513, "Module1::Test()", "My custom error."
  On Error GoTo 0

  Debug.Print "This line will be executed."

Exit Sub
errMyErrorHandler:
  MsgBox Err.Description, _
    vbExclamation + vbOKCancel, _
    "Error: " & CStr(Err.Number)
  Err.Clear
Resume Next
End Sub

Sub FailInErrorHandler()
  Dim a As Integer
  On Error GoTo errMyErrorHandler
    a = 7 / 0
  On Error GoTo 0

  Debug.Print "This line won't be executed."

DoCleanUp:
  a = 0
Exit Sub
errMyErrorHandler:
  a = 7 / 0 ' <== Terminating error!
  MsgBox Err.Description, _
    vbExclamation + vbOKCancel, _
    "Error: " & CStr(Err.Number)
Resume DoCleanUp
End Sub

Sub DontDoThis()

  ' Any error will go unnoticed!
  On Error Resume Next
  ' Some complex code that fails here.
End Sub

Sub DoThisIfYouMust()

  On Error Resume Next
  ' Some code that can fail but you don't care.
  On Error GoTo 0

  ' More code here
End Sub

또한 다음을 추가합니다.

  • 전역 Err개체는 예외 개체에 가장 가깝습니다.
  • 다음과 같이 효과적으로 "예외를 던질"수 있습니다. Err.Raise

그리고 재미로 :

  • On Error Resume Next 악마는 화신하고 피해야합니다. 조용히 오류를 감추기 때문입니다.

그래서 당신은 이렇게 할 수 있습니다

Function Errorthingy(pParam)
On Error GoTo HandleErr

 ' your code here

    ExitHere:
    ' your finally code
    Exit Function

    HandleErr:
        Select Case Err.Number
        ' different error handling here'
        Case Else
            MsgBox "Error " & Err.Number & ": " & Err.Description, vbCritical, "ErrorThingy"
        End Select


   Resume ExitHere

End Function

사용자 지정 예외를 적용하려는 경우. (예 : 비즈니스 규칙을 위반하는 경우) 위의 예를 사용하지만 goto를 사용하여 필요에 따라 방법의 흐름을 변경합니다.


다음은 내 표준 구현입니다. 나는 레이블이 자기 설명적인 것을 좋아합니다.

Public Sub DoSomething()

    On Error GoTo Catch ' Try
    ' normal code here

    Exit Sub
Catch:

    'error code: you can get the specific error by checking Err.Number

End Sub

또는 Finally블록으로 :

Public Sub DoSomething()

    On Error GoTo Catch ' Try

    ' normal code here

    GoTo Finally
Catch:

    'error code

Finally:

    'cleanup code

End Sub

Professional Excel Development오류 처리 체계가 매우 우수 합니다 . VBA에서 시간을 보내려면 책을 구하는 것이 좋습니다. VBA가 부족한 영역이 많이 있으며이 책에는 이러한 영역을 관리하기위한 좋은 제안이 있습니다.

PED는 ​​두 가지 오류 처리 방법을 설명합니다. 주된 것은 모든 진입 점 프로 시저가 하위 프로 시저이고 다른 모든 프로 시저가 부울을 반환하는 함수 인 시스템입니다.

진입 점 프로시 저는 On Error 문을 사용하여 설계된대로 오류를 캡처합니다. 비 진입 지점 프로시 저는 오류가 없으면 True를 반환하고 오류가 있으면 False를 반환합니다. 비 진입 지점 프로시 저도 On Error를 사용합니다.

두 가지 유형의 절차 모두 중앙 오류 처리 절차를 사용하여 오류를 상태로 유지하고 오류를 기록합니다.


나는 내가 직접 개발 한 코드를 사용하는데 그것은 내 코드에 꽤 좋다.

함수 또는 하위의 시작 부분에서 다음을 정의합니다.

On error Goto ErrorCatcher:

그런 다음 가능한 오류를 처리합니다.

ErrorCatcher:
Select Case Err.Number

Case 0 'exit the code when no error was raised
    On Error GoTo 0
    Exit Function
Case 1 'Error on definition of object
    'do stuff
Case... 'little description here
    'do stuff
Case Else
    Debug.Print "###ERROR"
    Debug.Print "   • Number  :", Err.Number
    Debug.Print "   • Descrip :", Err.Description
    Debug.Print "   • Source  :", Err.Source
    Debug.Print "   • HelpCont:", Err.HelpContext
    Debug.Print "   • LastDLL :", Err.LastDllError
    Stop
    Err.Clear
    Resume
End Select

여기 꽤 괜찮은 패턴이 있습니다.

디버깅 : 오류가 발생하면 Ctrl-Break (또는 Ctrl-Pause)를 누르고 중단 마커 (또는 호출되는 모든 항목)를 Resume 라인으로 드래그 한 다음 F8을 눌러 "던진"라인으로 이동합니다. 오류.

ExitHandler는 "마지막"입니다.

모래 시계는 매번 죽을 것입니다. 상태 표시 줄 텍스트는 매번 지워집니다.

Public Sub ErrorHandlerExample()
    Dim dbs As DAO.Database
    Dim rst As DAO.Recordset

    On Error GoTo ErrHandler
    Dim varRetVal As Variant

    Set dbs = CurrentDb
    Set rst = dbs.OpenRecordset("SomeTable", dbOpenDynaset, dbSeeChanges + dbFailOnError)

    Call DoCmd.Hourglass(True)

    'Do something with the RecordSet and close it.

    Call DoCmd.Hourglass(False)

ExitHandler:
    Set rst = Nothing
    Set dbs = Nothing
    Exit Sub

ErrHandler:
    Call DoCmd.Hourglass(False)
    Call DoCmd.SetWarnings(True)
    varRetVal = SysCmd(acSysCmdClearStatus)

    Dim errX As DAO.Error
    If Errors.Count > 1 Then
       For Each errX In DAO.Errors
          MsgBox "ODBC Error " & errX.Number & vbCrLf & errX.Description
       Next errX
    Else
        MsgBox "VBA Error " & Err.Number & ": " & vbCrLf & Err.Description & vbCrLf & "In: Form_MainForm", vbCritical
    End If

    Resume ExitHandler
    Resume

End Sub



    Select Case Err.Number
        Case 3326 'This Recordset is not updateable
            'Do something about it. Or not...
        Case Else
            MsgBox "VBA Error " & Err.Number & ": " & vbCrLf & Err.Description & vbCrLf & "In: Form_MainForm", vbCritical
    End Select

또한 DAO 및 VBA 오류를 모두 트랩합니다. 특정 Err 번호를 트랩하려는 경우 VBA 오류 섹션에 Select Case를 넣을 수 있습니다.

Select Case Err.Number
    Case 3326 'This Recordset is not updateable
        'Do something about it. Or not...
    Case Else
        MsgBox "VBA Error " & Err.Number & ": " & vbCrLf & Err.Description & vbCrLf & "In: Form_MainForm", vbCritical
End Select

아래 코드는 하위 / 기능에 대한 출구 지점이 하나만 있는지 확인하는 대안을 보여줍니다.

sub something()
    on error goto errHandler

    ' start of code
    ....
    ....
    'end of code

    ' 1. not needed but signals to any other developer that looks at this
    ' code that you are skipping over the error handler...
    ' see point 1...
    err.clear

errHandler:
    if err.number <> 0 then
        ' error handling code
    end if
end sub

토론과 관련이있는 것은 상대적으로 알려지지 않은 Erl기능입니다. 코드 프로 시저 내에 숫자 레이블이있는 경우 (예 :

Sub AAA()
On Error Goto ErrorHandler

1000:
' code
1100:
' more code
1200:
' even more code that causes an error
1300:
' yet more code
9999: ' end of main part of procedure
ErrorHandler:
If Err.Number <> 0 Then
   Debug.Print "Error: " + CStr(Err.Number), Err.Descrption, _
      "Last Successful Line: " + CStr(Erl)
End If   
End Sub 

Erl함수는 가장 최근에 발견 된 숫자 라인 레이블을 반환합니다. 위의 예에서 런타임 오류가 label 이후에 발생 1200:하지만 이전에 발생 1300:하면 Erl함수 1200는를 반환합니다 . 그 이유는 가장 성공적으로 라인 레이블을 발견했기 때문입니다. 오류 처리 블록 바로 위에 줄 레이블을 배치하는 것이 좋습니다. 나는 절차 9999의 주요 부분이 예상되는 결론에 도달했음을 나타 내기 위해 일반적으로 사용 합니다.

메모:

  • 라인 레이블은 양의 정수 MadeItHere:여야합니다 Erl. 같은 레이블 .

  • 라인 레이블은의 실제 라인 번호와 완전히 관련이 없습니다 VBIDE CodeModule. 원하는 순서대로 원하는 양수를 사용할 수 있습니다. 위의 예에서는 25 줄 정도의 코드 줄만 있지만 줄 레이블 번호는에서 시작합니다 1000. 에서 사용되는 편집기 줄 번호와 줄 레이블 번호 간에는 관계가 없습니다 Erl.

  • 라인 레이블 번호는 오름차순이 아닌 하향식 순서가 아니더라도 효능과 이점 Erl이 크게 감소하지만 Erl올바른 번호를보고 하더라도 특정 순서 일 필요는 없습니다 .

  • 라인 레이블은 표시되는 절차에 따라 다릅니다. 프로 시저 ProcAprocedure를 호출 ProcB하고 ProcB제어를 다시로 전달 하는 오류가 발생 ProcA하면 Erl(in ProcA)은 ProcA호출하기 전에 가장 최근에 발견 된 라인 레이블 번호를 반환 합니다 ProcB. 내부 ProcA에서는에 나타날 수있는 라인 레이블 번호를 가져올 수 없습니다 ProcB.

루프 내에 줄 번호 레이블을 넣을 때주의하십시오. 예를 들면

For X = 1 To 100
500:
' some code that causes an error
600:
Next X

라인 레이블을 따르는 코드가 500이전 600에 오류 일으키고 해당 오류가 루프의 20 번째 반복에서 발생하면 루프 의 이전 19 개 상호 작용에서 성공적으로 발생 했더라도 Erl를 반환 합니다.500600

절차 내에서 라인 레이블을 적절하게 배치 Erl하는 것은 진정으로 의미있는 정보를 얻기 위해 함수를 사용하는 데 중요합니다 .

절차에 숫자 줄 레이블을 자동으로 삽입하는 무료 유틸리티가 네트워크에 많이 있으므로 개발 및 디버깅 중에 세분화 된 오류 정보를 얻은 다음 코드가 활성화되면 해당 레이블을 제거합니다.

예상치 못한 오류가 발생하면 코드가 최종 사용자에게 오류 정보를 표시하는 경우 Erl해당 정보 의 값을 제공하면의 값 Erl이보고되지 않는 경우보다 훨씬 간단하게 문제를 찾고 수정할 수 있습니다 .


코끼리 덫을 조심하세요.

나는이 토론에서 이것에 대한 언급을 보지 못했습니다. [액세스 2010]

ACCESS / VBA가 CLASS 개체의 오류를 처리하는 방법은 구성 가능한 옵션에 의해 결정됩니다.

VBA 코드 편집기> 도구> 옵션> 일반> 오류 트래핑 :

여기에 이미지 설명 입력


중앙 오류 처리 방식이라고하는 다음이 가장 잘 작동한다는 것을 알았습니다.

혜택

애플리케이션 실행에는 디버그프로덕션 의 두 가지 모드가 있습니다 . 에서 디버그 모드, 코드는 예기치 않은 오류로 중단되고 당신은 두 번 F8을 눌러 발생한 라인에 점프 쉽게 디버깅 할 수 있습니다. 에서 생산 모드, 의미있는 오류 메시지가 사용자에게 표시 얻을 것이다.

다음과 같은 의도적 인 오류를 발생시켜 사용자에게 보내는 메시지와 함께 코드 실행을 중지 할 수 있습니다.

Err.Raise vbObjectError, gsNO_DEBUG, "Some meaningful error message to the user"

Err.Raise vbObjectError, gsUSER_MESSAGE, "Some meaningful non-error message to the user"

'Or to exit in the middle of a call stack without a message:
Err.Raise vbObjectError, gsSILENT

이행

다음 머리글과 바닥 글을 사용하여 상당한 양의 코드로 모든 서브 루틴과 함수를 "래핑"해야 ehCallTypeEntryPoint하며 모든 진입 점에서 지정해야합니다 . 메모 msModule모든 모듈에 넣어해야 할뿐만 아니라, 상수.

Option Explicit
Const msModule As String = "<Your Module Name>"

' This is an entry point 
Public Sub AnEntryPoint()
    Const sSOURCE As String = "AnEntryPoint"
    On Error GoTo ErrorHandler

    'Your code

ErrorExit:
    Exit Sub

ErrorHandler:
    If CentralErrorHandler(Err, ThisWorkbook, msModule, sSOURCE, ehCallTypeEntryPoint) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Sub

' This is any other subroutine or function that isn't an entry point
Sub AnyOtherSub()
    Const sSOURCE As String = "AnyOtherSub"
    On Error GoTo ErrorHandler

    'Your code

ErrorExit:
    Exit Sub

ErrorHandler:
    If CentralErrorHandler(Err, ThisWorkbook, msModule, sSOURCE) Then
        Stop
        Resume
    Else
        Resume ErrorExit
    End If
End Sub

중앙 오류 처리기 모듈의 내용은 다음과 같습니다.

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Comments: Error handler code.
'
'           Run SetDebugMode True to use debug mode (Dev mode)
'           It will be False by default (Production mode)
'
' Author:   Igor Popov
' Date:     13 Feb 2014
' Licence:  MIT
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

Option Explicit
Option Private Module

Private Const msModule As String = "MErrorHandler"

Public Const gsAPP_NAME As String = "<You Application Name>"

Public Const gsSILENT As String = "UserCancel"  'A silent error is when the user aborts an action, no message should be displayed
Public Const gsNO_DEBUG As String = "NoDebug"   'This type of error will display a specific message to the user in situation of an expected (provided-for) error.
Public Const gsUSER_MESSAGE As String = "UserMessage" 'Use this type of error to display an information message to the user

Private Const msDEBUG_MODE_COMPANY = "<Your Company>"
Private Const msDEBUG_MODE_SECTION = "<Your Team>"
Private Const msDEBUG_MODE_VALUE = "DEBUG_MODE"

Public Enum ECallType
    ehCallTypeRegular = 0
    ehCallTypeEntryPoint
End Enum

Public Function DebugMode() As Boolean
    DebugMode = CBool(GetSetting(msDEBUG_MODE_COMPANY, msDEBUG_MODE_SECTION, msDEBUG_MODE_VALUE, 0))
End Function

Public Sub SetDebugMode(Optional bMode As Boolean = True)
    SaveSetting msDEBUG_MODE_COMPANY, msDEBUG_MODE_SECTION, msDEBUG_MODE_VALUE, IIf(bMode, 1, 0)
End Sub

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Comments: The central error handler for all functions
'           Displays errors to the user at the entry point level, or, if we're below the entry point, rethrows it upwards until the entry point is reached
'
'           Returns True to stop and debug unexpected errors in debug mode.
'
'           The function can be enhanced to log errors.
'
' Date          Developer           TDID    Comment
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' 13 Feb 2014   Igor Popov                  Created

Public Function CentralErrorHandler(ErrObj As ErrObject, Wbk As Workbook, ByVal sModule As String, ByVal sSOURCE As String, _
                                    Optional enCallType As ECallType = ehCallTypeRegular, Optional ByVal bRethrowError As Boolean = True) As Boolean

    Static ssModule As String, ssSource As String
    If Len(ssModule) = 0 And Len(ssSource) = 0 Then
        'Remember the module and the source of the first call to CentralErrorHandler
        ssModule = sModule
        ssSource = sSOURCE
    End If
    CentralErrorHandler = DebugMode And ErrObj.Source <> gsNO_DEBUG And ErrObj.Source <> gsUSER_MESSAGE And ErrObj.Source <> gsSILENT
    If CentralErrorHandler Then
        'If it's an unexpected error and we're going to stop in the debug mode, just write the error message to the immediate window for debugging
        Debug.Print "#Err: " & Err.Description
    ElseIf enCallType = ehCallTypeEntryPoint Then
        'If we have reached the entry point and it's not a silent error, display the message to the user in an error box
        If ErrObj.Source <> gsSILENT Then
            Dim sMsg As String: sMsg = ErrObj.Description
            If ErrObj.Source <> gsNO_DEBUG And ErrObj.Source <> gsUSER_MESSAGE Then sMsg = "Unexpected VBA error in workbook '" & Wbk.Name & "', module '" & ssModule & "', call '" & ssSource & "':" & vbCrLf & vbCrLf & sMsg
            MsgBox sMsg, vbOKOnly + IIf(ErrObj.Source = gsUSER_MESSAGE, vbInformation, vbCritical), gsAPP_NAME
        End If
    ElseIf bRethrowError Then
        'Rethrow the error to the next level up if bRethrowError is True (by Default).
        'Otherwise, do nothing as the calling function must be having special logic for handling errors.
        Err.Raise ErrObj.Number, ErrObj.Source, ErrObj.Description
    End If
End Function

디버그 모드로 설정하려면 직접 실행 창에서 다음을 실행하십시오.

SetDebugMode True

이 스레드의 앞부분에서 작성한 진술에 대한 나의 개인적인 견해 :

그리고 재미로 :

On Error Resume Next는 악마의 화신으로, 조용히 오류를 숨기므로 피해야합니다.

나는 On Error Resume Next오류가 내 작업을 중단하지 않고 어떤 진술이 이전 진술의 결과에 의존하지 않는 곳 에서 on 절차를 사용하고 있습니다.

나는이 일을 해요 때 나는 글로벌 변수를 추가 debugModeOn하고 난으로 설정합니다 True. 그런 다음 이렇게 사용합니다.

If not debugModeOn Then On Error Resume Next

작업을 전달할 때 변수를 false로 설정하여 사용자에게만 오류를 숨기고 테스트 중에 표시합니다.

또한 비어있을 수있는 ListObject의 DataBodyRange를 호출하는 것과 같이 실패 할 수있는 작업을 수행 할 때도 사용합니다.

On Error Resume Next
Sheet1.ListObjects(1).DataBodyRange.Delete
On Error Goto 0

대신에:

If Sheet1.ListObjects(1).ListRows.Count > 0 Then 
    Sheet1.ListObjects(1).DataBodyRange.Delete
End If

또는 컬렉션에 항목이 있는지 확인합니다.

On Error Resume Next
Err.Clear
Set auxiliarVar = collection(key)

' Check existence (if you try to retrieve a nonexistant key you get error number 5)
exists = (Err.Number <> 5)

참고 URL : https://stackoverflow.com/questions/1038006/good-patterns-for-vba-error-handling

반응형