Vba номер строки ошибки

If your code doesn’t have line numbers, then VBA has no way of giving you line numbers.

You can write VBA and make it look 1980-like to do this:

Sub1
On Error GoTo 100
10   Call Function1
20   Call Function2
90   Exit Sub
100  Debug.Print Err.Message & " on line " & Erl
End Sub

But you don’t want to do that. Really, you don’t need a line number.

You need smaller functions that handle runtime errors.

On Error GoTo ErrHandler

When a runtime error occurs, execution jumps to the line label called ErrHandler.

     ...
     Exit Sub
ErrHandler: '<< the line label is denoted with a colon

What goes in that handler? If you’re debugging, you might want to just Stop execution there and inspect your locals:

    Stop

Then add Resume on the next line, and press F8 to step into it. Resume will return to the call that caused the error. If that’s a function call, then you need to handle runtime errors in that function.

Make sure you never leave Stop and Resume instructions in production code:

Sub WhenWillThisEnd()
    On Error GoTo ErrHandler
    Debug.Print 42/0
    Exit Sub
ErrHandler:
    Resume 'jumps back to the line that caused the error
    Resume Next 'resumes execution on the line right after the one that went boom
End Sub

Избегание условий ошибки

Когда возникает ошибка времени выполнения, хороший код должен ее обрабатывать. Лучшей стратегией обработки ошибок является запись кода, который проверяет условия ошибки и просто избегает выполнения кода, который приводит к ошибке выполнения.

Одним из ключевых элементов сокращения ошибок во время выполнения является запись небольших процедур, которые делают одно . Чем меньше процедур процедур приходится терпеть неудачу, тем проще код в целом — отлаживать.


Избежать ошибки времени выполнения 91 — Объект или С заблокированной переменной блока:

Эта ошибка будет повышена, если объект используется до назначения ссылки. Возможно, у вас есть процедура, которая получает параметр объекта:

Private Sub DoSomething(ByVal target As Worksheet)
    Debug.Print target.Name
End Sub

Если target не назначена ссылка, приведенный выше код вызовет ошибку, которую легко избежать, проверяя, содержит ли объект фактическую ссылку на объект:

Private Sub DoSomething(ByVal target As Worksheet)
    If target Is Nothing Then Exit Sub
    Debug.Print target.Name
End Sub

Если target назначению не присвоена ссылка, то непризнанная ссылка никогда не используется, и ошибка не возникает.

Этот способ раннего выхода из процедуры, когда один или несколько параметров недопустимы, называется предложением охраны .


Избегайте ошибки времени выполнения 9 — Подкласс вне диапазона:

Эта ошибка возникает при доступе к массиву за пределами его границ.

Private Sub DoSomething(ByVal index As Integer)
    Debug.Print ActiveWorkbook.Worksheets(index)
End Sub

Учитывая, что индекс больше, чем количество листов в ActiveWorkbook , приведенный выше код вызовет ошибку времени выполнения. Простое предложение охраны может избежать этого:

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

Большинство ошибок времени выполнения можно избежать, тщательно проверив значения, которые мы используем, прежде чем мы их используем, и разветвляемся на другом пути выполнения, соответственно, используя простой оператор If — в сторожевых предложениях, который не делает предположений и не проверяет параметры процедуры, или даже в тело более крупных процедур.

Оператор Error

Даже с защитными пунктами, один не может реально всегда учитывать все возможные ошибки , которые могут быть подняты в теле процедуры. Оператор On Error GoTo инструктирует VBA перейти к метке линии и ввести «режим обработки ошибок» всякий раз, когда во время выполнения происходит непредвиденная ошибка. После обработки ошибки, код может возобновить обратно в «нормальное» исполнение с помощью Resume ключевое слово.

Линейные метки обозначают подпрограммы : потому что подпрограммы исходят из устаревшего кода BASIC и используют GoSub GoTo и GoSub и Return чтобы вернуться к «основной» процедуре, довольно легко написать жесткий код спагетти, если все не строго структурировано , По этой причине лучше всего:

  • процедура имеет одну и только одну подпрограмму обработки ошибок
  • подпрограмма обработки ошибок работает только в состоянии ошибки

Это означает, что процедура, которая обрабатывает его ошибки, должна быть структурирована следующим образом:

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

Стратегии обработки ошибок

Иногда вы хотите обрабатывать разные ошибки с помощью разных действий. В этом случае вы будете проверять глобальный объект Err , который будет содержать информацию об ошибке, которая была поднята, и действовать соответственно:

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

В качестве общего руководства рассмотрите возможность включения обработки ошибок для всей подпрограммы или функции и обработайте все ошибки, которые могут возникнуть в пределах ее области действия. Если вам нужно обрабатывать ошибки только в секции небольшого сечения кода — включить и выключить обработку ошибок на одном уровне:

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

Номера строк

VBA поддерживает номера строк в стиле legacy (например, QBASIC). Скрытое свойство Erl можно использовать для идентификации номера строки, которая вызвала последнюю ошибку. Если вы не используете номера строк, Erl только вернет 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

Если вы используете номера строк, но не последовательно, а затем Erl возвращает номер последней строки перед командой, вызвавшей ошибку.

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

Имейте в виду, что Erl также имеет только Integer точность и будет бесшумно переполняться. Это означает, что номера строк за пределами целочисленного диапазона дадут неверные результаты:

Sub DoSomething()
99997 On Error GoTo 99999
99998 Debug.Print 42 / 0
99999
      Debug.Print Erl   'Prints 34462
End Sub

Номер строки не так актуален, как утверждение, вызвавшее ошибку, и строки нумерации быстро становятся утомительными и не совсем удобны в обслуживании.

Резюме ключевого слова

Подпрограмма обработки ошибок будет либо:

  • выполняются до конца процедуры, и в этом случае выполнение возобновляется в процедуре вызова.
  • или используйте ключевое слово Resume для возобновления выполнения внутри той же процедуры.

Ключевое слово Resume должно использоваться только в подпрограмме обработки ошибок, потому что если VBA встречает Resume не находясь в состоянии ошибки, возникает ошибка времени выполнения 20 «Возобновить без ошибок».

Существует несколько способов, по которым подпрограмма обработки ошибок может использовать ключевое слово Resume :

  • Resume используется отдельно, выполнение продолжается в инструкции, вызвавшей ошибку . Если ошибка на самом деле не обрабатывается , прежде чем делать это, то та же ошибка будет поднят снова, и выполнение может войти в бесконечный цикл.
  • Resume Next продолжает выполнение инструкции сразу после инструкции, вызвавшей ошибку. Если ошибка на самом деле не обрабатывается , прежде чем делать это, то выполнение разрешается продолжать с потенциально недействительными данными, которые могут привести к логическим ошибкам и неожиданному поведению.
  • Resume [line label] продолжает выполнение на указанной метке строки (или номер строки, если вы используете номера строк в стиле устаревшего стиля). Обычно это позволяет выполнить некоторый код очистки до того, как будет чисто выйти из процедуры, например, чтобы закрыть соединение с базой данных, прежде чем вернуться к вызывающему.

Вкл.

Сам оператор On Error может использовать ключевое слово Resume чтобы проинструктировать среду выполнения VBA для эффективного игнорирования всех ошибок .

Если ошибка не выполняется до этого, то выполнение разрешено продолжать с потенциально недействительными данными, что может привести к логическим ошибкам и неожиданному поведению .

Вышеупомянутый акцент не может быть особо подчеркнут. On Error Resume Next эффективно игнорирует все ошибки и выталкивает их под ковер . Программа, которая взрывается с ошибкой во время выполнения с учетом недопустимого ввода, — это более эффективная программа, чем программа, которая работает с неизвестными / непреднамеренными данными — будь то только потому, что ошибка намного легче идентифицируется. On Error Resume Next можно легко скрыть ошибки .

Оператор On Error является областью действия процедур — поэтому в данной процедуре обычно должен быть только один , такой оператор On Error .

Однако иногда не удается избежать ошибки, и переключение на подпрограмму обработки ошибок только на Resume Next просто не кажется правильным. В этом конкретном случае утверждение с известным до невозможности может быть обернуто между двумя On Error :

On Error Resume Next
[possibly-failing statement]
Err.Clear 'resets current error
On Error GoTo 0

Команда On Error GoTo 0 сбрасывает обработку ошибок в текущей процедуре, так что любая дополнительная инструкция, вызывающая ошибку времени выполнения , будет необработанной внутри этой процедуры и вместо этого будет переходить в стек вызовов до тех пор, пока она не будет захвачена активным обработчиком ошибок. Если в стеке вызовов нет активного обработчика ошибок, он будет рассматриваться как необработанное исключение.

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

Пользовательские ошибки

Часто при написании специализированного класса вы хотите, чтобы он поднимал свои собственные конкретные ошибки, и вам понадобится чистый способ для кода пользователя / вызова для обработки этих пользовательских ошибок. Оптимальным способом достижения этого является определение специального типа Enum :

Option Explicit
Public Enum FoobarError
    Err_FooWasNotBarred = vbObjectError + 1024
    Err_BarNotInitialized
    Err_SomethingElseHappened
End Enum

Используя встроенную константу vbObjectError пользовательские коды ошибок не перекрываются с зарезервированными / существующими кодами ошибок. Необходимо явно указать только первое значение перечисления, поскольку базовое значение каждого члена Enum 1 больше, чем предыдущий элемент, поэтому базовое значение Err_BarNotInitialized неявно является vbObjectError + 1025 .

Повышение собственных ошибок времени выполнения

Ошибка выполнения может быть повышена с Err.Raise оператора Err.Raise , поэтому пользовательская ошибка Err_FooWasNotBarred может быть повышена следующим образом:

Err.Raise Err_FooWasNotBarred

Метод Err.Raise также может принимать пользовательские параметры Description и Source — по этой причине рекомендуется также определять константы для хранения каждого пользовательского описания ошибки:

Private Const Msg_FooWasNotBarred As String = "The foo was not barred."
Private Const Msg_BarNotInitialized As String = "The bar was not initialized."

А затем создайте выделенный частный метод для повышения каждой ошибки:

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

После этого реализация класса может просто вызвать эти специализированные процедуры для повышения ошибки:

Public Sub DoSomething()
    'raises the custom 'BarNotInitialized' error with "DoSomething" as the source:
    If Me.Bar Is Nothing Then OnBarNotInitializedError "DoSomething"
    '...
End Sub

Клиентский код может обрабатывать Err_BarNotInitialized как и любую другую ошибку, внутри своей собственной подпрограммы обработки ошибок.


Примечание: наследие Error ключевое слово также может быть использован вместо Err.Raise , но это устаревшее / осуждается.

Хитрости »

21 Март 2015              96262 просмотров


Очень часто начинающие работать в VBA сталкиваются с различными ошибками, которые выдает код в момент выполнения. Если не знать как поступить в данном случае – то очень сложно будет исправить код быстро, а то и вообще невозможно будет определить причину ошибки без помощи более «продвинутых» пользователей. Новички зачастую делают правки наугад, что может порождать иные ошибки, а это в свою очередь не только затрудняет поиск первоначальной ошибки, но и может привести к невозможности исправить код вообще. Поэтому в этой статье я решил описать как производить отладку кода и определять ошибки.
Чтобы описанное в статье можно было сразу опробовать в практике советую скачать файл пример:

  Пример таблицы и кода (35,5 KiB, 2 877 скачиваний)


Что будет рассмотрено:

  • Способы отладки кода в момент появления ошибки
  • Использование окон Locals и Watches для отладки
  • Пошаговая отладка кода — что это такое, как и когда применять
  • Ошибок нет, но код все равно не выполняется

Помимо этого в конце статьи можно скачать файл с кодами ошибок VBA и их расшифровками.

Исходные данные
Допустим, имеется простая таблица
Таблица данных
И код, который должен пройтись по каждой строке таблицы, перемножить цену (столбец Цена) на количество (столбец Продажи шт), просуммировать перемноженные данные и вывести результирующую сумму в ячейку В17:

Option Explicit
Sub PrimitiveCode()
    Dim lr As Long, dblSumm As Double, dblIncr As Double
    'цикл от первой строки таблицы до последней
    For lr = 1 To 14
        'перемножение Цены на Количество (C*E)
        dblIncr = Cells(l, 3).Value * Cells(lr, 5).Value
        'прибавление результата к переменной общей суммы
        dblSumm = dblSumm + dblIncr
    Next
    'выводим результат в ячейку B17
    Cells(17, 2).Value = dblSumm
End Sub
Отладка кода в момент появления ошибки

Если посмотреть на код выше, то опытный программист VBA сразу поймет, что в таком виде код работать не будет – не выполнится и одна строка. Сразу появится ошибка:
Variable not defined

Ошибка означает, что внутри кода есть переменная, которая ранее не была объявлена.
Сама переменная, которую VBA считает не объявленной будет выделена:
Не объявленная переменная
Подробнее об этой ошибке и её причинах можно почитать в статье: Variable not defined или что такое Option Explicit и зачем оно нужно?
Если строки Option Explicit нет, то ошибка появится в момент выполнения строки с этой переменной, т.к. значение этой переменной будет 0, что нельзя использовать в качестве номер строки на листе. Как определить причину ошибки в конкретной строке описано ниже.

Если кратко, то переменной l нет среди объявленных переменных(Dim l As) и мы не можем её использовать, если объявлена директива Option Explicit(настоятельно рекомендую Option Explicit использовать всегда во избежание опечаток). В данном случае это опечатка и там должна быть lr, а не l. Исправляем переменную и код будет выглядеть так:

Sub PrimitiveCode()
    Dim lr As Long, dblSumm As Double, dblIncr As Double
    'цикл от первой строки таблицы до последней
    For lr = 1 To 14
        'перемножение Цены на Количество (C*E)
        dblIncr = Cells(lr, 3).Value * Cells(lr, 5).Value
        'прибавление результата к переменной общей суммы
        dblSumm = dblSumm + dblIncr
    Next
    'выводим результат в ячейку B17
    Cells(17, 2).Value = dblSumm
End Sub

С виду код теперь выполнен правильно и ошибок вызывать не должен. Однако, если его попытаться выполнить опять получим ошибку – на этот раз ошибку типов данных — Type Mismatch:
Type Mismatch
В момент появления главное нажать Debug, а не End (если будет желание прочитать про тип ошибки подробнее – можно еще нажать Help, текст будет на английском). VBA подсветит желтым строку, вычисления или операции в которой вызывают ошибку:
Строка ошибки
Теперь самый важный этап – необходимо определить причину ошибки. С виду все хорошо – одна ячейка перемножается на другую. Без опыта сложно сходу понять, что это ошибка типов данных, хоть VBA прямо об этом говорит(Type Mismatch – в переводе «Несовпадение типов»). Поэтому самое надежное в этом случае – это определить значение каждой составляющей той строки, в которой возникла ошибка. В случае с кодом выше можно воспользоваться двумя методами:

  1. Навести курсор мыши на любую переменную(dblSum, lr) и посмотреть всплывающую подсказку, которая показывает имя переменной и её текущее значение:
    Значение переменной
    Значение нашей переменной lr = 1. Запоминаем и переходим на лист с таблицей и смотрим, какое значение в ячейке первой строки третьего столбца(Cells(1,3)). Там значение Закуп цена, что явно не является числом. Следовательно перемножить его нельзя, т.к. это текст. Отсюда и ошибка типов – с текстом нельзя производить математические операции. Для вычислений предполагается в данном случае числовой тип данных(Integer,Long,Double).
  2. Узнать сразу значение ячейки Cells(lr, 3).Value и ячейки Cells(lr, 5).Value. Наведение курсора мыши в данном случае не даст результата. Как правило наведение курсора мыши не имеет эффекта если это не объявленные как переменные объекты (как в этом случае — Cells). Такие объекты не всегда могут быть вычислены в памяти в момент отладки. Поэтому чтобы просмотреть значение ячейки сначала необходимо отобразить окно Immediate(отобразить можно сочетанием клавиш Ctrl+G или через меню ViewImmediate Window). Выделить и скопировать полностью нужную переменную Cells(i, 3).Value и в окне Immediate написать:
    ?
    и после вопр.знака вставить скопированное. Должно получиться:
    ?Cells(i, 3).Value
    И нажать Enter. Строкой ниже в этом окне будет выведено значение для объекта или переменной (если оно может быть получено):
    Значение в окне Immediate
    По сути результат будет как и в первом примере – мы увидим, что в ячейке текст. Чем второй метод лучше первого? Тем, что таким образом можно сразу получить значение, не переходя на лист и не выискивая нужный номер строки. Ведь это в примере он равен 1, в реальности же строка может быть и 24451.

Окна Locals и Watches

Так же для отслеживания значений переменных очень удобно использовать окно

Locals

и окно

Watches

.

Окно Locals

Окно Locals отображает все локальные переменные, задействованные в выполняемой в настоящий момент процедуре:
Locals Window
Как видно в этом окне отображается имя переменной, её тип и значение. Все хорошо, но в этом окне отображаются исключительно локальные переменные, объявленные на уровне модуля. Переменных других модулей, объявленные как Public и используемые в текущей процедуре там не отображаются. Подробнее про видимость переменных можно узнать в статье: Что такое переменная и как правильно её объявить?

Окно Watches
Окно Watches представляет большую ценность – в это окно можно просто «перетащить» нужную переменную или объект и в этом окне будут отражены все данные об имени переменной, её типе и текущем значении:
Watches Window
Теперь рассмотрим чуть подробнее как перетаскивать в это окно данные. На примере кода выше:

  • Выделяем Cells(i, 3).Value
  • Не снимая выделения наводим курсор мыши на это выделение
  • Зажимаем левую кнопку мыши и не отпуская её переносим курсор в любое место окна Watches

Теперь данные по переменной загружены и доступны для просмотра. По сути все умеют это делать — процесс очень схож с обычным перемещением файлов и папок по рабочему столу. Выделили и с зажатой левой кнопкой мыши перенесли в нужное место.
В чем еще один плюс этого окна – в этом окне можно оставлять эти значения и просматривать в моменты пошаговой отладки только занесенные в это окно переменные(про пошаговую отладку будет рассказано ниже). Если вдруг какая-то переменная/объект стали не нужны для постоянного отслеживания их данных – можно удалить их из окна Watches, чтобы не мешалась. Для этого выделяем переменную-правая кнопка мыши – Delete Watch. Так же можно поиграть с иными пунктами, наибольший интерес из которых на мой взгляд, представляет пункт Edit Watch. После его нажатия появится окно
Edit Watch
Самые основные пункты в этом окне, важные для отладки:

  • Break Then value Changes. Если его установить, VBA будет отслеживать значение этой переменной и останавливать код при любом изменении значения переменной. Это может пригодится для отслеживания значений в циклах
  • Break Then value Is True – пункт пригодится для переменных с типом Boolean или для логических выражений. Как только переменная или результат выражения примет значение True – код будет остановлен на этой строке.
    Например, необходимо остановить код, если номер строки будет равен 10(т.е. переменная lr примет значение 10). Тогда выражение будет иметь вид:

    If lr = 10 Then
    'код
    End if

    Тогда надо будет выделить в строке If lr = 10 Then само условное выражение lr = 10, перенести её в окно Watches, выделить строку в окне Watches с этим выражением, нажать правую кнопку мыши и выбрать Edit Watch. Выбрать в окне Break Then value Is True. Теперь как только переменная lr достигнет значения 10(т.е. обрабатываться будет 10-я строка таблицы) – код остановится и строка с выражением будет выделена желтым. Можно будет проанализировать другие переменные или продолжить выполнение кода в пошаговом режиме(см.далее).


Пошаговая отладка кода

После знакомства с отладкой кода при возникновении ошибки работать с пошаговой отладкой будет проще.

Что такое вообще пошаговая отладка?

Это просмотр этапов выполнения кода строка за строкой.

Для чего это может быть нужно?

  • Чтобы проанализировать чужой код и понять более точно, что он делает изнутри, а не только увидеть результат его выполнения
  • Если вы начинающий программист и часто используете макрорекордер(записываете макросы) — то пошаговая отладка поможет понять какое действия выполняет каждая строка. Это поможет быстрее научиться понимать код и убирать из него лишнее, а так же совмещать различные коды
  • Если внутри кода есть ошибка логики выполнения. Это, пожалуй, самая сложная ошибка, т.к. в этом случае VBA не останавливает работу и не говорит об ошибке. Код выполняется без ошибок, но результат не такой, как ожидалось. Это означает, что либо какой-то переменной назначается не то значение, либо какое-то условие неверно или выполняется не в тот момент, в который должно. В общем по сути это ошибка разработчика, не приводящая к ошибкам синтаксиса или типов, которые VBA может отследить.

Как делать пошаговую отладку? Все просто: устанавливаете курсор в любом месте внутри кода и нажимаете клавишу F8 (либо выбрать в меню DegubStep Into). Теперь при каждом нажатии клавиши F8 код будет выполнять одну строку кода за другой в той очередности, в которой они расположены в процедуре. Если внутри процедуры будет вызов второй процедуры или функции – код пошагово выполнит и её и затем вернется в основную процедуру.
Так же хочу привести еще пару сочетаний клавиш, которые удобно применять при пошаговой отладке:

  • Shift+F8(DegubStep Over) — выполнение вложенной функции/процедуры без захода в неё. Если внутри основной процедуры или функции выполняется другая процедура или функция и Вы уверены, что она работает правильно — просматривать пошагово весь код вложенной процедуры/функции не имеет смысла. Чтобы вложенная процедура/функция выполнилась без пошагового просмотра надо просто нажать указанное сочетание клавиш тогда, когда строка вызова вложенной процедуры/функции будет подсвечена желтым
  • Ctrl+Shift+F8(DegubStep Out) — завершение вложенной функции/процедуры и выход в основную с остановкой. Если все же перестарались и перешли в пошаговый проход вложенной функции(или сделали это специально, но посмотрели все, что надо) — то нажимаете это сочетание и код быстро выполнить вложенную функцию, перейдет в основную и остановится для дальнейшей пошаговой отладки
  • Ctrl+F8(DegubRun to Cursor) — выполнение процедуры до строки, в которой на данный момент установлен курсор

Точки останова
Но куда чаще бывает нужно не просто весь код пройти пошагово, а начать пошаговое выполнение только начиная с какой-либо одной строки, чтобы не мотать строк 40 кода(да еще с циклами) ради достижения одной какой-то строки. Еще точки останова очень полезны при отладке событийных процедур(вроде Worksheet_Change, Worksheet_BeforeDoubleClick, событий элементов форм и т.п.), т.к. они в большинстве своем содержат аргументы и выполнить по F8 их просто невозможно и выполняются они только при наступлении самого события, которые они призваны обработать. Тоже самое справедливо для функций пользователя(UDF) именно для проверки их работы из листа, т.к. эти функции нельзя начать выполнять по F5 — они начинают выполняться только после их пересчета и зачастую ошибки можно выявить исключительно при вызове именно с листа.
Чтобы дать понять VBA на какой строке необходимо будет остановится необходимо установить курсор мыши в любое место нужной строки и нажать F9 или DebugToggle Breakpoint. Строка будет выделена темно-красным цветом.
Это еще называется установкой точки останова. Убрать точку останова можно так же, как она была установлена – F9 или DebugToggle Breakpoint. Так же точку основа можно установить с помощью мыши: для этого необходимо в области левее окна с кодом напротив нужной строки один раз щелкнуть левой кнопкой мыши:
Точка останова

Теперь можно запустить код любым удобным способом (в отладке это как правило делается клавишей F5 или с панели: RunRun Sub/UserForm). Как только код дойдет до указанной точки останова он остановится и строка будет подсвечена желтым. Дальше можно либо продолжить выполнение в пошаговом режиме (нажимая F8), либо (проверив значения нужных переменных и объектов) нажать опять F5 и код продолжит выполняться автоматически, пока не выполнится или не достигнет другой точки останова. Самих же точек останова может быть сколько угодно и расположены они могут быть в любой процедуре или функции.
Следует помнить, что после закрытия файла с кодом точки останова не сохраняются и при следующем открытии книги их необходимо будет установить заново, если это необходимо.


Ошибок нет, но код все равно не выполняется

Еще хочу добавить, что ошибки могут появляться не всегда, даже если они есть. Бывает и так, что код выполняется без ошибок, но однако либо выполняется не так, либо вообще ничего не делает. Как правило причин две:

  1. Логика кода построена неверно и ошибок VBA действительно не возникает. Но т.к. логика неверна — код выполняет не то, что от него ожидается. Решение одно — пошагово выполнить весь код и детально просмотреть всё, чтобы обнаружить в какой строке или строках нарушена логика
  2. Один из очень распространенных вариантов: в начале кода стоит обработчик ошибок On Error Resume Next и дальше обработчик не отменяется. Данный обработчик указывает VBA, что при возникновении ошибки её следует игнорировать и переходит к выполнению следующего оператора/строки. Таким образом ошибка хоть и возникает, но она пропускается и не показывается, а выполнение кода продолжается как ни в чем не бывало. Однако как правило одна ошибка влечет другую и третью и т.д. и может получиться так, что все строки после первой ошибочной станут так же ошибочными и как следствие не выполнятся. Данный оператор будет применяться либо до конца процедуры, либо до тех пор, пока в коде не будет поставлен иной обработчик ошибок:
    On Error GoTo Метка — переход выполнения кода к указанной метке(Метка).
    On Error GoTo 0 — по сути отменяет условный переход при возникновении ошибок. Метка 0 считается обнулением переходов

Один из примеров того, для чего может применяться обработчик ошибок можно найти в статье: Как из Excel обратиться к другому приложению. Там данный обработчик необходим, чтобы проверить открыто ли уже приложение Word. Если открыто — то ошибка не возникнет. Если же закрыто — то будет ошибка. И этот момент как правило и отслеживается. Я расставил подробные комментарии в коде, чтобы было более понятно что к чему:

Sub Check_OpenWord()
    Dim objWrdApp As Object
    On Error Resume Next 'необходимо, чтобы на первой же строке код не выдал ошибку при закрытом Word
    'пытаемся подключится к объекту Word
    Set objWrdApp = GetObject(, "Word.Application")
    'если Word закрыт - обязательно возникнет ошибка 429, 
    'указывающая на то, что невозможно подключиться к объекту Word
    'при этом переменная objWrdApp будет равняться Nothing, т.к. значение не удалось присвоить
    If objWrdApp Is Nothing Then
    'так же можно использовать и такую строку:
    'If Err.Number = 429 Then 'если ошибка 429 - значит Word не запущен - надо создавать новый
        'создаем новый экземпляр
        Set objWrdApp = CreateObject("Word.Application")
        'делаем приложение видимым. По умолчанию открывается в скрытом режиме
        objWrdApp.Visible = True
    Else
        'приложение открыто - выдаем сообщение
        MsgBox "Приложение Word уже открыто", vbInformation, "Check_OpenWord"
    End If
End Sub

Для чего вообще это нужно? Ведь можно создавать новый экземпляр. А дело в том, что не всегда правильно создавать новый — куда правильнее зачастую подключиться к ранее открытому, чем плодить новые экземпляры и захламлять диспетчер задач.
Тему обработки ошибок я здесь пока не раскрываю полностью, т.к. это не на один абзац. В одной из следующий статей постараюсь более подробно рассказать как и где их лучше применять и как правильно, а когда лучше вообще воздержаться от их использования.


Конечно, статья не описывает способы устранения ошибок — это просто невозможно. Только известных типов ошибок в VBA более 3 тысяч. Все их упоминать бессмысленно. Целью статьи было помочь начинающим найти строку с ошибкой и рассказать как производить отладку кода при необходимости. А все остальное придет с опытом. Однако на всякий случай я решил выложить файл Excel с описанием большей части ошибок, которые могут возникнуть. В файле указан номер ошибки, описание на английском и описание на русском. Причин ошибки может быть множество, поэтому нет однозначных рекомендаций по устранению каждой из них. Все зависит от данных и от самого кода.

  Ошибки VBA с описанием (152,0 KiB, 5 640 скачиваний)

Удачи в программировании!


Статья помогла? Поделись ссылкой с друзьями!

  Плейлист   Видеоуроки


Поиск по меткам



Access
apple watch
Multex
Power Query и Power BI
VBA управление кодами
Бесплатные надстройки
Дата и время
Записки
ИП
Надстройки
Печать
Политика Конфиденциальности
Почта
Программы
Работа с приложениями
Разработка приложений
Росстат
Тренинги и вебинары
Финансовые
Форматирование
Функции Excel
акции MulTEx
ссылки
статистика

I am trying to debug a long code I wrote and I need to step line by line.

The thing is I am on a mac and don’t know how to use an F8 in that case. Could anyone tell me how can I do that otherwise and how do I know which line is causing problems with execution?

Martijn Pieters's user avatar

asked May 6, 2013 at 11:56

seigna's user avatar

1

To check which line is giving you the error, you can use the ERL property. See this sample code below.

Sub sample()
Dim i As Long

On Error GoTo Whoa

10    Debug.Print "A"
20    Debug.Print "B"
30    i = "Sid"
40    Debug.Print "A"

50    Exit Sub
Whoa:
    MsgBox "Error on Line : " & Erl
End Sub

For this to work, you will have to number the code lines as I have done above. Run the above code and see what happens.

answered May 6, 2013 at 13:24

Siddharth Rout's user avatar

Siddharth RoutSiddharth Rout

147k17 gold badges206 silver badges250 bronze badges

Sub Main()

    Dim lNum As Long

    On Error GoTo ErrHandler

    lNum = 1 / 0

ErrExit:
    Exit Sub

ErrHandler:
    Debug.Print Err.Description
    Stop
    Resume

End Sub

When you get to Stop, then Step Into twice. If you don’t have F8, you should have a menu item for stepping into a line. Resume will take you back to the line that caused the error.

answered May 6, 2013 at 21:32

Dick Kusleika's user avatar

Dick KusleikaDick Kusleika

32.5k4 gold badges51 silver badges73 bronze badges

1

  1. Right click the toolbar.
  2. Choose «Customize…»
  3. Select «Debug»
  4. Drag «Step Into» into your toolbar.

Cees Timmerman's user avatar

answered May 6, 2013 at 12:17

saru_'s user avatar

saru_saru_

1211 silver badge1 bronze badge

 

Евгений

Пользователь

Сообщений: 65
Регистрация: 01.01.1970

Есть большой лист, в котором с помощью функции ВПР() собирается информацию из других листов. Очень часто появляются ссылки на уже несуществующие значения, и как итог — появляется ошибка #Н/Д, которую хотелось бы обрабатывать. Для этого в самом верху пустая ячейка со «статусом», в которой хочется видеть номер строки с ошибкой (если есть), и в идеале — красить её для привлечения внимания в красный.

Проблема в том, что нужно получить номер строки с ошибкой #Н/Д из диапазона ячеек, а функции вроде ЕНД(), ЕОШИБКА() с диапазонами не работают.

Но все же можно как-нибудь?

 

МВТ

Пользователь

Сообщений: 1198
Регистрация: 01.02.2015

Если Вы хотите чтобы вместо ошибки выводилась пустая строка или слово «Ошибка», используйте функцию =ЕСЛИОШИБКА(). При необходимости по ее результатам можно фильтровать список  

 

Sanja

Пользователь

Сообщений: 14849
Регистрация: 10.01.2013

А если такая ошибка не одна на листе? Номер какой строки будет в ячейке со «статусом»? А красить, для привлечения внимания можно сами ячейки с ошибкой через Условное форматирование

Согласие есть продукт при полном непротивлении сторон.

 

Евгений

Пользователь

Сообщений: 65
Регистрация: 01.01.1970

Я хочу взглянув на ячейку в самом верху понять — есть ли в данный момент ошибки #Н/Д, и если есть — на какой строке

 

Sanja

Пользователь

Сообщений: 14849
Регистрация: 10.01.2013

Обрабатывайте ошибки сразу в Ваших формулах, например, как предложил MBT

Согласие есть продукт при полном непротивлении сторон.

 

Евгений

Пользователь

Сообщений: 65
Регистрация: 01.01.1970

Их очень и очень много, плюс они действительно длинные что бы туда еще включить и обработку ошибок :cry:
Неужели совсем без этого не обойтись никак?

 

МВТ

Пользователь

Сообщений: 1198
Регистрация: 01.02.2015

#7

01.04.2015 23:45:30

Тогда так

Код
Function EE(Rng As Range) As Long
For Each Cell In Rng
If Cell.Text = "#Н/Д" Then
    EE = Cell.Row
    Exit Function
End If
Next Cell
End Function

 

Sanja

Пользователь

Сообщений: 14849
Регистрация: 10.01.2013

Вот и про «очень и очень много». Номер строки какой ячейки с ошибкой из этих «очень и очень», по Вашему мнению, должен быть в ячейке со статусом?

Согласие есть продукт при полном непротивлении сторон.

 

Евгений

Пользователь

Сообщений: 65
Регистрация: 01.01.1970

Sanja, первая нашедшаяся строка с ошибкой, а после того как будет исправлена — то номер следующей

Спасибо MBT, попробую!

Изменено: Евгений02.04.2015 00:09:48

 

МВТ

Пользователь

Сообщений: 1198
Регистрация: 01.02.2015

#10

01.04.2015 23:50:54

Немного модернизировал: теперь можно задавать номер вхождения ошибочной строки (необязательный аргумент)

Код
Function EE(ByVal Rng As Range, Optional Count As Long = 1) As Long
For Each Cell In Rng
If Cell.Text = "#Н/Д" Then
    If Count = 1 Then
        EE = Cell.Row
        Exit Function
     Else:
     Count = Count - 1
     End If
End If
Next Cell
End Function

Кстати, если заменить  If Cell.Text = «#Н/Д» на If IsError(Cell) функция будет работать с любыми ошибками

Изменено: МВТ02.04.2015 00:08:51
(Изменил проверку)

 

Kuzmich

Пользователь

Сообщений: 7999
Регистрация: 21.12.2012

#11

02.04.2015 00:01:48

Цитата
собирается информацию из других листов

Так может собирать информацию при помощи макроса и в нем анализировать данные,
не нашел — красить ячейку или писать мессидж «Нет такого значения»

 

Евгений

Пользователь

Сообщений: 65
Регистрация: 01.01.1970

MBT, оба макроса при запуске выдают «Argument not optional»

 

Sanja

Пользователь

Сообщений: 14849
Регистрация: 10.01.2013

Это не обычные макросы, это UDF

Согласие есть продукт при полном непротивлении сторон.

 

МВТ

Пользователь

Сообщений: 1198
Регистрация: 01.02.2015

#14

02.04.2015 00:18:51

Евгений, странно, у меня работают. Тем более, в первой UDF нет аргументов по умолчанию вообще :). Прикладываю файл с вставленной функцией. Кстати, если ошибок меньше, чем задан номер вхождения, то функция вернет 0.
Но, если Вы настаиваете на макросе, пожалуйста, он выведет в колонки J и K номера ВСЕХ строк с ошибками в выделенном диапазоне и соответствующие им ошибки. При этом, если выделено более одной колонки, выделение уменьшается до крайне левой.

Код
Sub EEE()
Dim L As Long
L = 1
With Selection
    If .Rows.Count > 1 Then .Resize(.Rows.Count, 1).Select
End With
For Each Cell In Selection
    If IsError(Cell) Then
      Cells(L, 10) = Cell.Row
      Cells(L, 11) = Cell.Text
      L = L + 1
    End If
Next Cell
End Sub

Прикрепленные файлы

  • Поиск ошибки.xlsm (15.94 КБ)

Изменено: МВТ02.04.2015 00:38:03
(Файл перезалил)

 

Евгений

Пользователь

Сообщений: 65
Регистрация: 01.01.1970

МВТ, Теперь возвращает и всё работает — спасибо за помощь!

 

Максим Зеленский

Пользователь

Сообщений: 4655
Регистрация: 11.06.2014

Microsoft MVP 2018-2022

#16

02.04.2015 11:27:34

странно. У меня сработала обычная формула (массивная):

Код
=МИН(ЕСЛИ(ЕНД($A$1:$A$20);СТРОКА($A$1:$A$20)))

F1 творит чудеса

 

МВТ

Пользователь

Сообщений: 1198
Регистрация: 01.02.2015

#17

03.04.2015 11:44:49

По просьбе ТС немного видоизменил код: он будет создавать список гиперссылок на ячейки, содержащие ошибки. Если выделен в качестве диапазона проверки весь столбец, из него будут браться ячейки до последней заполненной.

Код
Sub EEE()
Dim L, L1, Cl As Long, HText As String
L = 1
Cl = 10
HText = """#" & ActiveSheet.Name & "!"
With Selection
    If .Columns.Count > 1 Then .Resize(.Rows.Count, 1).Select
    L1 = Cells(Rows.Count, .Column).End(xlUp).Row
    If .Rows.Count > L1 Then .Resize(L1, 1).Select
End With
For Each Cell In Selection
    If IsError(Cell) Then
      Cells(L, Cl).FormulaLocal = "=ГИПЕРССЫЛКА(" & HText & Cell.Address & """" & ";" & """" & "Строка " & Cell.Row & " Ошибка " & Cell.Text & """" & ")"
      L = L + 1
    End If
Next Cell
Cells(1, Cl).Select
End Sub


 

Евгений

Пользователь

Сообщений: 65
Регистрация: 01.01.1970

МВТ, Вы не могли бы немного модифицировать макрос из

поста #10

, просто добавив гиперссылку туда? Спасибо!

 

МВТ

Пользователь

Сообщений: 1198
Регистрация: 01.02.2015

#19

03.04.2015 13:37:20

Чтобы функция возвращала гиперссылку у меня, к сожалению не получается (возможно как-то можно, но я не нашел — как). Но переделал формулу, чтобы она выводила результат, который можно вставлять в функцию рабочего листа =ГИПЕРССЫЛКА(EE(A:A))

Код
Function EE(ByVal Rng As Range, Optional Count As Long = 1) As String
Dim HText As String
HText = "#" & ActiveSheet.Name & "!"
With Rng
    If .Columns.Count > 1 Then .Resize(.Rows.Count, 1).Select
    L1 = Cells(Rows.Count, .Column).End(xlUp).Row
    If .Rows.Count > L1 Then .Resize(L1, 1).Select
End With
For Each Cell In Rng
If IsError(Cell) Then
    If Count = 1 Then
        EE = HText & Cell.Address & ""
        Exit Function
     Else:
     Count = Count - 1
     End If
End If
Next Cell
End Function

 

Евгений

Пользователь

Сообщений: 65
Регистрация: 01.01.1970

МВТ, Тоже вполне себе выход :) Но вы проверяли, это работает? Потому что у меня ругается на «Введенное значение неверно. Набор значений, которые могут быть введены в ячейку, ограничен». В функцию рабочего листа вставляю абсолютно так же

 

МВТ

Пользователь

Сообщений: 1198
Регистрация: 01.02.2015

Евгений,покажите, как Вы это делаете?

 

Евгений

Пользователь

Сообщений: 65
Регистрация: 01.01.1970

МВТ, Примерно так же как в примере у Вас, но с уточнением диапазона: =ГИПЕРССЫЛКА(EE(F$6:F$3100))

Кстати, странная вещь. Проверил сейчас, макрос всегда не работает в том случае, если диапазон ячеек отличен от А:А

 

Jake

Пользователь

Сообщений: 65
Регистрация: 01.01.1970

#23

06.04.2015 23:39:47

На будущее, может кому понадобится — как я все же добился гиперссылки на строку с ошибкой. Использовался макрос МВТ из коммента

№10

, за что ему большое спасибо.

Ячейка с вызовом макроса выглядит так

Код
=ЕСЛИ(EE(E5:E3100)=0;" ";ГИПЕРССЫЛКА("[Книга Учета.xlsm]Лист!" & "A" & EE(E5:E3100);EE(E5:E3100)))

Сначала идет поиск ошибок в указанном диапазоне ячеек, если макрос возвращает (0), то их нет и оставляем ячейку пустой. Если есть, показываем номер строки и делаем её гиперсылкой.

Всем спасибо!

 

МВТ

Пользователь

Сообщений: 1198
Регистрация: 01.02.2015

Jake, всегда пожалуйста :)
Евгений, я проверял на простеньком примере, вроде работало (я так понимаю, Вы говорите о функции в посте № 19?). Если приложите свой файл со вставленной функцией, возможно смогу сказать, где ошибка

 

МВТ

Пользователь

Сообщений: 1198
Регистрация: 01.02.2015

#25

08.04.2015 20:54:57

На досуге доработал макрос — вдруг кому-от понадобится? Фактически, если есть необходимость искать ошибки вычислений именно таким образом, можно попробовать использовать его как надстройку

Код
'Позволяет выделять диапазон как на активном рабочем листе, так и не дюбом другом
'При наличии на активном листе гиперссылок, выдает запрос на их удаление
'Вставляет ссылку на первую найденную в выделенном диапазоне ошибку в
'ячейку, которая была активной на момент вызова макроса.
'Выводит окно с запросом на поиск следующей ошибки. В случае согласия выводит ссылку на нее
'в той же колонке на строку ниже
'С уважением, МВТ

Dim HText As String, CurCell As Range
Dim Rng
Set CurCell = ActiveCell
With ActiveSheet
If .Hyperlinks.Count <> 0 Then
If MsgBox("На рабочем листе есть гиперссылки (" _
& .Hyperlinks.Count & "). Удалить?", vbYesNo) = vbYes Then
For Each HL In .Hyperlinks
HL.Parent.Clear
Next HL
End If
End If
End With
On Error Resume Next
Set Rng = Application.InputBox("Укажите диапазон для очистки ячеек:", "Запрос данных", , Type:=8)
If Rng Is Nothing Then Exit Sub
With Rng
    If .Columns.Count > 1 Then .Resize(.Rows.Count, 1).Select
    L1 = Cells(Rows.Count, .Column).End(xlUp).Row
    If .Rows.Count > L1 Then .Resize(L1, 1).Select
End With
HText = "#" & Rng.Worksheet.Name & "!"
For Each Cell In Rng
If IsError(Cell) Then
    HText = HText & Cell.Address & ""
    ActiveSheet.Hyperlinks.Add Anchor:=Range(CurCell.Address), _
                               Address:=HText, _
                               TextToDisplay:="Строка: " & Cell.Row
    If MsgBox("Найти следующую ошибку?", vbYesNo) = vbNo Then Exit Sub
    Set CurCell = CurCell.Offset(1, 0)
End If
Next Cell
End Sub

Понравилась статья? Поделить с друзьями:
  • Vba защита от ошибок
  • Vba если ошибка функция
  • Vba если ошибка то дальше
  • Vbs ошибка 800a01a8
  • Vbs ошибка 80070005