86

Приёмы отладки VBA-кода (на примере MS Excel)

Многие из тех, кто часто работает в MS Excel, используют книги или надстройки с VBA-макросами. Зачастую требуется доработать существующее решение, добавив новую функциональность или исправив найденный баг. В поисках места, где скрывается баг или производится нужное вычисление, можно провести немало времени, особенно если проект содержит десятки тысяч строк кода (увы, такие макросы иногда встречаются). Можно прибегнуть к текстовому поиску (например по тексту сообщения, вшитому в код), но иногда удобнее запустить макрос и прибегнуть к пошаговой отладке.

В этом посте описаны несколько приёмов, которые могут быть полезны при отладке макросов на VBA в приложениях MS Office на примере MS Excel.


1. Стандартные точки останова (breakpoint). Step-into, step-over

2. Окна Immediate ("REPL-терминал"), Locals (локальные переменные), Watches (наблюдатели)

3. Условные точки останова на базе watch expression

4. Обработка ошибок

1. Самый очевидный способ отладки - поставить точку останова в начале макроса, а затем пошагово исполнять строку за строкой, наблюдая за значениями переменных и состоянием ячеек в книге. Чтобы установить точку останова, нажмите F9 - и на строке, где находится курсор,

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

Приёмы отладки VBA-кода (на примере MS Excel) Microsoft Excel, Vba, Длиннопост

Теперь как только поток исполнения доберётся до этой строки, он будет приостановлен, а Вы увидите желтый указатель, указывающий на следующую инструкцию:

Приёмы отладки VBA-кода (на примере MS Excel) Microsoft Excel, Vba, Длиннопост

Многие не знают, что этот указатель можно перемещать на любой statement в пределах функции, просто перетащив мышью желтую стрелку!


Когда Вы приостановили исполнение кода, можете продолжить его исполнение пошагово, с помощью команд step-into, step-over и step-out:

* step-into (F8) - шаг вперёд. Будет выполнен код на строке под указателем, а сам указатель перемещён на следующую строку. Однако если в текущей строке есть вызов другой функции, указатель переместится внутрь этой функции

* step-over (Shift-F8) - полностью аналогичен step-into, за исключением того, что эта команда позволяет не "проваливаться" в вызов вложенных функций

* step-out (Ctrl-Shift-F8) - текущая функция будет исполнена до конца, а исполнение приостановлено в вызывающей функции. Полезна, если Вы случайно провалились в длинную функцию командой step-into


Теперь немного о том, как узнать значения переменных и иную полезную информацию.

2. В нижней части экрана VBE (Visual basic editor) Вы можете увидеть три окна:


* Immediate (терминал для выполнения кода и вывода текстовой информации)

* Locals (локальные переменные)

* Wathes (наблюдатели)

Приёмы отладки VBA-кода (на примере MS Excel) Microsoft Excel, Vba, Длиннопост

Если Вы не видите эти окна, включите их отображение в меню View

Приёмы отладки VBA-кода (на примере MS Excel) Microsoft Excel, Vba, Длиннопост

Разберём подробнее, какие возможности открывают нам эти инструменты.


Окно Immediate представляет собой REPL-терминал, в котором можно ввести выражение и запустить его нажатием Enter. Чтобы вывести результат в этот же терминал, передайте результат выражения в функцию Debug.Print (кстати, эту функцию можно вызвать и в основном коде макроса - результат так же будет выведен в консоль Immediate). В самой консоли можно воспользоваться и сокращённой версией этой команды - знаком вопроса:

Приёмы отладки VBA-кода (на примере MS Excel) Microsoft Excel, Vba, Длиннопост

Обратите внимание, что с помощью сочетаний Ctrl+Space и Ctrl+J можно вызывать окно IntelliSense , также как и в основном редакторе кода.


Окно Locals позволяет увидеть значения переменных, доступных в текущем контексте:

Приёмы отладки VBA-кода (на примере MS Excel) Microsoft Excel, Vba, Длиннопост

Например, текущее значение счётчика цикла или состав коллекции. Обратите внимание, что для объектов Collection при раскрытии отображаются значения элементов коллекции, а для словарей (Dictionary) - их ключи. Поэтому чтобы узнать значение самого элемента в словаре, нужно вычислить выражение, например такое:

dict.Items("key1")

Это можно сделать при помощи "наблюдателя" - watch expression

Приёмы отладки VBA-кода (на примере MS Excel) Microsoft Excel, Vba, Длиннопост

Выделите выражение и выполните команду Add watch... в контекстном меню (доступном с помощью правой кнопки мыши), или введите нужно выражение вручную в окне добавления наблюдателя:

Приёмы отладки VBA-кода (на примере MS Excel) Microsoft Excel, Vba, Длиннопост

Осторожно: выражение d.Item("key1") при вычислении может добавить элемент к словарю, если такой ключ в словаре отсутствует. Поэтому не забудьте удалить наблюдатель, когда он станет ненужным, иначе в дальнейшем можно провести немало времени, выясняя откуда взялся лишний элемент в словаре.

3. Условные точки останова, которые можно реализовать с помощью наблюдателей.


