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.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow