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.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow