manejo errores error excel vba

errores - resume next excel vba



Correctamente manejo de errores en VBA(Excel) (5)

He estado trabajando con VBA desde hace bastante tiempo, pero todavía no estoy tan seguro sobre el manejo de errores.

Un buen artículo es el de CPearson.com

Sin embargo, todavía me pregunto si la forma en que solía hacer ErrorHandling era / está completamente equivocada: bloque 1

On Error Goto ErrCatcher If UBound(.sortedDates) > 0 Then // Code Else ErrCatcher: // Code End If

La cláusula if, porque si es verdadera, se ejecutará y si falla la Goto irá a la parte Else, ya que el Ubound de una matriz nunca debe ser cero o menos, sin un error, este método funcionó bastante bien hasta aquí.

Si lo entendí bien debería ser así: Bloque 2

On Error Goto ErrCatcher If Ubound(.sortedDates) > 0 Then // Code End If Goto hereX ErrCatcher: //Code Resume / Resume Next / Resume hereX hereX:

O incluso así: Bloque 3

On Error Goto ErrCatcher If Ubound(.sortedDates) > 0 Then // Code End If ErrCatcher: If Err.Number <> 0 then //Code End If

La forma más común que veo es aquella, que el Error "Catcher" está al final de un sub y el Sub realmente termina antes con un "Sub de salida", pero sin embargo no es un poco confuso si el Sub es bastante grande si saltas al revés para leer el código?

Bloque 4

Fuente del siguiente código: CPearson.com

On Error Goto ErrHandler: N = 1 / 0 '' cause an error '' '' more code '' Exit Sub ErrHandler: '' error handling code'' Resume Next End Sub

¿Debería ser como en el Bloque 3?

Gracias por leer mi pregunta Saludos skofgar


Definitivamente no usaría Block1. No parece correcto tener el bloque Error en una declaración IF no relacionada con Errores.

Los bloques 2,3 y 4, supongo, son variaciones de un tema. Prefiero el uso de los Bloques 3 y 4 sobre 2 solo porque no me gusta la declaración GOTO; Generalmente uso el método Block4. Este es un ejemplo de código que uso para verificar si se agrega la biblioteca Microsoft ActiveX Data Objects 2.8 y si no se agrega o usa una versión anterior si 2.8 no está disponible.

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


Dos propósitos principales para el manejo de errores:

  1. Errores de trampa que puede predecir pero que no puede controlar al usuario (por ejemplo, guardar un archivo en una memoria USB cuando se han eliminado las unidades de memoria)
  2. Para errores inesperados, presente al usuario un formulario que le informe cuál es el problema. De esta forma, pueden transmitirte ese mensaje y quizás puedas darles una solución mientras trabajas en una solución.

Entonces, ¿cómo harías esto?

En primer lugar, cree un formulario de error para mostrar cuando se produce un error inesperado.

Podría ser algo como esto (FYI: El mío se llama frmErrors):

Observe las siguientes etiquetas:

  • lblHeadline
  • lblSource
  • lblProblem
  • lblResponse

Además, los botones de comando estándar:

  • Ignorar
  • Rever
  • Cancelar

No hay nada espectacular en el código para esta forma:

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

Básicamente, desea saber qué botón presionó el usuario cuando se cierra el formulario.

A continuación, cree un Módulo de manejo de errores que se utilizará en toda su aplicación 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

Puede tener errores que serán personalizados solo para su aplicación. Esto normalmente sería una breve lista de errores específicamente para su aplicación. Si aún no tiene un módulo de constantes, cree uno que contenga un ENUM de sus errores personalizados. (NOTA: Office ''97 NO es compatible con ENUMS). El ENUM debería verse más o menos así:

Public Enum CustomErrorName MaskedFilterNotSupported InvalidMonthNumber End Enum

Crea un módulo que lanzará tus errores personalizados.

''******************************************************************************************************************************** '' 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

Ahora está bien equipado para atrapar errores en su programa. Su sub (o función) debería verse más o menos así:

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

Copiar / pegar el código anterior puede no funcionar al salir de la puerta, pero definitivamente debe darle la esencia.

Por cierto, si alguna vez necesitas que haga el logotipo de tu empresa, búscame en http://www.MySuperCrappyLogoLabels99.com


El bloque 2 no funciona porque no restablece el manejador de errores y puede causar un bucle infinito. Para que Manejo de errores funcione correctamente en VBA, necesita una instrucción de Resume para borrar el controlador de errores. El Resume también reactiva el controlador de errores anterior. El bloque 2 falla porque un nuevo error volvería al controlador de errores anterior causando un bucle infinito.

El bloque 3 falla porque no hay una instrucción de Resume por lo que cualquier intento de manejo de error después de eso fallará.

Cada controlador de errores debe finalizar al salir del procedimiento o una instrucción de Resume . Enrutar la ejecución normal alrededor de un controlador de errores es confuso. Esta es la razón por la cual los manejadores de errores generalmente están en la parte inferior.

Pero aquí hay otra forma de manejar un error en VBA. Maneja el error en línea como Try / Catch en VB.net. Hay algunas trampas, pero administradas adecuadamente funciona muy bien.

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

Fuentes:

La clave para hacer que esto funcione es usar una instrucción de Resume inmediatamente seguida por otra declaración de On Error . El Resume está dentro del controlador de errores y desvía el código a la etiqueta EndTry1 . Debe establecer inmediatamente otra declaración On Error para evitar problemas ya que el controlador de errores anterior se "reanudará". Es decir, estará activo y listo para manejar otro error. Eso podría causar que el error se repita e ingrese un bucle infinito.

Para evitar volver a utilizar el manejador de errores anterior, debe configurar On Error a un nuevo manejador de errores o simplemente usar On Error Goto 0 para cancelar el manejo de todos los errores.


Mantengo las cosas simples:
En el nivel del módulo, defino dos variables y configuro una para el nombre del módulo en sí.

Private Const ThisModuleName As String = "mod_Custom_Functions" Public sLocalErrorMsg As String

Dentro de cada Sub / Función del módulo, defino una variable local

Dim ThisRoutineName As String

Establecí ThisRoutineName en el nombre del sub o función

'' Housekeeping On Error Goto ERR_RTN ThisRoutineName = "CopyWorksheet"

Luego envío todos los errores a un ERR_RTN: cuando ocurren, pero primero configuro el sLocalErrorMsg para definir qué es realmente el error y proporciono alguna información de depuración.

If Len(Trim(FromWorksheetName)) < 1 Then sLocalErrorMsg = "Parameter ''FromWorksheetName'' Is Missing." GoTo ERR_RTN End If

En la parte inferior de cada sub / función, dirijo el flujo lógico de la siguiente manera

'' '' 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

Luego tengo un módulo separado que incluí en todos los proyectos llamados "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

El resultado final es un mensaje de error emergente llamándome en qué módulo, qué soubroutine y cuál fue el mensaje de error específicamente. Además, también insertará el mensaje de error y el código de Windows.


Tienes una respuesta verdaderamente maravillosa de ray023, pero tu comentario de que probablemente sea excesivo es apto. Para una versión "más ligera" ...

El bloque 1 es, en mi humilde opinión, una mala práctica. Como ya lo señaló osknows, mezclar el manejo de errores con el código de ruta normal no es bueno. Por un lado, si se produce un nuevo error mientras hay una condición de error en vigencia, no tendrá la oportunidad de manejarlo (a menos que llame desde una rutina que también tenga un controlador de error, donde la ejecución "burbujeará") )

El bloque 2 parece una imitación de un bloque Try / Catch. Debería estar bien, pero no es The VBA Way. El bloque 3 es una variación del bloque 2.

Block 4 es una versión escueta de The VBA Way. Recomiendo encarecidamente usarlo, o algo así, porque es lo que esperará cualquier otro programador de VBA que interprete el código. Permítanme presentar una pequeña expansión, sin embargo:

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

Tenga en cuenta que segundo Resume . Este es un truco que aprendí recientemente: nunca se ejecutará en el proceso normal, ya que la instrucción de Resume <label> enviará la ejecución a otro lugar. Aunque puede ser una bendición para la depuración. Cuando reciba una notificación de error, elija Depurar (o presione Ctl-Break, luego elija Depurar cuando aparezca el mensaje "La ejecución fue interrumpida"). La siguiente declaración (resaltada) será MsgBox o la siguiente declaración. Use "Establecer instrucción siguiente" (Ctl-F9) para resaltar el resumen en Resume , luego presione F8. Esto le mostrará exactamente dónde se lanzó el error.

En cuanto a su objeción a este formato "saltando", A) es lo que los programadores de VBA esperan, como se dijo anteriormente, y B) sus rutinas deben ser lo suficientemente cortas como para que no quede lejos para saltar.