2013-09-27 10 views
6

Ho ricevuto il libro "Sviluppo Excel professionale" di Rob Bovey e mi sta aprendo gli occhi.Gestione errori VBA Excel - soprattutto nelle funzioni - Stile di sviluppo Excel professionale

Sto correggendo il mio codice con la gestione degli errori. Tuttavia, c'è molto che non capisco. Ho soprattutto bisogno di sapere come usarlo correttamente nelle funzioni. Uso la versione di retrocessione di Bovey del gestore degli errori (in fondo). Quando ho iniziato, stavo usando il metodo booleano di base (non rethrow) e trasformavo le mie subroutine in funzioni booleane. (P.S. Sto tornando al metodo booleano basato sulla risposta.)

Ho bisogno di indicazioni su come adattare le funzioni a questo schema. Voglio che restituiscano i loro valori reali (una stringa o un double, ad es. -1 se falliscono in alcuni casi) quindi posso annidarli in altre funzioni e non solo restituire un errore nella gestione di boolean.

Questo è ciò che una tipica chiamata di subroutine a bDrawCellBorders (myWS) apparirebbe all'interno di un punto di ingresso. Le chiamate secondarie sembrano funzionare bene. (Vale a dire che è una subroutine che è stato trasformato in una funzione unica in modo che possa restituire un valore booleano per lo schema di gestione degli errori.)

Sub UpdateMe() ' Entry Point 

    Const sSOURCE As String = "UpdateMe()" 

    On Error GoTo ErrorHandler 

    Set myWS = ActiveCell.Worksheet 
    Set myRange = ActiveCell 
    myWS.Unprotect 

' lots of code 

    If Not bDrawCellBorders(myWS) Then ERR.Raise glHANDLED_ERROR ' Call subroutine 

' lots of code 

ErrorExit: 
    On Error Resume Next 
    Application.EnableEvents = True 
    myWS.Protect AllowFormattingColumns:=True 
    Exit Sub 

ErrorHandler: 
    If bCentralErrorHandler(msMODULE, sSOURCE,,True) Then ' Call as Entry Point 
     Stop 
     Resume 
    Else 
     Resume ErrorExit 
    End If 
End Sub 

Tuttavia, non so come estendere questo per funzioni reali. Questo è basato su un esempio nel libro che è stato elaborato per una subroutine, e l'ho appena passato a una funzione. Domande: * Come si chiama? È semplicemente come x = sngDoSomeMath (17) * La sua gestione degli errori funzionerà correttamente? * Dov'è il posto giusto o dove chiamare la routine di gestione degli errori con bReThrow = true?

Public Function sngDoSomeMath(ByVal iNum As Integer) As Single 

Dim sngResult As Single 

Const sSOURCE As String = "sngDoSomeMath()" 

On Error GoTo ErrorHandler 

' example 1, input did not pass validation. don't want to 
' go up the error stack but just inform the 
' calling program that they didn't get a good result from this 
' function call so they can do something else 
If iNum <> 42 Then 
    sngResult = -1 'function failed because I only like the number 42 
    GoTo ExitHere 
End If 

' example 2, true error generated 
sngResult = iNum/0 

sngDoSomeMath = lResult 

ExitHere: 
    Exit Function 
ErrorHandler: 

' Run cleanup code 
' ... here if any 

' Then do error handling 

If bCentralErrorHandler(msMODULE, sSOURCE, , , True) Then ' The true is for RETHROW 
    Stop 
    Resume 
End If 

End Function 

La routine del gestore di errore:

' 
' Description: This module contains the central error 
'    handler and related constant declarations. 
' 
' Authors:  Rob Bovey, www.appspro.com 
'    Stephen Bullen, www.oaltd.co.uk 
' 
' Chapter Change Overview 
' Ch# Comment 
' -------------------------------------------------------------- 
' 15 Initial version 
' 
Option Explicit 
Option Private Module 

' ************************************************************** 
' Global Constant Declarations Follow 
' ************************************************************** 
Public Const gbDEBUG_MODE As Boolean = False ' True enables debug mode, False disables it. 
Public Const glHANDLED_ERROR As Long = 9999  ' Run-time error number for our custom errors. 
Public Const glUSER_CANCEL As Long = 18   ' The error number generated when the user cancels program execution. 


' ************************************************************** 
' Module Constant Declarations Follow 
' ************************************************************** 
Private Const msSILENT_ERROR As String = "UserCancel" ' Used by the central error handler to bail out silently on user cancel. 
Private Const msFILE_ERROR_LOG As String = "Error.log" ' The name of the file where error messages will be logged to. 


'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 
' Comments: This is the central error handling procedure for the 
'   program. It logs and displays any run-time errors 
'   that occur during program execution. 
' 
' Arguments: sModule   The module in which the error occured. 
'    sProc   The procedure in which the error occured. 
'    sFile   (Optional) For multiple-workbook 
'        projects this is the name of the 
'        workbook in which the error occured. 
'    bEntryPoint  (Optional) True if this call is 
'        being made from an entry point 
'        procedure. If so, an error message 
'        will be displayed to the user. 
' 
' Returns:  Boolean   True if the program is in debug 
'        mode, False if it is not. 
' 
' Date   Developer  Chap Action 
' -------------------------------------------------------------- 
' 03/30/08  Rob Bovey  Ch15 Initial version 
' 
Public Function bCentralErrorHandler(_ 
     ByVal sModule As String, _ 
     ByVal sProc As String, _ 
     Optional ByVal sFile As String, _ 
     Optional ByVal bEntryPoint As Boolean, _ 
     Optional ByVal bReThrow As Boolean = True) As Boolean 

    Static sErrMsg As String 

    Dim iFile As Integer 
    Dim lErrNum As Long 
    Dim sFullSource As String 
    Dim sPath As String 
    Dim sLogText As String 

    ' Grab the error info before it's cleared by 
    ' On Error Resume Next below. 
    lErrNum = ERR.Number 
    ' If this is a user cancel, set the silent error flag 
    ' message. This will cause the error to be ignored. 
    If lErrNum = glUSER_CANCEL Then sErrMsg = msSILENT_ERROR 
    ' If this is the originating error, the static error 
    ' message variable will be empty. In that case, store 
    ' the originating error message in the static variable. 
    If Len(sErrMsg) = 0 Then sErrMsg = ERR.Description 

    ' We cannot allow errors in the central error handler. 
    On Error Resume Next 

    ' Load the default filename if required. 
    If Len(sFile) = 0 Then sFile = ThisWorkbook.Name 

    ' Get the application directory. 
    sPath = ThisWorkbook.Path 
    If Right$(sPath, 1) <> "\" Then sPath = sPath & "\" 

    ' Construct the fully-qualified error source name. 
    sFullSource = "[" & sFile & "]" & sModule & "." & sProc 

    ' Create the error text to be logged. 
    sLogText = " " & sFullSource & ", Error " & _ 
       CStr(lErrNum) & ": " & sErrMsg 

    ' Open the log file, write out the error information and 
    ' close the log file. 
    iFile = FreeFile() 
    Open sPath & msFILE_ERROR_LOG For Append As #iFile 
    Print #iFile, Format$(Now(), "mm/dd/yy hh:mm:ss"); sLogText 
    If bEntryPoint Or Not bReThrow Then Print #iFile, 
    Close #iFile 

    ' Do not display or debug silent errors. 
    If sErrMsg <> msSILENT_ERROR Then 

     ' Show the error message when we reach the entry point 
     ' procedure or immediately if we are in debug mode. 
     If bEntryPoint Or gbDEBUG_MODE Then 
      Application.ScreenUpdating = True 
      MsgBox sErrMsg, vbCritical, gsAPP_NAME 
      ' Clear the static error message variable once 
      ' we've reached the entry point so that we're ready 
      ' to handle the next error. 
      sErrMsg = vbNullString 
     End If 

     ' The return vale is the debug mode status. 
     bCentralErrorHandler = gbDEBUG_MODE 

    Else 
     ' If this is a silent error, clear the static error 
     ' message variable when we reach the entry point. 
     If bEntryPoint Then sErrMsg = vbNullString 
     bCentralErrorHandler = False 
    End If 

    'If we're using re-throw error handling, 
    'this is not the entry point and we're not debugging, 
    're-raise the error, to be caught in the next procedure 
    'up the call stack. 
    'Procedures that handle their own errors can call the 
    'central error handler with bReThrow = False to log the 
    'error, but not re-raise it. 
    If bReThrow Then 
     If Not bEntryPoint And Not gbDEBUG_MODE Then 
      On Error GoTo 0 
      ERR.Raise lErrNum, sFullSource, sErrMsg 
     End If 
    Else 
     'Error is being logged and handled, 
     'so clear the static error message variable 
     sErrMsg = vbNullString 
    End If 

