VBA
Gestione degli errori
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.