Вероятно, Вы обратили внимание, что в меню добавления наблюдателя помимо стандартной опции "Watch Expression" есть ещё две: "Break When Value Is True" и "Break When Value Changes". Они позволяют добиться приостановки исполнения макроса при наступлении соответствующего условия (выражение истинно, либо значение выражения изменилось). Эта возможность может оказаться особенно полезной, например, при отладке циклов. Если Вы знаете, что макрос, который в цикле обрабатывает строки на листе Excel, сталкивается с ошибкой, скажем, на строке 42, то можно избежать ручного "пролистывания" 41 предшествующей итерации, добавив выражения вида i=42 с опцией Break When Value Is True, где i - это переменная счётчика цикла.

4. Напоследок хотел бы немного рассказать об обработке ошибок в VBA.

В настройках VBE Вы можете найти выбор из трёх опций (меню Tools -> Options, вкладка General):

Break on All Errors

Break in Class Module

Break on Unhandled Errors

Приёмы отладки VBA-кода (на примере MS Excel) Microsoft Excel, Vba, Длиннопост

По умолчанию, как правило, установлена последняя опция, которая позволяет программисту обрабатывать ошибки с помощью директив On Error Goto (показать сообщение об ошибке, "прибраться за собой" - закрыть книгу или соединение) и On Error Resume Next (игнорировать ошибку и продолжать исполнение). Однако, когда Вы столкнётесь с ошибкой вида "Извините, что-то пошло не так =(", которая отображается из обработчика в On Error Goto, будет трудно определить место, где произошла ошибка. В этом случае пригодится опция Break on All Errors, которая позволит проигнорировать директивы On Error и перейти к тому месту в коде, где и произошла ошибка. В этот момент может пригодиться и окно Locals, где Вы сможете увидеть, например, текущее значение счётчика цикла, и понять, при обработке какой строки на листе Excel возникла ошибка.


Указанные опции обработки ошибок устанавливаются на уровне настроек пользователя, и не сохраняются в VBA-проекте.

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


Минутка рекламы.

В свободное время я пилю надстройку общего назначения для MS Excel на платформе VSTO. Проект доступен на гитхабе под лицензией MIT (можете свободно использовать в своих проектах, форкать и изменять под свои нужды):

https://github.com/navferty/NavfertyExcelAddIn

Приёмы отладки VBA-кода (на примере MS Excel) Microsoft Excel, Vba, Длиннопост

Призываю неравнодушных пользователей оставить пожелания по добавлению новых функций (заводите issue на гитхабе, или пишите прямо тут в комментариях), а разработчиков, знакомых с технологией VSTO (или желающих познакомиться) - присоединяться к разработке!


Это мой первый пост на Пикабу, буду рад конструктивной критике в комментариях и ЛС.

MS, Libreoffice & Google docs

761 пост14.9K подписчиков

Правила сообщества

1. Не нарушать правила Пикабу

2. Публиковать посты соответствующие тематике сообщества

3. Проявлять уважение к пользователям

4. Не допускается публикация постов с вопросами, ответы на которые легко найти с помощью любого поискового сайта.

По интересующим вопросам можно обратиться к автору поста схожей тематики, либо к пользователям в комментариях


Важно - сообщество призвано помочь, а не постебаться над постами авторов! Помните, не все обладают 100 процентными знаниями и навыками работы с Office. Хотя вы и можете написать, что вы знали об описываемом приёме раньше, пост неинтересный и т.п. и т.д., просьба воздержаться от подобных комментариев, вместо этого предложите способ лучше, либо дополните его своей полезной информацией и вам будут благодарны пользователи.

Утверждения вроде "пост - отстой", это оскорбление автора и будет наказываться баном.

5
Автор поста оценил этот комментарий
Я бы добавил больше примеров про Immediate window, чтобы читатели оценили его по достоинству.
Оно умеет показывать текущее значение переменной (?х) или ячейки (?cells(1,1).value) или номер активной колонки и пр.
Умеет выполнять команды, например (cells(1,1).interior.color=rgb(255,0,0)) или сделать активной ячейку нужную (cells(2,2).select), умеет сравнивать выдавая в итоге тру или фолс - и это самое ценное когда отлаживаешь, смотришь в ячейку, там то же самое что в переменной, вроде должно работать но не работает, а это окно точно скажет в этом месте проблема или нет если спросить его сравнить (cells(1,1)=x)
раскрыть ветку (1)
1
Автор поста оценил этот комментарий

Спасибо за дополнение. Действительно, Immediate window это довольно мощный инструмент, позволяющий выполнить или вычислить любое выражение (ограничение лишь по размеру - выражение должно быть на одной строке).

Позволю себе немного позанудствовать дополнить Ваш комментарий. Cells неявно обращается к активному листу активной книги, поэтому не рекомендуется использовать такое выражение где-то кроме упомянутого окна Immediate. Советую всегда по возможности писать либо ThisWorkbook.Worksheets("sheet1").Cells , либо явно объявить объект книги/листа

Dim ws as Worksheet

Set ws = ActiveWorkbook.Worksheets(0)

и дальше обращаться уже через него: ws.Cells

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

0
Автор поста оценил этот комментарий

ок, суть такова: работаю в офисной базе, делаю отчёт по прибыли по всем клиентам, к примеру, копирую таблицу с результатами в эксель, хочу сделать сортировку по убыванию прибыли, но цифры сортируются неправильно, от девятки к единице, а должно быть от большего числа к меньшему, я подозреваю, что в таблице суммы содержат ненужные пробелы, поэтому и не происходит правильная сортировка, но корректировать 500-800 строк это муторно, пробовал менять формат таблицы на разные значения, не помогает. есть ли какой способ быстро отформатировать таблицу для правильной сортировки? для примера прикладываю как сейчас эксель делает сортировку

Иллюстрация к комментарию
раскрыть ветку (1)
0
Автор поста оценил этот комментарий

Действительно, зеленые флажки в каждой ячейке указывают на то, что данные отформатированы как текст.
Первый вариант, ручной. Выделить нужный диапазон или столбец (чтобы не затронуть остальные данные на листе), Ctrl-H (найти и заменить), и заменить все пробелы на "ничего" (оставить второе окно пустым). В зависимости от региональных настроек на Вашем компьютере, также может понадобиться заменить запятые на точки (смотря какой десятичный разделитель).

Второй вариант - присоединиться к числу бета-тестеров моей надстройки

https://github.com/navferty/NavfertyExcelAddIn
Там среди прочих функций есть и парсинг чисел из текстового формата.

PS А если есть желание, можно присоединиться и в качестве добровольца-разработчика. Проект открытый, под лицензией MIT

Иллюстрация к комментарию
0
Автор поста оценил этот комментарий

Да задача просто сформировать кучу шаблонов один раз в год на клиентской машине. Хрен с ними, закладками. Теперь нужно сделать так, чтобы юзер потом в документе мог выбрать n - ый элемент в m количестве в ячейке таблицы (то есть, есть заранее подготовленный сет элементов и юзер из них выбирает, что хочет, чтобы было в ячейке таблицы). Есть какие-нибудь советы (учитывая, что эту штуку потом печатать надо будет)?

раскрыть ветку (1)
Автор поста оценил этот комментарий

То есть реализовать что-то вроде корзины заказов, как интернет магазине - возможность выбрать некоторые из представленных элементов, каждое в определённом количестве. Я верно понял?

Могу предположить решение банальными чекбоксами для выбора элементов из списка (каждый из них можно привязать к ячейке под ним, для возможности фильтрации стандартным автофильтром), выбор количество - с помощью DataValidation организовать drop-down. Пока что обошлось без единой строки на VBA

Иллюстрация к комментарию
показать ответы
0
Автор поста оценил этот комментарий

с удовольствием читаю посты про EXCEL, люблю в нём работать и что-то новое узнавать. а куда бы написать вопрос с помощью по экселю, если, конечно, не трубно помочь

раскрыть ветку (1)
Автор поста оценил этот комментарий

Пишите тут, если вопрос не слишком большой для комментариев. Может быть, будет полезен и остальным.

показать ответы
2
Автор поста оценил этот комментарий

Первый раз читаю пост про VBA,  который не хочется раскритиковать) Очень грамотно описано и сам материал, и обращение к объектам, и всё остальное - новичкам будет очень полезно. Аплодирую стоя, автор молодец! Зайду теперь обязательно посмотрю на надстройку

раскрыть ветку (1)
Автор поста оценил этот комментарий

Спасибо за теплые слова!) В следующем посте хочу опубликовать туториал по созданию VBA-надстройки с кастомной панелью инструментов (Ribbon custom UI). Помню, в своё время когда делал, пришлось по крупицам собирать в интернете информацию

показать ответы
0
Автор поста оценил этот комментарий

Кто шарит, есть что-нибудь лучше, чем закладки в документе OOXML для работы с ним программно?

раскрыть ветку (1)
Автор поста оценил этот комментарий

Зависит от задачи и условий. Чаще всего я бы вовсе не рекомендовал использовать средства автоматизации (VBA, VSTO) для программного создания/редактирования документов (особенно если выполнение происходит на сервере, а не на клиентской машине). Для работы с документами OpenXML есть разные библиотеки под большинство популярных языков программирования и платформ. Например, под .NET есть Open XML SDK: https://docs.microsoft.com/en-us/office/open-xml/how-to-pars...

Правда некоторые вещи придётся делать руками, например доставать строковые значения ячеек из компонента SharedStrings. Есть и более высокоуровневые библиотеки, например Aspose - довольно мощная штука, но платная.

показать ответы
0
Автор поста оценил этот комментарий
Подскажите пожалуйста как из листа запустить хранимую процедуру в базе и передать ей колонку со значениями?
раскрыть ветку (1)
Автор поста оценил этот комментарий

Честно говоря, у меня мало опыта вызова процедур из VBA, но могу предположить, что как-то примерно так:

Command.Parameters.Append _

    Command.CreateParameter("@parameter1", adArray, adParamInput, , myArr)


https://stackoverflow.com/questions/31986552/how-do-i-run-a-...