End Function 

risposta

4

Avevo bisogno di un po 'più di aiuto su questa tecnica specifica, quindi sono andato direttamente alla fonte e Mr. Bovey è stato così gentile da rispondere. Mi ha dato il permesso di pubblicare la sua risposta alla community di StackOverflow.

Le istruzioni che seguono si riferiscono al suo metodo preferito di gestione degli errori per le funzioni della tecnica "boolean error handling" e non al "metodo rethrow" alternativo, entrambi descritti nel suo libro "Professional Excel Development" 2nd edition.


Hi Shari,

In risposta alle vostre domande su gestione degli errori nelle funzioni, ci sono scenari di gestione di tre errore si può avere con una funzione in VBA:

1) La funzione è così banale che non ha bisogno di un gestore di errori. Nell'evento improbabile si verifica un errore in una funzione come questa, si riverserà su nel gestore degli errori della procedura chiamante.

2) Una funzione non banale richiede un gestore errori e utilizza il sistema di valori di ritorno booleano descritto nel libro. Eventuali altri valori necessari per la funzione vengono restituiti tramite gli argomenti ByRef. Questo caso copre la maggior parte delle funzioni di che scrivo. Ci sono alcune cose che non si possono fare con funzioni come , alimentarle direttamente nell'argomento di un'altra funzione è un esempio di , ma ritengo che questo sia un buon compromesso per ottenere la gestione degli errori di prova bullet .

3) Una funzione non banale richiede un gestore errori e deve restituire un valore non relativo al relativo stato di errore. Questa è una situazione rara perché posso convertire il 99% di questi nel caso 2 ristrutturando il mio codice. Se non è possibile eseguire , l'unica possibilità è selezionare un valore di ritorno arbitrario compreso tra nell'intervallo di valori restituiti normali e utilizzarlo per indicare che si è verificato un errore con . Se il chiamante di questa funzione vede questo valore di errore arbitrario , sa che non può continuare.

Rob Bovey professionisti notifica http://www.appspro.com/


Esempio di codice (Shari W)


' Show how to call a function using this error handling method. 
Const giBAD_RESULT As Integer = -1 

Function TestMath() ' An Entry Point 

    Dim sngResult As Single 
    Dim iNum As Integer 

    ' Call the function, actual result goes in sngResult but it returns the error handling boolean. 
    ' A true error like Div 0 will go to error handler. 

    ' Set Up Error Handling for Entry Point 
    Application.EnableCancelKey = xlErrorHandler 
    Dim bUserCancel As Boolean 
    Const sSOURCE As String = "TestMath()" 
    On Error GoTo ErrorHandler 
    ' End Error Set Up 

    iNum = 0 ' Try 0 to create error 
    If Not bDoSomeMath(iNum, sngResult) Then ERR.Raise glHANDLED_ERROR 
    ' If function does parameter checking and wants to return a bad input code, check for that. 
    If sngResult = giBAD_RESULT Then 
     MsgBox ("Bad input to bDoSomeMath " & iNum) 
    Else 
     MsgBox ("I believe the answer is " & sngResult) 
    End If 

ErrorExit: 
    On Error Resume Next 
    Exit Function 

ErrorHandler: 
    If bCentralErrorHandler(msMODULE, sSOURCE, , True) Then 
     Stop 
     Resume 
    Else 
     Resume ErrorExit 
    End If 
End Function 
Function bDoSomeMath(ByVal iNum As Integer, ByRef sngResult As Single) As Boolean 

    ' Error handling Set Up 
    Dim bReturn As Boolean 
    Const sSOURCE As String = "bDoSomeMath()" 
    On Error GoTo ErrorHandler 
    bReturn = True 
    ' End Error Set Up 

    If iNum < 0 Or iNum > 1000 Then 
     sngResult = giBAD_RESULT 'function failed because I only like the numbers 0 to 1000 
     GoTo ErrorExit 
    Else 
     sngResult = 100/iNum ' generate a true error by iNum = 0 
    End If 

ErrorExit: 
    On Error Resume Next 
    bDoSomeMath = bReturn 
    Exit Function 

