W tym artykule wyjaśnię na czym polega instrukcja On Error GoTo -1 i czy jest sens jej używać w aplikacjach EXCEL/VBA. Wpis dedykowany jest głównie dla osób znających VBA w stopniu zaawansowanym.
On Error GoTo … vs. Err.Clear
Polecenie On Error GoTo -1 nie jest tym samym, co Err.Clear czy On Error GoTo 0, chociaż instrukcje te mają ze sobą wiele wspólnego.
Każda z nich czyści obiekt Err – zostają wtedy utracone wszystkie informacje dotyczące ostatniego błędu. Instrukcje Err.Clear i On Error GoTo -1 dodatkowo nie wyłączają obsługi błędów użytkownika. On Error GoTo 0 wyłącza ją w obrębie danej procedury – lub mówiąc inaczej – przywraca domyślny dla VBA sposób obsługiwania błędów.
W stosunku do Err.Clear, On Error GoTo -1 potrafi natomiast zrobić jeszcze jedną ważną rzecz. Umieszczenie On Error GoTo -1 w obsłudze błędów, pozwala korzystać z innych instrukcji typu On Error… właśnie w obsłudze błędów.
Brzmi jak groch z kapustą? Spróbujmy przeanalizować to na przykładach.
Kod VBA
Poniżej wkleiłem dwa makra – jedno główne + podprocedura.
Makro główne
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
Sub MakroGlowne() Dim wksArkusz As Worksheet Const sPROC As String = "MakroGlowne" 'Aktywuj obsługę błędów na starcie 1 On Error GoTo ObslugaBledu 'Tą zmienną będziemy czyścić w etykiecie "Wyjscie" 2 Set wksArkusz = ActiveSheet 'Wywołaj podprocedurę generującą sztuczne błędy 3 Podprocedura 'Przejdź do etykiety "Wyjscie" jeśli wystąpił błąd 4 If Err.Number <> 0 Then Err.Clear: GoTo Wyjscie 'Czy to się wykona ???? 5 MsgBox "sssssss" Wyjscie: 6 Set wksArkusz = Nothing 7 Exit Sub ObslugaBledu: 8 MsgBox "Wystąpił błąd nr " & Err.Number & " (" & Err.Description & ")." & _ vbCr & vbCr & "Linia kodu nr " & Erl & " w procedurze " & _ "'" & sPROC & "' modułu '" & msMODUL & "'.", vbInformation, "BŁĄD!" 9 Resume Wyjscie End Sub |
Podprocedura
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
Private Sub Podprocedura() Dim wkbPlik As Workbook Const sPROC = "Podprocedura" 'Aktywuj obsługę błędów na starcie 1 On Error GoTo ObslugaBledu 'Tą zmienną będziemy czyścić w etykiecie "Wyjscie" 2 Set wkbPlik = ActiveWorkbook 'Wywołaj błąd nr 11 3 Err.Raise 11 Wyjscie: 4 Set wkbPlik = Nothing 5 Exit Sub ObslugaBledu: 6 MsgBox "Wystąpił błąd nr " & Err.Number & " (" & Err.Description & ")." & _ vbCr & vbCr & "Linia kodu nr " & Erl & " w procedurze " & _ "'" & sPROC & "' modułu '" & msMODUL & "'.", vbInformation, "BŁĄD!" 7 On Error GoTo -1 'Zezwól na nową obsługę błędów 8 On Error Resume Next 'Ignoruj błędy, które się pojawią 9 Err.Raise 13 'Wywołaj błąd aby został zapamiętany 10 GoTo Wyjscie 'Posprzątaj podprocedurę End Sub |
Założenia dla obsługi błędów
Całość powinna działać w następujący sposób:
- Makro główne wprowadza swoje ustawienia na starcie (tutaj jest to przypisanie zmiennej wksArkusz do aktywnego arkusza, ale równie dobrze mogą to być ustawienia przyspieszające działanie makra).
- Makro wyzwala podprocedurę, która w sposób sztuczny generuje błąd 11.
- Po wystąpieniu błędu w podprocedurze:
- Makro wyświetla MsgBox z informacją na temat błędu.
- Kod „sprząta” podprocedurę (zeruje zmienne obiektowe, przywraca ustawienia domyślne dla VBA itp.).
- Makro zamyka podprocedurę.
- Kod wraca do makra głównego aby sprawdzić czy wystąpił błąd w podprocedurze (jeżeli nie wystąpił – makro główne działa dalej – jeżeli wystąpił – kod „sprząta” makro główne (zeruje zmienne obiektowe, przywraca ustawienia domyślne dla VBA itp.) i kończy bieg.
Te dwie procedury typu Sub (Makro główne i podprocedura) realizują nasz cel.
Wprowadźmy jednak drobne zmiany w kodzie, ale uzyskać potwierdzenie dla naszych tez zawartych we wstępie notki.
Testowanie podprocedury
Uruchamiamy tylko podprocedurę. Poniżej będę chciał udowodnić prawdziwość hipotez.
1 – On Error GoTo 0 i On Error Resume Next nie działają w trybie obsługi błędów
Wystarczy zamienić w linii kodu nr 7, On Error GoTo -1 na On Error GoTo 0, aby zobaczyć, że makro „wyrzuci” błąd w linii nr 9.
Taki sam rezultat otrzymamy, gdy usuniemy linię kodu nr 7 (On Error Resume Next użyte w obsłudze błędów nie zadziała – błędy nie będą ignorowane). Podobnie nie będzie działać instrukcja On Error GoTo Etykieta.
Aby odblokować te wszystkie instrukcje, należy je poprzedzić poleceniem On Error GoTo -1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
Private Sub Podprocedura() Dim wkbPlik As Workbook Const sPROC = "Podprocedura" 'Aktywuj obsługę błędów na starcie 1 On Error GoTo ObslugaBledu 'Tą zmienną będziemy czyścić w etykiecie "Wyjscie" 2 Set wkbPlik = ActiveWorkbook 'Wywołaj błąd nr 11 3 Err.Raise 11 Wyjscie: 4 Set wkbPlik = Nothing 5 Exit Sub ObslugaBledu: 6 MsgBox "Wystąpił błąd nr " & Err.Number & " (" & Err.Description & ")." & _ vbCr & vbCr & "Linia kodu nr " & Erl & " w procedurze " & _ "'" & sPROC & "' modułu '" & msMODUL & "'.", vbInformation, "BŁĄD!" 7 On Error GoTo 0 'Spróbuj wyłączyć obsługę błędów użytkownika 8 On Error Resume Next 'Ignoruj błędy, które się pojawią 9 Err.Raise 13 'Wywołaj błąd aby został zapamiętany 10 GoTo Wyjscie 'Posprzątaj podprocedurę End Sub |
2 – On Error GoTo -1 nie wyłącza aktywnej obsługi błędów użytkownika
Jakkolwiek mogłoby się wydawać, że to polecenie (podobnie jak On Error GoTo 0) wyłącza obsługę błędów, to jednak tak nie jest.
Aby się o tym przekonać wystarczy wykomentować linię kodu nr 8. W takiej sytuacji korzystamy z polecenia On Error GoTo -1, ale nie ustanawiamy nowego sposobu obsługiwania błędów.
W efekcie makro wraca z linii nr 9, do linii nr 6 (i biegnie tak w kółko) – czyli zapamiętuje aktywną obsługę błędów On Error GoTo ObslugaBledu.
Gdyby instrukcja wyłączała obsługę błędu makro znów by się „wysypało” – tak się dzieje, gdy użyjemy w tym kontekście On Error GoTo 0).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
Private Sub Podprocedura() Dim wkbPlik As Workbook Const sPROC = "Podprocedura" 'Aktywuj obsługę błędów na starcie 1 On Error GoTo ObslugaBledu 'Tą zmienną będziemy czyścić w etykiecie "Wyjscie" 2 Set wkbPlik = ActiveWorkbook 'Wywołaj błąd nr 11 3 Err.Raise 11 Wyjscie: 4 Set wkbPlik = Nothing 5 Exit Sub ObslugaBledu: 6 MsgBox "Wystąpił błąd nr " & Err.Number & " (" & Err.Description & ")." & _ vbCr & vbCr & "Linia kodu nr " & Erl & " w procedurze " & _ "'" & sPROC & "' modułu '" & msMODUL & "'.", vbInformation, "BŁĄD!" 7 On Error GoTo -1 'Zezwól na nową obsługę błędów 8 'On Error Resume Next 'Ignoruj błędy, które się pojawią 9 Err.Raise 13 'Wywołaj błąd aby został zapamiętany 10 GoTo Wyjscie 'Posprzątaj podprocedurę End Sub |
3 – Błędy w obsłudze błędów procedury mogą być obsłużone z poziomu innego makra
Wróćmy teraz do obu makr wyjściowych. W podprocedurze zmieńmy tylko jedną linię kodu (nr 7), tak jak w punkcie nr 1.
Gdy uruchamialiśmy tylko podprocedurę, makro wyrzucało błąd – instrukcje On Error … nie działały bowiem w aktywnej obsłudze błędów procedury.
Uruchamianie zaczynamy teraz jednak od makra głównego. W efekcie otrzymujemy dwa komunikaty MsgBox (błąd nr 11 i 13), makro się jednak „nie wywali”. Wynika to z faktu, że drugi błąd (linia kodu nr 9) zostaje obsłużony już z poziomu makra głównego (On Error GoTo ObslugaBledu).
Jest to rzecz, o której warto wiedzieć, ale należy pamiętać, że nie realizuje ona naszego zamysłu. Naszą intencją była emulacja błędu nr 13, tylko po to aby przenieść informację o błędzie do makra głównego (nie chcieliśmy wyświetlać żadnej informacji odnośnie błędu nr 13).
W takim układzie podprocedura nie jest również „sprzątana”, ponieważ po wykryciu błędu, kod przeskakuje od razu do makra głównego.
4 – Polecenia Typu On Error … i Err.Clear czyszczą obiekt Err
Do okienka Watches w edytorze VB dodajmy sobie obiekt Err.
Po wykonaniu instrukcji On Error GoTo -1 lub Err.Clear zobaczymy, że obiekt Err zostanie wyczyszczony.
Oczywiście taki sam efekt zobaczymy po wykonaniu instrukcji On Error GoTo 0 lub On Error GoTo Etykieta
Czy warto używać instrukcji On Error GoTo -1?
Moim zdaniem o tej instrukcji powinniśmy myśleć w kategoriach pewnej ciekawostki, a nie narzędzia, wokół którego będziemy budować obsługę błędów w swoich aplikacjach.
Osobiście nie używam tej instrukcji, zaś w centralnej obsłudze błędów korzystam ze zmiennej publicznej, która przechwytuje mi numer ewentualnego błędu.