Как часто вы ловите ошибки в VBA?
А как часто вам приходится пытаться понять откуда ноги растут?
Если макрос состоит из одной процедуры, это немного другая история…
Но вот если у вас полноценный стек вызовов, когда:
Main() -> NestedSub1 -> NestedFunc -> NestedSub2 ... -> NestedSubN
как отловить, в каком произошла ошибка?
Окей, вы скажите «Поставим On Error GoTo Catch
и в Catch: Debug.Print "Function name"
«, да?
А если эту функцию вызывают несколько разных Sub/Function, как понять в каком из них произошла ошибка?
В «нормальных» (не обрезанных) языках программирования для этого придуманы Traceback’и, которые после ошибки выводят информацию о вызовах, которые привели программу к ошибке. VBA, к сожалению, лишен этой плюшки. Так как же быть?
Изобретать костыли, конечно
Предлагаю вашему вниманию не самый умный и ленивый и прямолинейный, но все таки достаточно наглядный и действенный способ.
Итак, что нам потребуется:
-
Модуль
Exception
. -
Три процедуры:
PushTrace
,PopTrace
,PrintTrace
Собсна все.
Начнем изобретать с процедуры PushTrace
.
PushTrace
Что она будет делать? Копить в коллекцию Trace
переданные вами данные, например «Модуль.Процедура» в которой мы сейчас находимся.
Public Sub PushTrace(ParamArray Args() As Variant) If this.Trace Is Nothing Then Set this.Trace = New Collection ' Разделитель и время в начале можно выбрать на свое усмотрение this.Trace.Add DateTime.Now & ": " & Strings.Join(Args, " ") End If
Забыл уточнить, в верху модуля надо объявить приватные UDT TException
и переменную this
:
Private Type TException Trace As Collection End Type Private this As TException
Идем далее…
PopTrace
Что будет делать PopTrace
? Правильно — удалять последнюю запись в коллекции Trace
:
Public Sub PopTrace() If this.Trace Is Nothing Then Exit Sub this.Trace.Remove this.Trace.Count ' В принципе, можно еще возвращать значение, ' но я не знаю зачем :D End If
PrintTrace
Ну сейчас придется немного поднапрячься… Как вы уже догадались, PrintTrace печатает стек вызовов. Чтобы не засорить кучей мусора вбансоль (immediate window), я решил добавить опциональный аргумент LastN со значением по умолчанию 10. Это значит, что печататься будут только LastN (если не указано — 10) последних вызовов:
Public Sub PrintTrace(Optional ByVal LastN As Long = 10) If this.Trace Is Nothing Then Exit Sub If this.Trace.Count = 0 Then Exit Sub If LastN < 1 Then Exit Sub Dim Index As Long If LastN > this.Trace.Count Or this.Trace.Count - LastN < 1 Then Index = 1 Else Index = this.Trace.Count - LastN + 1 End If Dim TraceString As String Dim T As Variant Dim Sep As String Do While this.Trace.Count > 0 If this.Trace.Count < Index Then Exit Do T = this.Trace(Index) Index = Index + 1 TraceString = TraceString & Sep & T Sep = VbNewLine Loop Dim ErrMsg As String ErrMsg = "#{number}: [{source}] {message}" ErrMsg = Strings.Replace(ErrMsg, "{number}", Err.Number) ErrMsg = Strings.Replace(ErrMsg, "{source}", Err.Source) ErrMsg = Strings.Replace(ErrMsg, "{message}", Err.Description) TraceString = TraceString & Sep & Sep & ErrMsg Debug.Print "Traceback:" Debug.Print TraceString End Sub
Описание:
-
Делаем «проверки на дурака».
-
Далее, вычисляем стартовый индекс: если
LastN
большеTrace
илиTrace - LastN < 1
, тогда стартовый индекс 1 (т.к. коллекция стартует с 1), иначе индекс =Trace - LastN + 1
. -
Дальше в цикле вытаскиваем значения из Trace, начиная со стартового индекса.
-
Потом извлекаем текст ошибки в подготовленный шаблон
#{number}: [{source}] {message}
. -
И в итоге выводим результат в вбансоль.
Как пользоваться?
А, как я и писал выше, все очень просто:
-
В начале каждой (ну или только отслеживаемой) процедуры вызываем:
Exception.PushTrace "<Module.Sub>" ' подставить свои данные
-
В конце каждой процедуры, непосредственно перед чистым выходом (без ошибки), это важно:
Exception.PopTrace
-
На основную процедуру вешаем
On Error GoTo Catch
и вCatch
вызываем:Exception.PrintTrace ' при этом можно ограничить вывод с помощью LastN
Пример:
' Точка входа Public Sub Main() On Error GoTo Catch Exception.PushTrace "Module.Main" ' Положили в Trace DoStuff Exception.PopTrace ' Удалили из Trace Exit Sub Catch: Exception.PrintTrace LastN:=3 End Sub Public Sub DoStuff() Exception.PushTrace "Module.DoStuff" ' Положили в Trace DoOtherStuff Exception.PopTrace ' Удалили из Trace End Sub Public Sub DoOtherStuff() Exception.PushTrace "Module.DoOtherStuff" ' Положили в Trace DoSomeERRORStuff Exception.PopTrace ' Удалили из Trace End Sub Public Sub DoSomeERRORStuff() Exception.PushTrace "Module.DoSomeERRORStuff" ' Положили в Trace DoSomeOtherStuff ' Эта процедура выполнится без ошибки ' поэтому в Trace останется только ' текущая процедура, т.к. ниже ошибка Err.Raise 9, Source:="DoSomeERRORStuff", Description:="THIS IS THE ERROR" Exception.PopTrace ' Недостижимый код End Sub Public Sub DoSomeOtherStuff() Exception.PushTrace "Module.DoSomeOtherStuff" DoSomeOtherStuff Exception.PopTrace ' Удалили из Trace End Sub
На выходе получим что-то вроде:
Traceback: 01.01.1970 0:00:00: Module.DoStuff 01.01.1970 0:00:00: Module.DoOtherStuff 01.01.1970 0:00:00: Module.DoSomeERRORStuff #9: [DoSomeERRORStuff] THIS IS THE ERROR
Обратите внимание, что так как мы поставили ограничитель LastN = 3
, вывелось только три последних Trace.
Как-то так, надеюсь будет полезно 🙂
P.S. Я, кстати, еще в телеграм иногда пишу всякую vba’шную (и не только) всячину.
ссылка на оригинал статьи https://habr.com/ru/articles/873686/
Добавить комментарий