Ricerca…


Evitare condizioni di errore

Quando si verifica un errore di runtime, un buon codice dovrebbe gestirlo. La migliore strategia di gestione degli errori è quella di scrivere codice che controlli le condizioni di errore e di evitare semplicemente l'esecuzione di codice che si traduce in un errore di runtime.

Un elemento chiave nella riduzione degli errori di runtime, è la scrittura di piccole procedure che fanno una cosa . Minore è il motivo per cui le procedure devono fallire, più facile è il codice nel suo complesso per eseguire il debug.


Evitare l'errore di runtime 91 - Oggetto o variabile di blocco Con non impostata:

Questo errore verrà generato quando un oggetto viene utilizzato prima che venga assegnato il suo riferimento. Si potrebbe avere una procedura che riceve un parametro oggetto:

Private Sub DoSomething(ByVal target As Worksheet)
    Debug.Print target.Name
End Sub

Se a target non viene assegnato un riferimento, il codice sopra riportato genererà un errore che può essere facilmente evitato controllando se l'oggetto contiene un riferimento a un oggetto reale:

Private Sub DoSomething(ByVal target As Worksheet)
    If target Is Nothing Then Exit Sub
    Debug.Print target.Name
End Sub

Se alla target non viene assegnato un riferimento, il riferimento non assegnato non viene mai utilizzato e non si verifica alcun errore.

Questo modo di uscire anticipatamente da una procedura quando uno o più parametri non sono validi, è chiamato clausola di guardia .


Evitare l'errore di runtime 9 - Indice fuori intervallo:

Questo errore viene generato quando si accede a un array al di fuori dei suoi limiti.

Private Sub DoSomething(ByVal index As Integer)
    Debug.Print ActiveWorkbook.Worksheets(index)
End Sub

Dato un indice superiore al numero di fogli di lavoro in ActiveWorkbook , il codice sopra riportato genererà un errore di runtime. Una semplice clausola di salvaguardia può evitare che:

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 maggior parte degli errori di runtime può essere evitata verificando attentamente i valori che usiamo prima di utilizzarli, e ramificando su un altro percorso di esecuzione di conseguenza usando una semplice dichiarazione If - nelle clausole di salvaguardia che non fanno presupposizioni e convalida i parametri di una procedura, o anche nel corpo di procedure più grandi.

Sulla dichiarazione di errore

Anche con le clausole di guardia, non si può realisticamente sempre tenere conto di tutte le possibili condizioni di errore che potrebbero essere sollevate nel corpo di una procedura. L'istruzione On Error GoTo indica a VBA di passare a un'etichetta di riga e immettere "modalità di gestione degli errori" ogni volta che si verifica un errore imprevisto in fase di esecuzione. Dopo aver gestito un errore, il codice può riprendere in esecuzione "normale" utilizzando la parola chiave Resume .

Etichette delle linee denotano subroutine: perché subroutine provengono da eredità codice BASIC e utilizza GoTo e GoSub salti e Return dichiarazioni per tornare indietro alla routine "principale", è abbastanza facile da scrivere difficile da seguire spaghetti code se le cose non sono rigorosamente strutturati . Per questo motivo, è meglio che:

  • una procedura ha una sola subroutine di gestione degli errori
  • la subroutine di gestione degli errori viene eseguita sempre in uno stato di errore

Ciò significa che una procedura che gestisce i suoi errori deve essere strutturata in questo modo:

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

Strategie di gestione degli errori

A volte vuoi gestire errori diversi con azioni diverse. In questo caso ispezionerai l'oggetto Err globale, che conterrà informazioni sull'errore che è stato sollevato e agirà di conseguenza:

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

Come regola generale, prendere in considerazione l'attivazione della gestione degli errori per l'intera subroutine o funzione e gestire tutti gli errori che potrebbero verificarsi nell'ambito del proprio ambito. Se devi gestire solo gli errori nella sezione piccola sezione del codice - attiva e disattiva la gestione degli errori allo stesso livello:

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

Numeri di linea

VBA supporta i numeri di linea in stile legacy (ad es. QBASIC). La proprietà nascosta di Erl può essere utilizzata per identificare il numero di riga che ha generato l'ultimo errore. Se non stai usando i numeri di riga, Erl restituirà sempre solo 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

Se si utilizza i numeri di riga, ma non in modo coerente, allora Erl restituirà l'ultimo numero di riga prima che l'istruzione che ha generato l'errore.

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

Tieni presente che anche Erl ha solo precisione di Integer e silenziosamente traboccherà. Ciò significa che i numeri di riga al di fuori dell'intervallo intero forniranno risultati errati:

Sub DoSomething()
99997 On Error GoTo 99999
99998 Debug.Print 42 / 0
99999
      Debug.Print Erl   'Prints 34462
End Sub

Il numero di riga non è tanto rilevante quanto l'affermazione che ha causato l'errore, e le linee di numerazione diventano rapidamente noiose e non abbastanza manutenibili.

Riprendi parola chiave

Una subroutine di gestione degli errori:

  • eseguire fino alla fine della procedura, nel qual caso l'esecuzione riprende nella procedura di chiamata.
  • oppure, utilizzare la parola chiave Resume per riprendere l' esecuzione all'interno della stessa procedura.

La parola chiave Resume dovrebbe sempre essere utilizzata solo all'interno di una subroutine di gestione degli errori, poiché se VBA rileva Resume senza essere in uno stato di errore, viene generato l'errore di runtime 20 "Resume without error".

Esistono diversi modi in cui una subroutine di gestione degli errori può utilizzare la parola chiave Resume :

  • Resume usato da solo, l'esecuzione continua sulla dichiarazione che ha causato l'errore . Se l'errore non viene effettivamente gestito prima di farlo, lo stesso errore verrà nuovamente generato e l'esecuzione potrebbe entrare in un ciclo infinito.
  • Resume Next continua l'esecuzione sull'istruzione immediatamente successiva all'istruzione che ha causato l'errore. Se l'errore non viene effettivamente gestito prima di farlo, l'esecuzione può continuare con dati potenzialmente non validi, il che può causare errori logici e comportamenti imprevisti.
  • Resume [line label] continua l'esecuzione all'etichetta della linea specificata (o al numero di riga, se si utilizzano numeri di linea in stile legacy). Ciò in genere consente di eseguire un codice di pulitura prima di chiudere in modo pulito la procedura, ad esempio assicurandosi che una connessione al database sia chiusa prima di tornare al chiamante.

In caso di errore, riprendi

L'istruzione On Error può utilizzare la parola chiave Resume per indicare al runtime VBA di ignorare efficacemente tutti gli errori .

Se l'errore non viene effettivamente gestito prima di farlo, l'esecuzione può continuare con dati potenzialmente non validi, il che può causare errori logici e comportamenti imprevisti .

L'enfasi sopra non può essere enfatizzata abbastanza. Su Errore Riprendi Avanti in modo efficace ignora tutti gli errori e li spinge sotto il tappeto . Un programma che esplode con un errore di runtime dato input non valido è un programma migliore di uno che continua a funzionare con dati sconosciuti / non voluti - sia solo perché il bug è molto più facilmente identificabile. On Error Resume Next può facilmente nascondere bug .

L'istruzione On Error è a livello di procedura - ecco perché dovrebbe esserci normalmente una sola , singola istruzione On Error in una determinata procedura.

Tuttavia a volte una condizione di errore non può essere completamente evitata, e saltare a una subroutine di gestione degli errori solo per Resume Next non sembra giusto. In questo caso specifico, l'istruzione known-to-fail può essere racchiusa tra due istruzioni On Error :

On Error Resume Next
[possibly-failing statement]
Err.Clear 'resets current error
On Error GoTo 0

L'istruzione On Error GoTo 0 reimposta la gestione degli errori nella procedura corrente, in modo tale che qualsiasi ulteriore istruzione che causa un errore di runtime non venga gestita all'interno di tale procedura e passi invece allo stack di chiamate finché non viene catturato da un gestore di errori attivo. Se non è presente alcun gestore di errori attivo nello stack di chiamate, verrà considerato come un'eccezione non gestita.

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

Errori personalizzati

Spesso quando si scrive una classe specializzata, si vorrà che aumenti i propri errori specifici e si vorrà un modo pulito per il codice utente / chiamante per gestire questi errori personalizzati. Un modo efficace per raggiungere questo obiettivo è definire un tipo Enum dedicato:

Option Explicit
Public Enum FoobarError
    Err_FooWasNotBarred = vbObjectError + 1024
    Err_BarNotInitialized
    Err_SomethingElseHappened
End Enum

L'uso della vbObjectError integrata vbObjectError garantisce che i codici di errore personalizzati non si sovrappongano ai codici di errore riservati / esistenti. Solo il primo valore enum deve essere specificato esplicitamente, poiché il valore sottostante di ciascun membro Enum è 1 maggiore del membro precedente, quindi il valore sottostante di Err_BarNotInitialized è implicitamente vbObjectError + 1025 .

Aumentare i propri errori di runtime

Un errore di runtime può essere generato utilizzando l'istruzione Err.Raise , quindi l'errore Err_FooWasNotBarred personalizzato può essere generato come segue:

Err.Raise Err_FooWasNotBarred

Il metodo Err.Raise può anche utilizzare i parametri Description e Source : per questo motivo è una buona idea definire anche le costanti per contenere la descrizione di ciascun errore personalizzato:

Private Const Msg_FooWasNotBarred As String = "The foo was not barred."
Private Const Msg_BarNotInitialized As String = "The bar was not initialized."

E quindi creare un metodo privato dedicato per aumentare ogni errore:

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

L'implementazione della classe può quindi semplicemente chiamare queste procedure specializzate per sollevare l'errore:

Public Sub DoSomething()
    'raises the custom 'BarNotInitialized' error with "DoSomething" as the source:
    If Me.Bar Is Nothing Then OnBarNotInitializedError "DoSomething"
    '...
End Sub

Il codice client può quindi gestire Err_BarNotInitialized come qualsiasi altro errore, all'interno della propria subroutine di gestione degli errori.


Nota: la parola chiave Error legacy può anche essere utilizzata al posto di Err.Raise , ma è obsoleta / deprecata.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow