Что-то сильно примелькались темы о проблемах, когда человек не может выявить ошибку самостоятельно. При том, что более опытные люди как-то эти ошибки выявляют же.
Не знаю, как это делают другие, внимательно вчитываются в код или ещё как-то. У меня одним из средств решения проблем и даже написания нового кода служат стандартные средства отладки, встроенную в среду VBA.
Разберём их все.
Первое, что мы можем сделать в любой программе из состава MS Office - открыть редактор VBA. Самый быстрый способ, особенно для тех, кто это делает каждый раз, когда открывает базу акцесса, нажать сочетание клавиш Alt+F11. Это срабатывает всегда, скрыто ли у вас меню, риббон, окно базы данных, нет открытых форм.
По всем окошкам редактора я сильно углубляться не буду. Перечислю те, которые нужны именно в написании и отладке кода.
Выпадающие списки объектов.
Это не окно, но один из самых важных элементов среды. Когда мы пишем код, набрали название какого-нибудь объекта, поставили точку и хотим написать название его дочернего объекта или функцию, выпадает список:

А вы знали, что его можно открыть в любой момент и на любом месте клавишами Ctrl+J? Зачем он нужен? В нём перечислено всё, что доступно в работе с объектом, стоящим перед точкой. Среда VBA контролирует синтаксис и всегда выводит только то, что реально можно использовать в данном участке кода. Только в особых избранных случаях можно использовать и то, чего в списке нет. Но эти случаи настолько редкие и экзотические... Таким образом мы всегда можем найти нужную нам функцию или свойство, о которых мы что-то отдалённо помнили или догадывались об их существовании, но вот убей - не вспомнишь, как оно точно звалось. Я постоянно действую именно так - набираю первое слово до точки или даже только первые буквы, а остальное нахожу в списке, ставлю точку, и начинаю листать список. Много случаев было, когда я нашёл интересные особенности объекта именно этим пролистыванием, не читая документации, не копаясь в справке и не обращаясь к гуглам-форумам. Так же полный список самих объектов и их возможностей всегда можно открыть клавишей F2

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

Она кратко описывает синтаксис написания кода для этого объекта. Жирным отмечается параметр, который мы должны описать прямо сейчас, согласно его порядковому положению. В данном случае на картинке это Prompt - текст сообщения. В квадратных скобках перечислены параметры, не обязательные для заполнения. Для некоторых из них даже указаны значения по-умолчанию. Например, стандартная кнопка у любого меседжбокса, даже, если не указать, какие кнопки должны быть - ОК. Такая константа там и указана - vbOKOnly. Что, если мы хотим выбрать другие кнопки? Если мы после текста сообщения поставим запятую, то выпадет список доступных констант для настройки кнопок. Мы их можем перечислить через знак плюса. Например сочетание констант | VB.NET | 1
| MsgBox "текст сообщения", vbAbortRetryIgnore + vbDefaultButton2 + vbExclamation, "заголовок |
|
даст нам такой вид (Title (заголовок сообщения) мы можем пропустить. Тогда будет подставлено название программы):

Имеем: три кнопки - прервать, повтор, пропустить. Кнопка "повтор" выбрана заранее и если пользователь сразу нажмёт клавишу Пробел, то сработает эта кнопка. Ну и константа vbExclamation вывела нам иконку восклицания.
К чему я это всё описываю вроде бы к самому банальному коду из разряда "хэлоу, ворлд"? А к тому, что если мы захотим изменить этот кусок кода, то могли бы заменить одну из констант, нажав те самые Ctrl+J. Но есть подвох - откроется список всех возможных констант и объектов языка, а не только те константы, которые можно употреблять в этом параметре меседжбокса. Как же быть? Просто. Удаляем запятую перед перечислением этих констант. Или ставим ещё один знак плюса после одной из имеющихся констант. И список выпадает опять. И он опять показывает только те константы, которые мы можем подставить, чтоб они работали. Таким образом, мы, очень слабо зная бэйсик и английский со словарём, можем писать целые программы, ни разу не заглянув в справку. Код сам подсказывает нам, что мы должны писать.
Окно немедленного выполнения команд Immediate
Часто нам бывает нужно узнать результат вычисления какого-нибудь выражения или выполнить какую-то команду, не создавая специально для этого модуль. В акцессе, например, это может быть команда
| VB.NET | 1
| DoCmd.SetWarnings False |
|
для скрытия всех вопросов с подтверждением на запросы удаления, обновления и добавления записей. Куда писать эту команду? Открываем редактор VBA и нажимаем клавишу Ctrl+G. Откроется окошко Immediate. Если оно уже было открыто, курсор встанет в него и мы можем сразу набирать, что захотим. Если надо, к примеру, узнать ответ на извечный вопрос, то так и пишем: Нажимаем Enter и получаем ответ. Вопросительный знак перед командой требует вывести ответ в это же окошко. Если мы напишем | VB.NET | 1
| ? MsgBox ("текст сообщения", vbAbortRetryIgnore + vbDefaultButton2 + vbExclamation, "заголовок") |
|
, то увидим описанный нами меседжбокс. Но, когда мы нажмём одну из его кнопок, то в окошке Immediate появится код этой кнопки. Т.е. результат работы этой "программы". Кстати, здесь перечислять параметры меседжбокса надо в скобках, т.к. этот синтаксис используется в норме в таком виде:
| VB.NET | 1
2
3
| If MsgBox("текст сообщения", vbAbortRetryIgnore + vbDefaultButton2 + vbExclamation, "заголовок") = vbIgnore Then
MsgBox "меня все игнорируют"
End If |
|
А ещё мы можем сделать наоборот. Писать программу в модуле, а результаты работы получать во временное место, невидимое пользователю - в это же окошко Immediate. Например в процедуре у нас есть цикл. Он что-то там делает и мы хотим узнать, что насчиталось в переменной a и какое значение имеет счётчик цикла после выхода из него:
| VB.NET | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
| Sub НашаПроцедура()
For i = 0 To 3
a = a + i
Debug.Print "итерация", i, a
Next
Debug.Print "итог", i, a
End Sub
результат:
итерация 0 0
итерация 1 1
итерация 2 3
итерация 3 6
итог 4 6 |
|
Результат вывелся с помощью Debug.Print. Через запятые можно перечислять все переменные, которые нас интересуют - они выведутся с отступом через табулятор. Теперь мы знаем, что в VBA счётчик цикла после выхода из цикла имеет значение на единицу больше, чем последняя итерация и он не обнуляется. Иногда бывают ошибки, когда переменная i в программе используется дальше и забыли её обнулить. Таким образом мы можем проверять значения различных переменных после отработки процедуры.
Режим отладки
Кстати, в общих модулях запустить любую процедуру можно прямо из кода. Т.е. для этого не надо писать где-то ещё "Call НашаПроцедура" (Call нагляднее - понятно, что это написанная нами процедура). Мы просто ставим курсор в любом месте кода процедуры и нажимаем клавишу F5 (а если имеем дело с формой или отчётом, то запустим их работу) или кнопку "воспроизведения"

Запустим вот такой глупый код:
| VB.NET | 1
2
3
4
5
6
7
| Sub НашаПроцедура()
For i = 3 To 0 Step -1
a = a + i
a = a / i
Debug.Print a
Next
End Sub |
|
Наша программа немного поработала, а потом выдала сообщение об ошибке.

Не будем его сразу закрывать и бежать на форум с воплем "ваш пример не работает выдаёт какую-то ашипку". Посмотрим, что нам предлагает сделать сам VBA. Run-time error - это код ошибки. Его можно использовать в таком обработчике ошибок:
| VB.NET | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
| Sub НашаПроцедура()
On Error GoTo ErrorHandler
For i = 3 To 0 Step -1
a = a + i
a = a / i
Debug.Print a
Next
Exit Sub
ErrorHandler:
Select Case Err.Number
Case 11: MsgBox "На ноль делить нельзя", vbCritical
Case Else: MsgBox Err.Description, vbCritical, "Код ошибки: " & Err.Number
End Select
End Sub |
|
On Error GoTo ErrorHandler - инструкция о том, что в случае ошибки программа должна перейти к метке ErrorHandler. Сама метка в коде обозначается её названием и двоеточием. Стоит она после строки Exit Sub - это ещё не конец процедуры, но без меток программа туда не попадёт. Затем начинается перебор всех кодов ошибок, которые в ходе работы процедуры могут возникнуть - Select Case Err.Number. Пока нам известен только код 11 - мы его увидели в сообщении. ДЛя него мы и сделаем своё грозное предупреждение, что так делать нельзя
| Visual Basic | 1
| Case 11: MsgBox "На ноль делить нельзя", vbCritical |
|
Двоеточие после "Case 11" - это не метка, а сокращение написания - чтоб можно было очередную команду написать в этой же строчке. Вот такой код короче на одну строчку за счёт того, что "a = a / i" написано в одной строчке с "Else" 
| VB.NET | 1
2
3
4
| If i = 0 Then
a = 0
Else: a = a / i
End If |
|
Продолжим рассматривать обработчик. Строчка
| VB.NET | 1
| Case Else: MsgBox Err.Description, vbCritical, "Код ошибки: " & Err.Number |
|
выводит не менее грозное сообщение об ошибке, но текст с описанием ошибки берётся из самого VBA - из свойства Err.Description. Если MS Office руссифицированный, то большинство сообщений об ошибках будет написано по-русски. Таким образом и ошибку мы обработаем так, чтоб пользователь не гадал, как на неё реагировать. И, если дополнительно постараться, то можно в программе реализовать правильные решения в таких ситуациях. Но эта тема для отдельного разговора.
Если вы точно уверены, что программа даже с ошибкой будет работать правильно и ошибочное выражение можно смело пропустить, то вместо "On Error GoTo ErrorHandler" можно написать "On Error Resume Next". А если во время отладки хотите проверить конкретный участок кода, то прямо перед ним добавить "On Error GoTo 0". Тогда в случае ошибки появится обычное отладочное сообщение, как на картинке выше. Оно проигнорирует все предыдущие инструкции на счёт реакции на ошибку. Это бывает удобно, если отладчик уже написан, но закралась ещё одна ошибка, о которой мы ещё ничего не знаем.
На всякий случай, чтоб не потерять мысль:
Кликните здесь для просмотра всего текста
| VB.NET | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| Sub НашаПроцедура()
On Error GoTo ErrorHandler
For i = 3 To 0 Step -1
a = a + i
On Error GoTo 0
a = a / i
If i = 0 Then
a = 0
Else: a = a / i
End If
Debug.Print a
Next
Exit Sub
ErrorHandler:
Select Case Err.Number
Case 11: MsgBox "На ноль делить нельзя", vbCritical
Case Else: MsgBox Err.Description, vbCritical, "Код ошибки: " & Err.Number
End Select
End Sub |
|
Так. Что-то я отвлёкся на обработчик ошибки. У нас же было своё - отладочное - сообщение. Продолжим его рассматривать.

А дальше у нас несколько кнопок.
Кнопка "Continue"
В сообщения об ошибках она заблокирована, т.к. ошибку устранять нам, а пока мы с ней не разберёмся, программа продолжать работать не будет. Но есть случаи, когда можно и продолжить. А эти случаи самые что ни наесть отладочные. Допустим, программа зашла в бесконечный цикл:
| VB.NET | 1
2
3
4
5
6
| Sub Бесконечность()
Dim i As Integer
While i = 0
Debug.Print i
Wend
End Sub |
|
Переменная i у нас самого начала была равна нулю и в ходе выполнения цикла с ней ничего не происходит, только выводится её значение. Но цикл никак не может завершиться. Что же делать, если мы запустили такую программу? Перезагружать компьютер, лезть в диспетчер задач? Не обязательно. В любой момент нажимаем клавиши Ctrl+Break. И вот в этом случае у нас и выходит отладочное сообщение, на котором кнопка Continue в этот раз доступна. И нажав её, мы можем опять созерцать работу бесконечности. Но остановить бесконечность нам удалось только потому, что компьютер у нас никогда из неё по своей воле не выйдет. А если программа будет чуть покороче бесконечности или компьютер достаточно быстрый, то мы не успеем уловить этот момент и прервать выполнение программы. А ведь иногда надо это сделать. Для этого у нас есть парочка способов.
Команда Stop
Эту команду мы можем вставить в промежуток между любыми строчками нашего кода. Когда программа в ходе своей работы наткнётся на неё, сообщение мы не увидим, но программа остановится (Странно. Были случаи, когда и сообщение появлялось тоже). Немного модифицируем и запустим нашу бесконечность:

Когда переменная a достигает значения 100, программа останавливается на команде Stop. Сама она выделяется жёлтым маркером, а в левом поле редактора напротив неё стоит жёлтая стрелка - маркер перехода. Мы можем схватить эту стрелку мышкой и переставить в любую другую строчку кода, затем опять нажать клавишу F5 и программа продолжит свою работу с этого места. Кстати, из-за того, что Stop я разместил в одной строке с условием "If a = 100 Then", переставить этот маркер перехода на само условие стало сложнее. Поэтому, хоть такой код и выглядит комактнее, отлаживать его с помощью стопов чуточку сложнее. Впрочем и на команде Stop можно нажать клавишу F5. Но это как-то бессмысленно. Как это можно использовать? Можно нажать другую клавишу - F8. Она продолжает только одну команду в коде. Таким образом мы можем вести программу по шагам. Выполнять очередной шаг и смотреть на результаты. Можно сразу всю программу запустить в пошаговом режиме. Вместо F5 с самого начала нажать F8 и следить за каждым её шагом.
Точка останова
У команды Stop есть одновременно и достоинство и недостаток. Достоинство в том, что с его помощью мы можем оставить этакую закладку. Мы можем сохранить программу в таком виде, запустить её через некоторое время, и программа опять остановится в этом же месте, как бы напоминая нам, что в этом месте надо было сделать ещё что-то важное. Главное, не забыть о ней, отдавая программу пользователю. В этом и кроется её недостаток - о ней надо помнить, чтоб потом убрать из кода. А чтоб отладить программу и не оставлять следов этого, можно использовать точки останова. Они не сохраняются, если программу закрыть, что тоже удобно. Поставить её можно, щёлкнув в левое поле редактора напротив нужной строчки или нажать клавишу F9

На картинке опять эта ситуация из-за "сокращения" кода, когда не понятно, какая из команд будет выполняться следующей - проверка условия или команда Stop.
Опять я отвлёкся от нашего отладочного сообщения. Но на самом деле, не сильно. Дело в том, что в случае ошибки кнопка "Debug" в нём возвращает нас в код, как при использовании стопов. И в этот раз мы уже можем маркер перехода переставить в другую строчку кода, обойдя ошибочную команду, или даже иногда исправить текст кода, пока программа стоит на паузе, а потом опять её продолжить.
Окно Watches
Останавливать программу мы научились. Пропустить команды можем. Можем посмотреть значения некоторых переменных. Но делается это как-то сложно. Надо много писать, смотреть в какие-то окна, не всегда понимая, значения каких именно переменных нам туда написало. А ведь у нас в распоряжении есть ещё одно удобное окошко - Watches. Никаких специальных клавиш для него не заведено. Потому, что его обычно размещают в удобном месте экрана и больше не трогают. Открыть его можно через меню View - Watch Window:

Изначально это окошко всегда пустое, а через него мы можем отслеживать состояние любого объекта среды программирования (в акцессе через него мы можем следить ещё и за и за формами и отчётами). Есть несколько способов, как это сделать. Если у нас в коде программы уже написаны какие-то объекты, то мы их можем:
1. перетащить мышкой в окно Watches
2. выделив название и нажав правой кнопкой мыши, выбрать в меню команду "Add Watch"
3. в окне Watches в свободном пространстве щёлкнуть правой кнопкой мышки, выбрать команду "Add Watch" и в поле "Expression" набрать любое выражение. Например, "2+2". После добавления увидим результат выражения в столбце Value.
Кроме того, в диалоге "Add Watch" можно задать параметры слежки

