Traceback в VBA? ЧТО?

от автора

O_o

O_o

Как часто вы ловите ошибки в VBA?
А как часто вам приходится пытаться понять откуда ноги растут?

Если макрос состоит из одной процедуры, это немного другая история…
Но вот если у вас полноценный стек вызовов, когда:

Main() -> NestedSub1 -> NestedFunc -> NestedSub2 ... -> NestedSubN

как отловить, в каком произошла ошибка?
Окей, вы скажите «Поставим On Error GoTo Catch и в Catch: Debug.Print "Function name"«, да?

А если эту функцию вызывают несколько разных Sub/Function, как понять в каком из них произошла ошибка?

В «нормальных» (не обрезанных) языках программирования для этого придуманы Traceback’и, которые после ошибки выводят информацию о вызовах, которые привели программу к ошибке. VBA, к сожалению, лишен этой плюшки. Так как же быть?

Изобретать костыли, конечно

Предлагаю вашему вниманию не самый умный и ленивый и прямолинейный, но все таки достаточно наглядный и действенный способ.

Итак, что нам потребуется:

  1. Модуль Exception.

  2. Три процедуры: 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

Описание:

  1. Делаем «проверки на дурака».

  2. Далее, вычисляем стартовый индекс: если LastN больше Trace или Trace - LastN < 1, тогда стартовый индекс 1 (т.к. коллекция стартует с 1), иначе индекс = Trace - LastN + 1.

  3. Дальше в цикле вытаскиваем значения из Trace, начиная со стартового индекса.

  4. Потом извлекаем текст ошибки в подготовленный шаблон #{number}: [{source}] {message}.

  5. И в итоге выводим результат в вбансоль.

Как пользоваться?

А, как я и писал выше, все очень просто:

  1. В начале каждой (ну или только отслеживаемой) процедуры вызываем:
    Exception.PushTrace "<Module.Sub>" ' подставить свои данные

  2. В конце каждой процедуры, непосредственно перед чистым выходом (без ошибки), это важно:
    Exception.PopTrace

  3. На основную процедуру вешаем 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *