Buscar..
Evitando condiciones de error.
Cuando se produce un error de tiempo de ejecución, un buen código debe manejarlo. La mejor estrategia de manejo de errores es escribir código que verifique las condiciones de error y simplemente evite ejecutar código que genere un error de tiempo de ejecución.
Un elemento clave para reducir los errores de tiempo de ejecución, es escribir pequeños procedimientos que hacen una cosa . Cuantas menos razones tengan los procedimientos para fallar, más fácil será la depuración del código en su totalidad.
Evitando el error de tiempo de ejecución 91 - Objeto o con variable de bloque no establecida:
Este error se generará cuando se utilice un objeto antes de que se asigne su referencia. Uno podría tener un procedimiento que recibe un parámetro de objeto:
Private Sub DoSomething(ByVal target As Worksheet)
Debug.Print target.Name
End Sub
Si el target
no tiene asignada una referencia, el código anterior generará un error que se evitará fácilmente al verificar si el objeto contiene una referencia de objeto real:
Private Sub DoSomething(ByVal target As Worksheet)
If target Is Nothing Then Exit Sub
Debug.Print target.Name
End Sub
Si el target
no tiene una referencia asignada, la referencia no asignada nunca se usa y no se produce ningún error.
Esta forma de salir temprano de un procedimiento cuando uno o más parámetros no es válido, se denomina cláusula de guarda .
Evitar el error de tiempo de ejecución 9 - Subíndice fuera de rango:
Este error se produce cuando se accede a una matriz fuera de sus límites.
Private Sub DoSomething(ByVal index As Integer)
Debug.Print ActiveWorkbook.Worksheets(index)
End Sub
Dado un índice mayor que el número de hojas de trabajo en ActiveWorkbook
, el código anterior generará un error de tiempo de ejecución. Una simple cláusula de guardia puede evitar que:
Private Sub DoSomething(ByVal index As Integer)
If index > ActiveWorkbook.Worksheets.Count Or index <= 0 Then Exit Sub
Debug.Print ActiveWorkbook.Worksheets(index)
End Sub
La mayoría de los errores de tiempo de ejecución se pueden evitar verificando cuidadosamente los valores que usamos antes de usarlos, y bifurcándonos en otra ruta de ejecución en consecuencia usando una simple sentencia If
: en cláusulas de seguridad que no hacen suposiciones y validan los parámetros de un procedimiento, o incluso en el Cuerpo de procedimientos más grandes.
Declaración de error
Incluso con las cláusulas de guardia, no se puede dar cuenta de manera realista siempre para todas las posibles condiciones de error que podrían ser planteadas en el cuerpo de un procedimiento. La instrucción On Error GoTo
indica a VBA que salte a una etiqueta de línea e ingrese al "modo de manejo de errores" siempre que ocurra un error inesperado en el tiempo de ejecución. Después de manejar un error, el código puede reanudar de nuevo en la ejecución "normal" con el Resume
de palabras clave.
Las etiquetas de línea denotan subrutinas : como las subrutinas se originan a partir del código BASIC heredado y GoSub
saltos GoTo
y GoSub
y declaraciones de Return
para saltar a la rutina "principal", es bastante fácil escribir código espagueti difícil de seguir si las cosas no están estructuradas de manera rigurosa . Por esta razón, es mejor que:
- un procedimiento tiene una y solo una subrutina de manejo de errores
- la subrutina de manejo de errores solo se ejecuta en un estado de error
Esto significa que un procedimiento que maneja sus errores, debe estar estructurado así:
Private Sub DoSomething()
On Error GoTo CleanFail
'procedure code here
CleanExit:
'cleanup code here
Exit Sub
CleanFail:
'error-handling code here
Resume CleanExit
End Sub
Estrategias de manejo de errores
A veces quieres manejar diferentes errores con diferentes acciones. En ese caso, inspeccionará el objeto Err
global, que contendrá información sobre el error que se generó, y actuará en consecuencia:
CleanExit:
Exit Sub
CleanFail:
Select Case Err.Number
Case 9
MsgBox "Specified number doesn't exist. Please try again.", vbExclamation
Resume
Case 91
'woah there, this shouldn't be happening.
Stop 'execution will break here
Resume 'hit F8 to jump to the line that raised the error
Case Else
MsgBox "An unexpected error has occurred:" & vbNewLine & Err.Description, vbCritical
Resume CleanExit
End Select
End Sub
Como una guía general, considere activar el manejo de errores para una subrutina o función completa, y manejar todos los errores que puedan ocurrir dentro de su alcance. Si solo necesita manejar los errores en la sección pequeña del código, active y desactive el manejo de errores al mismo nivel:
Private Sub DoSomething(CheckValue as Long)
If CheckValue = 0 Then
On Error GoTo ErrorHandler ' turn error handling on
' code that may result in error
On Error GoTo 0 ' turn error handling off - same level
End If
CleanExit:
Exit Sub
ErrorHandler:
' error handling code here
' do not turn off error handling here
Resume
End Sub
Línea de números
VBA admite números de línea de estilo heredado (por ejemplo, QBASIC). La propiedad oculta de Erl
se puede usar para identificar el número de línea que generó el último error. Si no estás usando números de línea, Erl
solo devolverá 0.
Sub DoSomething()
10 On Error GoTo 50
20 Debug.Print 42 / 0
30 Exit Sub
40
50 Debug.Print "Error raised on line " & Erl ' returns 20
End Sub
Si está usando números de línea, pero no de manera consistente, entonces Erl
devolverá el último número de línea antes de la instrucción que generó el error .
Sub DoSomething()
10 On Error GoTo 50
Debug.Print 42 / 0
30 Exit Sub
50 Debug.Print "Error raised on line " & Erl 'returns 10
End Sub
Tenga en cuenta que Erl
también solo tiene precisión Integer
y se desbordará silenciosamente. Esto significa que los números de línea fuera del rango de enteros darán resultados incorrectos:
Sub DoSomething()
99997 On Error GoTo 99999
99998 Debug.Print 42 / 0
99999
Debug.Print Erl 'Prints 34462
End Sub
El número de línea no es tan relevante como la declaración que causó el error, y las líneas de numeración rápidamente se vuelven tediosas y no son fáciles de mantener.
Reanudar palabra clave
Una subrutina de manejo de errores puede:
- ejecutar hasta el final del procedimiento, en cuyo caso la ejecución se reanuda en el procedimiento de llamada.
- o, use la palabra clave
Resume
para reanudar la ejecución dentro del mismo procedimiento.
La palabra clave Resume
solo se debe utilizar dentro de una subrutina de manejo de errores, porque si VBA encuentra Resume
sin estar en un estado de error, se genera el error 20 "Reanudar sin error".
Hay varias formas en que una subrutina de manejo de errores puede usar la palabra clave Resume
:
-
Resume
utilizado solo, la ejecución continúa en la declaración que causó el error . Si el error no se maneja realmente antes de hacer eso, entonces el mismo error volverá a aparecer, y la ejecución podría entrar en un bucle infinito. -
Resume Next
continúa la ejecución en la instrucción inmediatamente después de la instrucción que causó el error. Si el error no se maneja realmente antes de hacer eso, entonces se permite que la ejecución continúe con datos potencialmente inválidos, lo que puede resultar en errores lógicos y un comportamiento inesperado. -
Resume [line label]
continúa la ejecución en la etiqueta de línea especificada (o número de línea, si está usando números de línea de estilo heredado). Por lo general, esto permitiría ejecutar algún código de limpieza antes de salir del procedimiento de forma limpia, como asegurarse de que la conexión de la base de datos se cierre antes de regresar a la persona que llama.
En error reanudar siguiente
La propia declaración de On Error
puede usar la palabra clave Resume
para indicar al tiempo de ejecución de VBA que ignore efectivamente todos los errores .
Si el error no se maneja realmente antes de hacer eso, entonces se permite que la ejecución continúe con datos potencialmente inválidos, lo que puede resultar en errores lógicos y un comportamiento inesperado .
El énfasis anterior no se puede enfatizar lo suficiente. On Error Resume Next ignora efectivamente todos los errores y los empuja debajo de la alfombra . Un programa que explota con un error de tiempo de ejecución dado una entrada no válida es mejor que uno que se sigue ejecutando con datos desconocidos / no deseados, ya sea porque el error es mucho más fácil de identificar. On Error Resume Next
puede ocultar errores fácilmente.
La declaración On Error
tiene un ámbito de procedimiento, por eso normalmente debería haber solo una , tal como la declaración On Error
en un procedimiento determinado.
Sin embargo, a veces no se puede evitar una condición de error, y saltar a una subrutina de manejo de errores solo para Resume Next
simplemente no se siente bien. En este caso específico, lo conocido a fallar posiblemente-afirmación puede ser envuelto entre dos On Error
declaraciones:
On Error Resume Next
[possibly-failing statement]
Err.Clear 'resets current error
On Error GoTo 0
La instrucción On Error GoTo 0
restablece el manejo de errores en el procedimiento actual, de manera que cualquier instrucción adicional que cause un error en tiempo de ejecución se manejaría dentro de ese procedimiento y en lugar de eso pasaría la pila de llamadas hasta que la detecte un controlador de errores activo. Si no hay un controlador de errores activo en la pila de llamadas, se tratará como una excepción no controlada.
Public Sub Caller()
On Error GoTo Handler
Callee
Exit Sub
Handler:
Debug.Print "Error " & Err.Number & " in Caller."
End Sub
Public Sub Callee()
On Error GoTo Handler
Err.Raise 1 'This will be handled by the Callee handler.
On Error GoTo 0 'After this statement, errors are passed up the stack.
Err.Raise 2 'This will be handled by the Caller handler.
Exit Sub
Handler:
Debug.Print "Error " & Err.Number & " in Callee."
Resume Next
End Sub
Errores personalizados
A menudo, al escribir una clase especializada, querrá que genere sus propios errores específicos, y querrá una forma limpia para que el usuario / código de llamada maneje estos errores personalizados. Una buena forma de lograrlo es mediante la definición de un tipo de Enum
dedicado:
Option Explicit
Public Enum FoobarError
Err_FooWasNotBarred = vbObjectError + 1024
Err_BarNotInitialized
Err_SomethingElseHappened
End Enum
El uso de la vbObjectError
incorporada vbObjectError
garantiza que los códigos de error personalizados no se superpongan con los códigos de error reservados / existentes. Solo el primer valor de enumeración debe especificarse explícitamente, ya que el valor subyacente de cada miembro de Enum
es 1
mayor que el miembro anterior, por lo que el valor subyacente de Err_BarNotInitialized
es implícitamente vbObjectError + 1025
.
Elevando tus propios errores de ejecución.
Se puede Err.Raise
un error de tiempo de ejecución utilizando la declaración Err.Raise
, por lo que el error personalizado Err_FooWasNotBarred
se puede Err_FooWasNotBarred
siguiente manera:
Err.Raise Err_FooWasNotBarred
El método Err.Raise
también puede tomar parámetros personalizados de Description
y Source
; por esta razón, es una buena idea también definir constantes para contener la descripción de cada error personalizado:
Private Const Msg_FooWasNotBarred As String = "The foo was not barred."
Private Const Msg_BarNotInitialized As String = "The bar was not initialized."
Y luego crea un método privado dedicado para elevar cada error:
Private Sub OnFooWasNotBarredError(ByVal source As String)
Err.Raise Err_FooWasNotBarred, source, Msg_FooWasNotBarred
End Sub
Private Sub OnBarNotInitializedError(ByVal source As String)
Err.Raise Err_BarNotInitialized, source, Msg_BarNotInitialized
End Sub
La implementación de la clase puede simplemente llamar a estos procedimientos especializados para generar el error:
Public Sub DoSomething()
'raises the custom 'BarNotInitialized' error with "DoSomething" as the source:
If Me.Bar Is Nothing Then OnBarNotInitializedError "DoSomething"
'...
End Sub
El código del cliente puede manejar Err_BarNotInitialized
como lo haría con cualquier otro error, dentro de su propia subrutina de manejo de errores.
Nota: la palabra clave de Error
heredada también se puede usar en lugar de Err.Raise
, pero está obsoleta / obsoleta.