Этим же способом можно настроить слежку за теми объектами, которые мы добавляли из кода.
Мы можем этим способом остановить нашу бесконечность, когда переменная a будет равна сотне. Для этого добавляем в Watches не саму переменную, а выражение "a = 100", а в настройках слежки выбираем пункт "Break When Value Is True". Т.е. остановить программу, когда выражение "a = 100" станет истиной. Когда программа стоит на паузе, мы можем руками исправлять значение переменных, которые добавлены в окно Watches. Щёлкаем по имеющемуся значению в столбце Value и пишем туда нужное нам значение. Например, сделаем a равной 99. Напоминает хирургическую операцию под наркозом, неправда ли?
Мы так же можем во время паузы выполнения программы размещать в Watches другие переменные и даже формы со всеми их кнопками, полями и вложенными формами.
Изобразим пример таких манипуляций.
Допустим, у нас есть некая Таблица1 с полем Поле1. На основе этой таблицы имеем форму Form1 и кнопку для перехода к следующей записи. Код этой кнопки будет такой:
| VB.NET | 1
2
3
4
5
| Private Sub Кнопка2_Click()
DoCmd.GoToRecord , , acNext
Debug.Print Me.Поле1
Stop
End Sub |
|
Сначала процедура переходит в следующую запись, затем выводит для нас значение Поле1 в этой записи, а потом останавливается программа. И вот теперь мы можем посмотреть кое-что интересное. Выделяем слово Me (это ссылка на объект Form1 в контексте её своей собственной работы) и перетаскиваем в окно Watches. Там это слово появляется не просто так, а с плюсиком слева от себя. Если его нажать, то развернётся боооооольшооооой список всех свойств формы.
Там мы можем в конце списка найти перечисление всех элементов на форме "Item 1", "Item 2" и т.д. Один из них - Поле1, другой - наша кнопка. Они имеют свои большие списки.

Если в окно Watches поместить выражение Me.Поле1, то мы сразу увидим свойства только этого элемента.
Свойства элементов формы прямо здесь изменить мы не сможем, но просто посмотреть, что с ними происходит, пока программа стоит на паузе, можем. Очень часто и этого бывает достаточно. Да и просто много интересного о внутренностях нашей программы можно узнать.
Вообще, во время паузы можно посмотреть значения свойств объектов и переменных прямо в коде, никуда их не вынося - просто наведя мышку на них. Но эти свойства должны быть в коде, чтоб на них можно было смотреть. А в окне Watches можно видеть и то, с чем мы в данный момент и не работаем.
Прямо над Watches Window в меню можно открыть ещё одно окошко - Locals. По сути это почти такое же окошко, как и Watches, но охватывает только список переменных, которые мы используем в нашей процедуре и модуле. Но их не надо поштучно выносить в список, чтоб посмотреть на их значения. Правда, переменные из других модулей, даже если они объявлены Public, мы не увидим.
Шаги обхода
Клавиши F5 и F8 я уже описал. Но у нас есть ещё несколько клавиш.

Посмотрим на таком примере.
| VB.NET | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| Sub Месяц()
Dim dt As Date
dt = DateValue("01/05/2016")
While Month(dt) = 5
If Выходной(dt) = True Then
Debug.Print "выходной", dt
Else
Debug.Print "рабочий день", dt
End If
dt = DateAdd("d", 1, dt)
Wend
End Sub
Function Выходной(dDate As Date) As Boolean
Select Case Weekday(dDate, vbMonday)
Case 6, 7
Выходной = True
Case Else
Выходной = False
End Select
End Function |
|
Запускать будем процедуру "Месяц". Она в цикле перебирает все дни мая 2016 года и запрашивает у функции "Выходной" - этот день выходной? Допустим, мы хотим по шагам проверить работу только процедуры. Мы уверены, что функция работает правильно и пошаговый проход займёт у нас лишнее время. В этом случае мы можем, доходя до строки "If Выходной(dt) = True Then", нажимать не F8, а Shift+F8 - перешагнуть. Т.е. просим VBA выполнить функцию, но не показывать нам, как она это делает.
Но, если мы, всё таки, зашли в функцию и хотим из неё быстро выйти всё потому, что она нам не интересна, то мы можем нажать Ctrl+Shift+F8 - выйти.
Ctrl+F8 - запустить до курсора. Мы можем поставить курсор в коде на несколько строчек позже текущего шага и перешагнуть через все строки, которые окажутся между ними. Это всё равно, что поставить на месте курсора точку останова и нажать F5.
|