ErrorHandler: 
    bReturn = False 
    If bCentralErrorHandler(msMODULE, sSOURCE, , , True) Then 
     Stop 
     Resume 
    Else 
     Resume ErrorExit 
    End If 

End Function 
2

una proposta per la gestione di gestione in VBA di errore può essere trovato here.

Lo stesso strumento (MZ-Tools) e il metodo (gestore di errori standard/generico, che potrebbe essere utilizzato per creare un sistema automatico di segnalazione degli errori) funzionerà con Excel.

+0

MZ-Tools è un grande strumento. Ho sostituito il loro standard ErrorHandler con la versione di Rob Bovey (sopra). Con ONE CLICK posso incollare intestazioni e gestori di errori in una routine, anche se ho trascurato di iniziare con loro.Ma ho bisogno di informazioni più specifiche sulla gestione degli errori di prima mano per le funzioni! Grazie. –

+0

+ 1 Buon suggerimento su MZ Tools :) –

+0

Grazie ragazzi, ma spero ancora che qualcuno che usi i metodi descritti mi faccia da guida e mi raddrizzi! Gli esempi su quel sito Web e quello fornito con MZ-Tools sono troppo semplici (li ho già sostituiti nelle opzioni di MZ-Tools.) –

12

Questo è un libro fantastico di Rob.

I miei due centesimi di Gestione degli errori (sia per una procedura o una funzione) si basa su BACIO (Keep It Simple sciocco)

Capire che cosa vuoi dalla tua gestore degli errori?

Questo è di solito quello che voglio/aspettarsi dal mio gestore degli errori ...

  1. linea sulla quale l'errore è accaduto
  2. numero di errore
  3. messaggio di errore
  4. Ripristina Eventi se applicabile

Si interrompe quanto sopra. Come già saprai già come si presenta il tuo gestore degli errori, considera questo esempio.

Sub Sample() 
    Dim i As Integer, j As Integer 

    On Error GoTo Whoa 

    Application.ScreenUpdating = False 

    i = 1111111111 

    For j = 1 To i 
     Debug.Print ThisWorkbook.Sheets(1).Cells(i, 1).Value 
    Next i 

LetsContinue: 
    Exit Sub 
Whoa: 
    MsgBox Err.Description 
    Resume LetsContinue 
End Sub 

Questo è un gestore di errori molto semplice ma è di molto meno aiuto per me. Quindi cerchiamo di renderlo più utile. Se si esegue il codice sopra riportato si ottiene un messaggio di errore come mostrato nella schermata qui sotto e se si nota, non è di grande aiuto.

enter image description here

Passiamo ora ad affrontare tutti i punti che ho citato nel Logic sopra

  1. linea sulla quale l'errore è accaduto

C'è una proprietà chiamata ERL di cui pochissime persone sono a conoscenza. Puoi effettivamente usarlo per ottenere il numero di riga del codice in cui si è verificato l'errore. Per questo è necessario assicurarsi di numerare il codice. Vedi questo esempio.

Sub Sample() 
    Dim i As Integer, j As Integer 

10  On Error GoTo Whoa 

20  Application.ScreenUpdating = False 

30  i = 1111111111 

40  For j = 1 To i 
50   Debug.Print ThisWorkbook.Sheets(1).Cells(i, 1).Value 
60  Next j 

LetsContinue: 
70  Exit Sub 
Whoa: 
80  MsgBox Erl 
90  Resume LetsContinue 
End Sub 

Quando si esegue il codice di cui sopra, si otterrà questo

enter image description here

Quindi ora so che l'errore è accaduto sulla linea 30 che è i = 1111111111

Passando al prossimo

  1. Errore Numb er
  2. messaggio di errore

Il numero di errore e il messaggio di errore possono essere recuperati da, rispettivamente, Err.Number e Err.Description. Così ora cerchiamo di combinare Erl, Err.Number e Err.Description

controllare questo esempio

Sub Sample() 
    Dim i As Integer, j As Integer 

10  On Error GoTo Whoa 

20  Application.ScreenUpdating = False 

30  i = 1111111111 

40  For j = 1 To i 
50   Debug.Print ThisWorkbook.Sheets(1).Cells(i, 1).Value 
60  Next j 

LetsContinue: 
70  Exit Sub 
Whoa: 
80  MsgBox "The Error Happened on Line : " & Erl & vbNewLine & _ 
      "Error Message : " & Err.Description & vbNewLine & _ 
      "Error Number : " & Err.Number 
90  Resume LetsContinue 
End Sub 

Quando si esegue questo codice, si otterrà qualcosa di simile.

enter image description here

Si può scegliere di personalizzare ulteriormente il messaggio di errore per renderlo più user friendly. Per esempio

'~~> Message you want to deliver to the user in case the error happens 
Const sMsg As String = "Please take a screenshot of this message and contact the developer for a resolution" 
'~~> Title of your message box 
Const sTitle As String = "Oopsie Daisies" 

'~~> Change the above as applicable 

Sub Sample() 
    Dim i As Integer, j As Integer 

10  On Error GoTo Whoa 

20  Application.ScreenUpdating = False 

30  i = 1111111111 

40  For j = 1 To i 
50   Debug.Print ThisWorkbook.Sheets(1).Cells(i, 1).Value 
60  Next j 

LetsContinue: 
70  Exit Sub 
Whoa: 
80  MsgBox "The Error Happened on Line : " & Erl & vbNewLine & _ 
      "Error Message : " & Err.Description & vbNewLine & _ 
      "Error Number : " & Err.Number & vbNewLine & vbNewLine & _ 
      sMsg, vbCritical, sTitle 
90  Resume LetsContinue 
End Sub 

enter image description here

On per il prossimo :)

Ripristina Eventi se applicabile

quando si lavora con gli eventi e si verifica un errore, se ci non viene gestito alcun errore, il codice si rompe. Sfortunatamente questo non resetta gli eventi. È molto importante ripristinare gli eventi nel gestore degli errori.

Se si nota nel codice di cui sopra stiamo impostando il Application.ScreenUpdating = False. Quando il codice si rompe, quell'evento non viene ripristinato. In questo caso dovrai gestirlo nel gestore degli errori LetsContinue. Vedi questo esempio.

'~~> Message you want to deliver to the user in case the error happens 
Const sMsg As String = "Please take a screenshot of this message and contact the developer for a resolution" 
'~~> Title of your message box 
Const sTitle As String = "Oopsie Daisies" 

'~~> Change the above as applicable 

Sub Sample() 
    Dim i As Integer, j As Integer 

10  On Error GoTo Whoa 

20  Application.ScreenUpdating = False 

30  i = 1111111111 

40  For j = 1 To i 
50   Debug.Print ThisWorkbook.Sheets(1).Cells(i, 1).Value 
60  Next j 

LetsContinue: 
70  Application.ScreenUpdating = True 
80  Exit Sub 
Whoa: 
90  MsgBox "The Error Happened on Line : " & Erl & vbNewLine & _ 
      "Error Message : " & Err.Description & vbNewLine & _ 
      "Error Number : " & Err.Number & vbNewLine & vbNewLine & _ 
      sMsg, vbCritical, sTitle 
100  Resume LetsContinue 
End Sub 

Come Philippe, suggerisco inoltre di utilizzare MZ-Tools per VBA. Lo sto usando ora per anni d'asino ...

Spero che questo aiuti.

+2

risposta interessante. Se io dove dovrei implementare qualche oggetto di errore e un metodo error.add che aggiungerà automaticamente gli errori a un file txt, un database o anche una mail. Questo eviterà il passaggio "screenshot" e ti consentirà di gestire le tue attività di debug come un professionista! –

+0

Vero :) E ho pensato all'opzione email ma non ero sicuro che l'utente potesse avere accesso alla posta elettronica su quel pc. Sono sicuro che ciò che hai suggerito può essere facilmente incorporato se l'utente lo desidera. Il mio suggerimento sopra ruota attorno a 'KISS'. Un utente può prendere il suggerimento di cui sopra e portarlo a un livello molto più alto :) –

+0

Siddharth - Sei una forza da non sottovalutare in questo sito! Grazie per l'aiuto di tanti. La tua risposta è una soluzione eccellente per un modello di controllo degli errori di base e molte persone che stanno guardando questo non dovranno andare oltre. Tuttavia, ho bisogno di qualcosa di più robusto per il mio cliente aziendale ed è per questo che sto cercando di capire i punti salienti delle tecniche di gestione degli errori di Bovey. Mi piace l'idea che il log degli errori mi sia stato inviato per email. –