Centralna obsługa błędów to zaawansowany sposób obsługiwania błędów w aplikacjach VBA. Jej głównym zadaniem jest przerwanie działania aplikacji, bez względu na to, w której linii kodu wystąpił błąd.
Idea centralnej obsługi błędów
Jakkolwiek nazwa zagadnienia brzmi dosyć „groźnie”, tak w gruncie rzeczy chodzi o właściwą reakcję na błąd, który pojawił się w dowolnym miejscu w kodzie.
Poprzez „właściwą reakcję” mam na myśli:
- Przerwanie makra.
- Wyświetlenie okienka z dokładną informacją o błędzie.
- Przywrócenie ustawień domyślnych w podprocedurze wyzwalającej błąd.
- Przywrócenie ustawień domyślnych w procedurze głównej.
Makro główne i podprocedury
Rozważmy przykład, w którym mamy makro główne ProceduraA i trzy podprocedury, które będziemy wywoływć z poziomu ProceduraA.
Poniżej wklejam kod, ale warto w tym momencie otworzyć plik z przykładem.
1 2 3 4 5 6 7 |
Private Const ms_MODUL As String = "M0_ModulGlowny" Option Explicit Public gl_BLAD_APLIKACJI As Long Public Const gb_DEBUG_TRYB = False |
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
Public Sub ProceduraA() Dim xlKalkulacja As XlCalculation Const sPROC As String = "ProceduraA" 'Aktywuj obsługę błędów na starcie 1 On Error GoTo ObslugaBledu 'Zapisz aktualne przeliczanie i ustaw na manualne 2 xlKalkulacja = Application.Calculation 3 Application.Calculation = xlCalculationManual 'Ustaw w komórce A1 wpis "A" 4 wksDane.Range("A1").Value = "A" 'Uruchom makro "ProceduraB" 5 Call ProceduraB 6 If gl_BLAD_APLIKACJI <> 0 Then GoTo Wyjscie 'Uruchom makro "ProceduraC" 7 Call ProceduraC 8 If gl_BLAD_APLIKACJI <> 0 Then GoTo Wyjscie 'Uruchom makro "ProceduraD" 9 Call ProceduraD 10 If gl_BLAD_APLIKACJI <> 0 Then GoTo Wyjscie Wyjscie: 11 Application.Calculation = xlKalkulacja 12 gl_BLAD_APLIKACJI = 0 13 On Error GoTo 0 14 Exit Sub ObslugaBledu: 15 If gb_DEBUG_TRYB Then Stop 16 Application.ScreenUpdating = True 17 MsgBox Title:="Błąd programu!", Buttons:=vbInformation, _ Prompt:="Informacje dotyczące błędu: " & vbCr & vbCr & _ "Numer: " & vbTab & Err.Number & vbCr & _ "Opis: " & vbTab & Err.Description & vbCr & vbCr & _ "Moduł: " & vbTab & ms_MODUL & vbCr & _ "Makro: " & vbTab & sPROC & vbCr & _ "Linia: " & vbTab & Erl() 18 gl_BLAD_APLIKACJI = Err.Number 19 GoTo Wyjscie End Sub |
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 ProceduraB() Const sPROC As String = "ProceduraB" 'Aktywuj obsługę błędów na starcie 1 On Error GoTo ObslugaBledu 'Ustaw w komórce A1 wpis "B" 2 wksDane.Range("A1").Value = "B" Wyjscie: 3 On Error GoTo 0 4 Exit Sub ObslugaBledu: 5 If gb_DEBUG_TRYB Then Stop 6 Application.ScreenUpdating = True 7 MsgBox Title:="Błąd programu!", Buttons:=vbInformation, _ Prompt:="Informacje dotyczące błędu: " & vbCr & vbCr & _ "Numer: " & vbTab & Err.Number & vbCr & _ "Opis: " & vbTab & Err.Description & vbCr & vbCr & _ "Moduł: " & vbTab & ms_MODUL & vbCr & _ "Makro: " & vbTab & sPROC & vbCr & _ "Linia: " & vbTab & Erl() 8 gl_BLAD_APLIKACJI = Err.Number 9 GoTo Wyjscie End Sub |
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 ProceduraC() Const sPROC As String = "ProceduraC" 'Aktywuj obsługę błędów na starcie 1 On Error GoTo ObslugaBledu 'Wygeneruj błąd dzielenia przez 0 2 wksDane.Range("A1").Value = 5 / 0 Wyjscie: 3 On Error GoTo 0 4 Exit Sub ObslugaBledu: 5 If gb_DEBUG_TRYB Then Stop 6 Application.ScreenUpdating = True 7 MsgBox Title:="Błąd programu!", Buttons:=vbInformation, _ Prompt:="Informacje dotyczące błędu: " & vbCr & vbCr & _ "Numer: " & vbTab & Err.Number & vbCr & _ "Opis: " & vbTab & Err.Description & vbCr & vbCr & _ "Moduł: " & vbTab & ms_MODUL & vbCr & _ "Makro: " & vbTab & sPROC & vbCr & _ "Linia: " & vbTab & Erl() 8 gl_BLAD_APLIKACJI = Err.Number 9 GoTo Wyjscie End Sub |
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 ProceduraD() Const sPROC As String = "ProceduraD" 'Aktywuj obsługę błędów na starcie 1 On Error GoTo ObslugaBledu 'Ustaw w komórce A1 wpis "D" 2 wksDane.Range("A1").Value = "D" Wyjscie: 3 On Error GoTo 0 4 Exit Sub ObslugaBledu: 5 If gb_DEBUG_TRYB Then Stop 6 Application.ScreenUpdating = True 7 MsgBox Title:="Błąd programu!", Buttons:=vbInformation, _ Prompt:="Informacje dotyczące błędu: " & vbCr & vbCr & _ "Numer: " & vbTab & Err.Number & vbCr & _ "Opis: " & vbTab & Err.Description & vbCr & vbCr & _ "Moduł: " & vbTab & ms_MODUL & vbCr & _ "Makro: " & vbTab & sPROC & vbCr & _ "Linia: " & vbTab & Erl() 8 gl_BLAD_APLIKACJI = Err.Number 9 GoTo Wyjscie End Sub |
Miejsce wystąpienia błędu
Procedura główna nie zawiera błędu. Podobnie procedura B i procedura D.
Błąd zaś znajduje się w procedurze C – jest to błąd dzielenia przez zero, który umieściliśmy w makrze celowo, aby wywołać centralną obsługę błędów i pokazać jej działanie.
Zmienna publiczna z numerem błędu
Na samej górze mamy zadeklarowaną zmienną publiczną gl_BLAD_APLIKACJI, której zadaniem jest przechowanie informacji na temat numeru błędu (jeśli taki się pojawi).
Domyślnie jej wartość to 0 (brak błędu). Jej wartość sprawdzam natomiast po każdym wykonaniu podprocedury. Jeśli różni się ona od zera, wówczas jest to znak, że w podprocedurze wystąpił błąd i należy przerwać działanie całej aplikacji.
Analiza makra
Przeanalizujmy działanie makra:
W procedurze głównej ProceduraA na samym początku aktywujemy obsługę błędów poleceniem On Error GoTo. Następnie zapisujemy bieżące ustawienia (przeliczanie komórek) i wprowadzamy ustawienia własne (przeliczanie manualne)
Wpisujemy do komórki A1 arkusza wksDane wartość A – nie generuje to błędu. Błąd pojawiłby się gdybyśmy np. nie mieli w naszym skoroszycie arkusza z nazwą kodową wksDane. Wtedy od razu makro przeskoczyłoby do etykiety On Error GoTo ObslugaBledu (linia kodu nr 15).
Wywołujemy ProceduraB. To makro zmienia nam jedynie wartość komórki A1, więc również nie generuje błędu
Wywołujemy ProceduraC. W tym makrze specjalnie umieściłem linię kodu, która wygeneruje nam błąd (dzielenie przez zero).
W wyniku tego nastąpi przejście do obsługi błędów w ProceduraC. Zostanie wyświetlone okienko ze szczegółowymi informacjami dotyczącymi błędu (numer, opis, moduł, procedura, linia kodu).

Następnie zmiennej gl_BLAD_APLIKACJI zostanie przypisany numer błędu (11). Na koniec nastąpi przejście do etykiety Wyjscie, która przywróci ustawienia bazowe dla ProceduraC.
Wracamy do ProceduraA i w linii kodu nr 8 sprawdzamy warunek If gl_BLAD_APLIKACJI <> 0 Then GoTo Wyjscie. Wiemy, że wartość zmiennej publicznej jest różna od 0 (11), więc makro przerywa swoje działanie (ProceduraD nie zostanie uruchomiona!) i przenosi się do etykiety Wyjscie, która przywraca nam ustawienia wyjściowe (przeliczanie danych) i zeruje wartość zmiennej gl_BLAD_APLIKACJI
Korzyści stosowania COB
Główny atut centralnej obsługi błędów polega na tym, że działanie aplikacji jest przerywane w momencie wystąpienia błędu w dowolnym miejscu w kodzie VB.
Następuje wtedy wyświetlenie szczegółowej informacji w okienku na temat błędu oraz „posprzątanie” procedury, w której błąd wystąpił (u nas ProceduraC) i procedury głównej (u nas ProceduraA).
Uwagi
W praktyce często spotykam się z sytuacją, w której Klient chce, aby po wystąpieniu błędu, makro kontynuowało bieg. Czyli, przekładając to na naszą sytuację, aby po wystąpieniu błędu w ProceduraC, uruchomiona została jeszcze ProceduraD.
Jest to bardzo zła praktyka, na którą się nie godzę z dwóch powodów.
Po pierwsze, błędy wyskakują wtedy kaskadowo jeden za drugim co nie tylko jest irytujące, ale grozi uszkodzeniem pliku.
Po drugie, ignorujemy fakt wystąpienia błędu. Możemy więc działać na błędnych założeniach i mieć złudne wrażenie, że aplikacja prawidłowo wykonała swoją pracę, ponieważ makra wykonały się do końca.
Plik XLSM
Uruchom procedurę główną metodą krokową (klawisz F8) i zobacz co się stanie w sytuacji, gdy kompilator napotka na błąd.
