Sök…
Undvik felförhållanden
När ett runtime-fel inträffar bör bra kod hantera det. Den bästa felhanteringsstrategin är att skriva kod som kontrollerar för felförhållanden och helt enkelt undviker att köra kod som resulterar i ett runtime-fel.
Ett viktigt element i att minska runtime-fel är att skriva små procedurer som gör en sak . Ju färre skäl procedurer måste misslyckas, desto lättare är koden som helhet att felsöka.
Undvika runtime-fel 91 - Objekt eller med blockvariabel inte inställd:
Det här felet kommer att tas upp när ett objekt används innan dess referens tilldelas. Man kan ha en procedur som tar emot en objektparameter:
Private Sub DoSomething(ByVal target As Worksheet)
Debug.Print target.Name
End Sub
Om target
inte har tilldelats en referens, kommer koden ovan att skapa ett fel som lätt undviks genom att kontrollera om objektet innehåller en verklig objektreferens:
Private Sub DoSomething(ByVal target As Worksheet)
If target Is Nothing Then Exit Sub
Debug.Print target.Name
End Sub
Om target
inte har tilldelats en referens används aldrig den otilldelade referensen och inget fel uppstår.
Det här sättet att tidigt lämna en procedur när en eller flera parametrar inte är giltiga kallas en skyddsklausul .
Undvik runtime-fel 9 - Prenumeration utanför räckvidden:
Det här felet uppstår när en matris nås utanför dess gränser.
Private Sub DoSomething(ByVal index As Integer)
Debug.Print ActiveWorkbook.Worksheets(index)
End Sub
Med tanke på ett index som är större än antalet kalkylblad i ActiveWorkbook
kommer koden ovan att höja ett körtidsfel. En enkel skyddsklausul kan undvika att:
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
De flesta runtime-fel kan undvikas genom att noggrant verifiera de värden vi använder innan vi använder dem, och grenas på en annan exekveringsväg i enlighet därmed med ett enkelt If
uttalande - i skyddsklausuler som inte gör några antaganden och validerar en procedurs parametrar, eller till och med i större procedurer.
Vid felmeddelande
Även med skyddsklausuler kan man inte realistiskt alltid redogöra för alla möjliga felförhållanden som kan tas upp i en procedur. On Error GoTo
uttalandet On Error GoTo
instruerar VBA att hoppa till en radetikett och gå in i "felhanteringsläge" när ett oväntat fel inträffar vid körning. Efter att ha hanterat ett fel kan koden återupptas till "normal" körning med nyckelordet Resume
.
Linjetiketter anger subroutiner : eftersom subroutiner härstammar från äldre BASIC-kod och använder GoTo
och GoSub
hopp och Return
uttalanden för att hoppa tillbaka till den "huvudsakliga" rutinen, är det ganska lätt att skriva svag att följa spaghettikod om saker inte är strikt strukturerade . Av denna anledning är det bäst att:
- en procedur har en och endast en felhanterande subroutin
- felhanteringssubutinen körs bara alltid i feltillstånd
Detta innebär att en procedur som hanterar sina fel bör struktureras så här:
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
Felhantering av strategier
Ibland vill du hantera olika fel med olika åtgärder. I så fall kommer du att inspektera det globala Err
objektet, som kommer att innehålla information om felet som uppstod - och agera i enlighet därmed:
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
Som en allmän riktlinje, överväg att aktivera felhanteringen för hela subrutinen eller funktionen och hantera alla fel som kan uppstå inom dess omfattning. Om du bara behöver hantera fel i det lilla avsnittet i koden - slå på och stänga av felhantering på samma nivå:
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
Radnummer
VBA stöder gamla nummer (t.ex. QBASIC) radnummer. Den dolda egenskapen Erl
kan användas för att identifiera radnumret som höjde det sista felet. Om du inte använder linjenummer kommer Erl
bara att returnera 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
Om du använder radnummer, men inte konsekvent, då Erl
kommer tillbaka den sista raden numret innan instruktionen som höjde felet.
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
Tänk på att Erl
också bara har Integer
och tyst kommer att flyta över. Detta innebär att linjenummer utanför heltalsområdet ger felaktiga resultat:
Sub DoSomething()
99997 On Error GoTo 99999
99998 Debug.Print 42 / 0
99999
Debug.Print Erl 'Prints 34462
End Sub
Linjenumret är inte lika relevant som påståendet som orsakade felet och numreringsraderna blir snabbt tråkiga och inte riktigt underhållsvänliga.
Fortsätt nyckelordet
En subrutin med felhantering kommer antingen att:
- kör till slutet av proceduren, i vilket fall exekveringen återupptas i samtalsproceduren.
- eller använd
Resume
nyckelordet för att återuppta körningen inom samma procedur.
Resume
nyckelordet ska bara någonsin användas i ett felhanteringsunderprogram, för om VBA möter Resume
utan att vara i ett felläge höjs körtid 20 "Resume without error".
Det finns flera sätt en subhutin med felhantering kan använda nyckelordet Resume
:
-
Resume
används ensam, körningen fortsätter på det uttalande som orsakade felet . Om felet faktiskt inte hanteras innan du gör det, kommer samma fel att tas upp igen och körning kan komma in i en oändlig slinga. -
Resume Next
fortsätter körningen på uttalandet omedelbart efter uttalandet som orsakade felet. Om felet inte är faktiskt hanteras innan du gör det, då utförande tillåts fortsätta med potentiellt ogiltiga data, vilket kan resultera i logiska fel och oväntat beteende. -
Resume [line label]
fortsätter körningen på den angivna radetiketten (eller radnummer, om du använder äldre linjenummer). Detta skulle vanligtvis möjliggöra exekvering av någon saneringskod innan proceduren rent avslutas, som att säkerställa att en databasanslutning är stängd innan den återvänder till den som ringer.
Vid fel Återuppta nästa
Selve On Error
uttalandet kan använda Resume
nyckelordet för att instruera VBA-runtime att effektivt ignorera alla fel .
Om felet inte hanteras innan det görs, är körning tillåtet att fortsätta med potentiellt ogiltiga data, vilket kan leda till logiska fel och oväntat beteende .
Betoningen ovan kan inte betonas tillräckligt. Vid fel Återuppta Nästa ignorerar effektivt alla fel och skjuter dem under mattan . Ett program som blåser upp med ett runtime-fel med en ogiltig inmatning är ett bättre program än ett som fortsätter att köra med okända / oavsiktliga data - vare sig det bara är att felet är mycket lättare att identifiera. On Error Resume Next
kan enkelt dölja buggar .
On Error
uttalandet är procedurskopierat - det är därför det normalt bara ska finnas ett , ett sådant On Error
uttalande i en given procedur.
Men ibland kan ett feltillstånd inte riktigt undvikas, och att hoppa till en subhutro med felhantering bara för att Resume Next
känns inte riktigt. I detta specifika fall, den kända till eventuellt-fail uttalande kan lindas mellan två On Error
uttalanden:
On Error Resume Next
[possibly-failing statement]
Err.Clear 'resets current error
On Error GoTo 0
On Error GoTo 0
återställer felhanteringen i den aktuella proceduren, så att alla ytterligare instruktioner som orsakar ett runtime-fel skulle hanteras inom den proceduren och istället skickas upp samtalstacken tills den fångas upp av en aktiv felhanterare. Om det inte finns någon aktiv felhanterare i samtalstacken, behandlas den som ett obehandlat undantag.
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
Anpassade fel
Ofta när du skriver en specialklass, vill du att den ska höja sina egna specifika fel och du vill ha ett rent sätt för användar- / samtalskod att hantera dessa anpassade fel. Ett snyggt sätt att uppnå detta är genom att definiera en dedikerad Enum
typ:
Option Explicit
Public Enum FoobarError
Err_FooWasNotBarred = vbObjectError + 1024
Err_BarNotInitialized
Err_SomethingElseHappened
End Enum
vbObjectError
använder vbObjectError
inbyggd konstant ser du till att de anpassade felkoderna inte överlappar varandra med reserverade / befintliga felkoder. Endast det första enumvärdet behöver specificeras uttryckligen, för det underliggande värdet för varje Enum
medlem är 1
större än den tidigare medlemmen, så det underliggande värdet för Err_BarNotInitialized
är implicit vbObjectError + 1025
.
Att höja dina egna runtime-fel
Ett runtime-fel kan tas upp med Err.Raise
uttalandet, så det anpassade Err_FooWasNotBarred
felet kan höjas på följande sätt:
Err.Raise Err_FooWasNotBarred
Den Err.Raise
Metoden kan också ta egna Description
och Source
parametrar - Av denna anledning är det en bra idé att också definiera konstanter för att hålla varje anpassad fel beskrivning:
Private Const Msg_FooWasNotBarred As String = "The foo was not barred."
Private Const Msg_BarNotInitialized As String = "The bar was not initialized."
Och skapa sedan en särskild privat metod för att höja varje fel:
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
Klassens implementering kan då helt enkelt ringa dessa specialiserade procedurer för att höja felet:
Public Sub DoSomething()
'raises the custom 'BarNotInitialized' error with "DoSomething" as the source:
If Me.Bar Is Nothing Then OnBarNotInitializedError "DoSomething"
'...
End Sub
Klientkoden kan sedan hantera Err_BarNotInitialized
som om det skulle vara något annat fel, inuti den egna subrutinen för felhantering.
Observera: Nyckelordet med äldre Error
kan också användas i stället för Err.Raise
, men det är föråldrat / avskrivet.