Отладка 1С 8.3 — это умение остановить код в нужном месте и увидеть, где испортились данные. F9 и F5 знают все, а время экономят другие приёмы: условная точка на нужной строке, просмотр временных таблиц, стек вызовов, остановка на «проглоченной» ошибке, замер по этапам и журнал регистрации там, где отладчик не подключить. Ниже — эти приёмы и код для своей конфигурации или расширения.
Запуск отладки в файловой базе — F5 в конфигураторе («Отладка → Начать отладку»): точки останова срабатывают и в клиентском, и в серверном коде. Какой клиент стартует, задаётся в «Сервис → Параметры → Запуск 1С:Предприятия». Клиент, запущенный не из конфигуратора, подключают ключами командной строки:
1cv8c.exe /F"D:\Bases\Test" /DEBUG -http -attach /DEBUGGERURL "http://localhost:1550"
В клиент-серверной базе агент кластера должен работать с ключом -debug (для HTTP-отладки — -debug -http), иначе серверные точки молча пропускаются, а F11 на вызове сервера перешагивает строку. Протокол отладчика («Сервис → Параметры → Отладка») должен совпадать с серверным. При HTTP-отладке работает сервер отладки dbgs.exe, через него подключаются EDT, веб-клиент и удалённые предметы отладки. Настройка службы — в статье про отладку на сервере 1С.
| Где выполняется код | Что нужно для остановки |
|---|---|
Клиент: формы, &НаКлиенте | Запуск по F5 или с ключом /DEBUG |
| Сервер, файловая база | Только запуск по F5 |
| Сервер, клиент-серверная база | Кластер с -debug, предмет «Сервер» в «Отладка → Подключение» |
| Фоновые и регламентные задания | Автоподключение фоновых заданий в «Отладка → Подключение» |
| HTTP- и веб-сервисы | Автоподключение сервисов; для файловой базы на веб-сервере — отладка, разрешённая в публикации |
F9 ставит и снимает точку, Alt+F9 открывает список всех точек: там их отключают, не удаляя, и правят условия. Точка срабатывает до выполнения строки, поэтому её результат виден только после F10.
Условная точка («Отладка → Точка останова с условием», Alt+Shift+F9) останавливает выполнение, только когда условие истинно. Условие — выражение встроенного языка в контексте строки: доступны локальные переменные, реквизиты объекта и функции общих модулей.
| Задача | Условие |
|---|---|
| Одна строка из тысячи | СтрокаТЧ.НомерСтроки = 152 |
| Пустая цена | Не ЗначениеЗаполнено(СтрокаТЧ.Цена) |
| Конкретный документ | Номер = "ТД00-000123" |
| Только свой сеанс в общей базе | ИмяПользователя() = "Иванов" |
| Нужный запрос | СтрНайти(Запрос.Текст, "ВТ_Остатки") > 0 |
| Записать значение без остановки | ОтладкаСервер.Трасса("Цена", СтрокаТЧ.Цена) |
Последняя строка — «точка трассировки»: функция пишет значение в журнал и возвращает Ложь, выполнение не прерывается. В длинных циклах быстрее «ловушка» в коде — проверку делает сам код:
// Общий модуль ОтладкаСервер. Флажки: «Сервер», «Вызов сервера».
// «Вызов сервера» нужен, чтобы функции работали и в условиях точек
// останова в клиентском коде.
// «Точка трассировки»: пишет значение в журнал регистрации и возвращает Ложь,
// поэтому условная точка останова с этой функцией не прерывает выполнение.
Функция Трасса(Метка, Значение = Неопределено) Экспорт
ЗаписьЖурналаРегистрации("Отладка.Трасса", УровеньЖурналаРегистрации.Информация, , ,
Строка(Метка) + ": " + ЗначениеВТекст(Значение));
Возврат Ложь;
КонецФункции
// Текстовое представление значения: коллекции разворачиваются в XML целиком.
Функция ЗначениеВТекст(Значение) Экспорт
ТипЗначения = ТипЗнч(Значение);
Если ТипЗначения = Тип("Структура") Или ТипЗначения = Тип("Массив")
Или ТипЗначения = Тип("Соответствие") Или ТипЗначения = Тип("ТаблицаЗначений") Тогда
Попытка
Запись = Новый ЗаписьXML;
Запись.УстановитьСтроку();
СериализаторXDTO.ЗаписатьXML(Запись, Значение, НазначениеТипаXML.Явное);
Возврат Запись.Закрыть();
Исключение
// Внутри есть несериализуемое значение: оставляем тип и размер
Возврат СтрШаблон("%1, элементов: %2", ТипЗначения, Значение.Количество());
КонецПопытки;
КонецЕсли;
Возврат СтрШаблон("%1 (%2)", Значение, ТипЗначения);
КонецФункции
// «Ловушка» в модуле объекта документа (временно, удалить после отладки).
// F9 ставится на строку с вызовом Трасса: отладчик остановится только
// на проблемной строке, а без отладчика данные останутся в журнале.
Процедура ОбработкаПроверкиЗаполнения(Отказ, ПроверяемыеРеквизиты)
Для Каждого СтрокаТЧ Из Товары Цикл
Если СтрокаТЧ.Количество > 0 И СтрокаТЧ.Цена = 0 Тогда
ОтладкаСервер.Трасса("Нулевая цена", Новый Структура("НомерСтроки, Номенклатура",
СтрокаТЧ.НомерСтроки, СтрокаТЧ.Номенклатура));
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Возврат Ложь — суть приёма: условие никогда не выполняется, а след в журнале остаётся.СериализаторXDTO.ЗаписатьXML разворачивает коллекцию целиком, ссылкам и примитивам хватает представления с типом.Остановка по ошибке («Отладка → Остановка по ошибке») ставит выполнение на паузу там, где возникло исключение, в том числе внутри Попытка…Исключение, которая его потом «глотает». Так находят причину пустых результатов и сообщения «В данной транзакции уже происходили ошибки!». Чтобы не останавливаться на каждом исключении типовой, задайте подстроку текста, например «Деление на 0».
| Действие | Клавиши |
|---|---|
| Начать отладку / продолжить до следующей точки | F5 |
| Завершить отладку | Shift+F5 |
| Шагнуть через процедуру | F10 |
| Шагнуть в процедуру / выйти из неё | F11 / Shift+F11 |
| Идти до курсора | Shift+F10 |
| Вычислить выражение | Shift+F9 |
| Стек вызовов | Ctrl+Alt+C |
Табло («Отладка → Табло») пересчитывается на каждом шаге — там держат то, за чем следят весь сеанс: ТранзакцияАктивна(), Товары.Итог("Сумма"). «Вычислить выражение» — разовая проверка: Запрос.Выполнить().Выгрузить() раскрывается как таблица значений. Значения примитивных типов можно изменить прямо в ячейке «Значение» и продолжить выполнение — гипотеза проверяется без правки кода.
Стек вызовов показывает цепочку процедур до текущей строки. Двойной щелчок по уровню открывает место вызова, а выражения вычисляются в контексте этого уровня: видно, с какими параметрами пришли в процедуру.
Временные таблицы удобно выгрузить одной функцией. Без менеджера временных таблиц подойдёт Запрос.ВыполнитьПакетСПромежуточнымиДанными(): он вернёт результаты всех запросов пакета, включая ПОМЕСТИТЬ, но выполнит пакет повторно.
// Общий модуль ОтладкаСервер (продолжение).
// Содержимое временных таблиц для просмотра в отладчике. В «Вычислить выражение»:
// ОтладкаСервер.ВременныеТаблицы(Запрос.МенеджерВременныхТаблиц, "ВТ_Остатки, ВТ_Цены")
Функция ВременныеТаблицы(МенеджерВТ, Знач ИменаТаблиц = "") Экспорт
Результат = Новый Соответствие;
Если ТипЗнч(МенеджерВТ) <> Тип("МенеджерВременныхТаблиц") Тогда
Возврат Результат;
КонецЕсли;
Отбор = СтрРазделить(ВРег(СтрЗаменить(ИменаТаблиц, " ", "")), ",", Ложь);
Для Каждого Таблица Из МенеджерВТ.Таблицы Цикл
Если Отбор.Количество() > 0 И Отбор.Найти(ВРег(Таблица.ПолноеИмя)) = Неопределено Тогда
Продолжить;
КонецЕсли;
Результат.Вставить(Таблица.ПолноеИмя, Таблица.ПолучитьДанные().Выгрузить());
КонецЦикла;
Возврат Результат;
КонецФункции
ПолучитьДанные() читает уже созданную таблицу без повторного выполнения пакета, поэтому функцию можно вызывать на любом шаге. Запрос.Выполнить() в окне выражения, наоборот, повторит ПОМЕСТИТЬ и может упасть на уже существующей таблице.
На строке с вызовом &НаСервере-процедуры F11 переносит отладчик в серверный код; если строка просто перешагивается, сервер не подключён к отладке. Что путает на стыке контекстов:
&НаСервере реквизиты формы — копия, приехавшая с вызовом: на клиенте изменения появятся только после возврата;&НаСервереБезКонтекста формы нет, и выражение Объект.Товары даёт «Переменная не определена (Объект)»;Фоновое задание выполняется в отдельном сеансе, и точка в нём срабатывает только при автоподключении — см. статью о фоновых заданиях. Метод регламентного задания часто проще вызвать в текущем сеансе с теми же параметрами: работают обычные точки и остановка по ошибке, запуск можно повторять. Форма внешней обработки для этого:
// Внешняя обработка, модуль формы. Реквизит формы ИмяЗадания (Строка) -
// имя регламентного задания в метаданных, команда ЗапуститьМетод.
// Метод выполняется в текущем сеансе: точки останова срабатывают
// без автоподключения фоновых заданий.
&НаКлиенте
Процедура ЗапуститьМетод(Команда)
ВыполнитьМетодЗадания(ИмяЗадания);
ПоказатьОповещениеПользователя("Метод регламентного задания выполнен");
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ВыполнитьМетодЗадания(Имя)
МетаданныеЗадания = Метаданные.РегламентныеЗадания.Найти(Имя);
Если МетаданныеЗадания = Неопределено Тогда
ВызватьИсключение СтрШаблон("Регламентное задание ""%1"" не найдено", Имя);
КонецЕсли;
// Параметры берём у самого задания. Непредопределённых заданий
// одного вида может быть несколько - тогда берётся первое.
Задания = РегламентныеЗадания.ПолучитьРегламентныеЗадания(
Новый Структура("Метаданные", МетаданныеЗадания));
ПараметрыЗадания = Новый Массив;
Если Задания.Количество() > 0 Тогда
ПараметрыЗадания = Задания[0].Параметры;
КонецЕсли;
Аргументы = Новый Массив;
Для Индекс = 0 По ПараметрыЗадания.ВГраница() Цикл
Аргументы.Добавить(СтрШаблон("ПараметрыЗадания[%1]", Формат(Индекс, "ЧН=0; ЧГ=0")));
КонецЦикла;
// ИмяМетода имеет вид "ИмяОбщегоМодуля.ИмяПроцедуры"
Выполнить(МетаданныеЗадания.ИмяМетода + "(" + СтрСоединить(Аргументы, ", ") + ");");
КонецПроцедуры
Выполнить видит локальные переменные, поэтому в строку подставляются обращения ПараметрыЗадания[0], а не сами значения.ПолучитьТекущийСеансИнформационнойБазы().ПолучитьФоновоеЗадание() поведёт себя иначе. Создание заданий из кода — в статье о регламентных заданиях.Внешнюю обработку отлаживают как код конфигурации: откройте файл в конфигураторе («Файл → Открыть»), расставьте точки, запустите отладку и откройте тот же файл в 1С:Предприятии. После правки сохраните файл и откройте его заново — открытая форма работает со старым модулем. Если обработка зарегистрирована в дополнительных отчётах и обработках, код берётся из базы, а не из файла: этот случай разобран в статье о внешней печатной форме на БСП.
Когда вопрос «где тормозит», включите «Отладка → Замер производительности», выполните операцию и остановите замер: конфигуратор покажет строки модулей с числом выполнений и долей времени. Строка с тысячами выполнений внутри цикла — первый кандидат. Остальные инструменты — в статье «1С тормозит и зависает».
Замер не включить у пользователя на рабочей базе, и он неудобен, если операция тормозит раз в день. Тогда в код ставят секундомер, который пишет в журнал только медленные вызовы с разбивкой по этапам:
// Общий модуль ОтладкаСервер (продолжение): секундомер по этапам.
Функция НачатьЗамер(Операция) Экспорт
Сейчас = ТекущаяУниверсальнаяДатаВМиллисекундах();
Возврат Новый Структура("Операция, Начало, Отсечка, Этапы", Операция, Сейчас, Сейчас, Новый Массив);
КонецФункции
Процедура Этап(Замер, ИмяЭтапа) Экспорт
Сейчас = ТекущаяУниверсальнаяДатаВМиллисекундах();
Замер.Этапы.Добавить(СтрШаблон("%1: %2 мс", ИмяЭтапа, Формат(Сейчас - Замер.Отсечка, "ЧН=0; ЧГ=0")));
Замер.Отсечка = Сейчас;
КонецПроцедуры
// Пишет в журнал, только если операция шла дольше порога.
Процедура ЗавершитьЗамер(Замер, ПорогМс = 0) Экспорт
Всего = ТекущаяУниверсальнаяДатаВМиллисекундах() - Замер.Начало;
Если Всего < ПорогМс Тогда
Возврат;
КонецЕсли;
ЗаписьЖурналаРегистрации("Отладка.Замер", УровеньЖурналаРегистрации.Предупреждение, , ,
СтрШаблон("%1: всего %2 мс%3%4", Замер.Операция, Формат(Всего, "ЧН=0; ЧГ=0"),
Символы.ПС, СтрСоединить(Замер.Этапы, Символы.ПС)));
КонецПроцедуры
// Использование в модуле объекта документа. ЗаполнитьДвижения и ПроверитьОстатки -
// процедуры самого документа, время которых нужно разделить.
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
Замер = ОтладкаСервер.НачатьЗамер("Проведение " + Строка(Ссылка));
ЗаполнитьДвижения();
ОтладкаСервер.Этап(Замер, "Движения");
ПроверитьОстатки(Отказ);
ОтладкаСервер.Этап(Замер, "Контроль остатков");
ОтладкаСервер.ЗавершитьЗамер(Замер, 1000); // в журнал - только дольше секунды
КонецПроцедуры
ТекущаяУниверсальнаяДатаВМиллисекундах() даёт миллисекунды и не зависит от часового пояса.Отладчик не подключить к рабочему серверу без перезапуска службы, к HTTP-сервису, который вызывает внешняя система с таймаутом, к сеансу пользователя в другом городе. Там отладочные записи пишут через ЗаписьЖурналаРегистрации. Чтобы не засорять журнал, подробный режим включается для одного пользователя через хранилище общих настроек — без новых объектов метаданных и перезапуска:
// Общий модуль ОтладкаСервер (продолжение): подробный лог по требованию.
// Включён ли подробный лог для текущего пользователя.
Функция ПодробныйЛог() Экспорт
Возврат ХранилищеОбщихНастроек.Загрузить("Отладка", "ПодробныйЛог") = Истина;
КонецФункции
// Функция, а не процедура: её можно вызвать и из «Вычислить выражение».
// Чтобы включить лог другому пользователю, нужны административные права.
Функция УстановитьПодробныйЛог(ИмяПользователяИБ, Включить = Истина) Экспорт
ХранилищеОбщихНастроек.Сохранить("Отладка", "ПодробныйЛог", Включить, , ИмяПользователяИБ);
Возврат Включить;
КонецФункции
// Шаг операции. ИдОперации связывает все записи одного вызова.
Процедура Шаг(ИдОперации, Текст, Ссылка = Неопределено) Экспорт
ЗаписьЖурналаРегистрации("Отладка.Шаг", УровеньЖурналаРегистрации.Информация,
Метаданные.НайтиПоТипу(ТипЗнч(Ссылка)), Ссылка, СтрШаблон("[%1] %2", ИдОперации, Текст));
КонецПроцедуры
Обработчик HTTP-сервиса с этим модулем: ошибка пишется всегда, шаги — только в подробном режиме, а код операции уходит в ответ.
// Модуль HTTP-сервиса Заказы, обработчик метода POST.
Функция ЗаказыPOST(Запрос)
ИдОперации = Строка(Новый УникальныйИдентификатор);
Подробно = ОтладкаСервер.ПодробныйЛог();
Тело = Запрос.ПолучитьТелоКакСтроку();
Если Подробно Тогда
ОтладкаСервер.Шаг(ИдОперации, "Получено: " + Лев(Тело, 5000));
КонецЕсли;
Попытка
Заказ = ЗаказыСервер.ЗаписатьЗаказ(Тело); // прикладная логика сервиса
Если Подробно Тогда
ОтладкаСервер.Шаг(ИдОперации, "Заказ записан", Заказ);
КонецЕсли;
Ответ = Новый HTTPСервисОтвет(200);
Исключение
ЗаписьЖурналаРегистрации("Отладка.Ошибка", УровеньЖурналаРегистрации.Ошибка, , ,
СтрШаблон("[%1] %2", ИдОперации, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке())));
Ответ = Новый HTTPСервисОтвет(500);
Ответ.УстановитьТелоИзСтроки("Ошибка обработки, код операции " + ИдОперации);
КонецПопытки;
Ответ.Заголовки.Вставить("X-Operation-Id", ИдОперации);
Возврат Ответ;
КонецФункции
X-Operation-Id, а вы отбираете события «Отладка.Шаг» и «Отладка.Ошибка» и ищете код в комментарии.Лев(Тело, 5000) ограничивает размер записи; пароли, токены и персональные данные в журнал не пишите.ОтладкаСервер.УстановитьПодробныйЛог("Интеграция") из внешней обработки администратора, выключают — тем же вызовом с Ложь.-debug, протоколы сервера и отладчика разные или в «Отладка → Подключение» не подключён «Сервер».&Вместо — ставьте точку в модуле расширения. Или при F5 отказались обновлять конфигурацию базы данных, и отладка идёт по старому коду.Запрос.Выполнить().Выгрузить(), и оно выполняется после каждого F10.ИмяПользователя() = "Иванов".Перезапустите клиент с ключом /DEBUG, для HTTP-отладки — с -http -attach /DEBUGGERURL и адресом сервера отладки, как в примере выше. Сеанс появится в «Отладка → Подключение».
Откройте расширение в конфигураторе («Конфигурация → Расширения конфигурации»), поставьте точки в его модулях и запустите отладку как обычно. Перед запуском обновите расширение в базе данных, иначе выполняется прежняя версия кода.
Технически да, но ключ -debug требует перезапуска службы, а остановка внутри транзакции держит блокировки, и у пользователей начинаются ошибки по таймауту. На рабочей базе — журнал регистрации и секундомер, отладчик — на свежей копии.
EDT работает только через HTTP-отладку, а клавиши там как в Eclipse: F5 — шаг внутрь, F6 — шаг через, F7 — выход из процедуры, F8 — продолжить. Условные точки, остановка по исключениям и изменение значений переменных есть и там.
Если ошибка воспроизводится только на рабочей базе, «плавает» между сеансами или прячется в обмене и фоновых заданиях, поиск причины легко растягивается на дни. Разместите задание с текстом ошибки и шагами воспроизведения — исполнитель найдёт причину на копии базы и предложит исправление.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.