В процессе разработки и поддержки конфигураций 1С, особенно если вы работаете с чужим или очень большим кодом, часто возникает необходимость точно знать, откуда в коде пришло то или иное сообщение, или где именно произошла ошибка. Стандартные средства отладки, безусловно, мощны, но не всегда удобны для автоматического логирования или вывода служебных сообщений с указанием точного места в коде. Нам требуется программно получить номер строки и наименование текущего модуля, в котором выполняется наш код, чтобы, например, добавить эту информацию в сообщение об ошибке или в журнал регистрации.
Представьте ситуацию: вы пишете код в модуле формы "ПростаяФорма", и при вызове некоторой функции, допустим, ПолучитьМестоВызова(), вы хотите, чтобы эта функция возвращала не только имя текущего модуля, но и номер строки, в которой она была непосредственно вызвана. Это не просто прихоть, а критически важная функциональность для эффективного поиска и исправления проблем, ведь, как отмечает автор темы, на поиск источника ошибки без такой информации может уйти очень много времени.
Давайте рассмотрим, какие существуют подходы для решения этой задачи в платформе 1С.
Платформа 1С, несмотря на отсутствие прямого метода для получения номера строки, предоставляет мощный механизм работы с исключениями, который мы можем адаптировать для наших целей. Суть метода заключается в том, чтобы искусственно создать исключение, а затем перехватить его и извлечь всю необходимую метаинформацию о контексте выполнения, которую платформа автоматически собирает при возникновении ошибки.
Разберем этот подход по шагам:
ВызватьИсключение, чтобы намеренно сгенерировать ошибку. Это действие заставляет платформу зафиксировать текущий контекст выполнения, включая имя модуля и номер строки. В качестве параметра мы можем передать любую строку, которая поможет нам идентифицировать это исключение как "служебное" или "информационное", например, "Информация о вызове".ВызватьИсключение в блок обработки ошибок ПОПЫТКА ... ИСКЛЮЧЕНИЕ ... КОНЕЦПОПЫТКИ. Это гарантирует, что исключение будет перехвачено и обработано, не нарушая работу приложения.ИСКЛЮЧЕНИЕ мы вызываем функцию глобального контекста ИнформацияОбОшибке(). Эта функция возвращает специальный объект, который содержит подробные сведения о последнем возникшем исключении, включая то самое имя модуля и номер строки, которые нам нужны.ИнформацияОбОшибке() мы можем обратиться к его свойствам ИмяМодуля и НомерСтроки, чтобы получить требуемую информацию.Посмотрим на практический пример реализации этого подхода в виде вспомогательной функции:
// Функция для получения имени модуля и номера строки,
// из которой она была вызвана.
// Возвращает строку формата "ИмяМодуля (строка: НомерСтроки)"
Функция ПолучитьМестоВызова() Экспорт
ПеременнаяИмяМодуля = "Не определено";
ПеременнаяНомерСтроки = 0;
Попытка
// Искусственно вызываем исключение.
// Это заставляет платформу собрать информацию о текущем контексте.
ВызватьИсключение "СлужебныйВызовДляПолученияМеста";
Исключение
// Перехватываем наше искусственное исключение.
// Получаем объект, содержащий детальную информацию об ошибке.
ИнформацияОбОшибке = ИнформацияОбОшибке();
// Извлекаем имя модуля и номер строки.
// Важно: НомерСтроки будет указывать на строку ВызватьИсключение,
// но при использовании в функции, это часто то, что нужно для логирования
// места вызова самой функции.
Если ИнформацияОбОшибке <> Неопределено Тогда
ПеременнаяИмяМодуля = ИнформацияОбОшибке.ИмяМодуля;
ПеременнаяНомерСтроки = ИнформацияОбОшибке.НомерСтроки;
КонецЕсли;
КонецПопытки;
// Формируем и возвращаем результат.
Возврат ПеременнаяИмяМодуля + " (строка: " + ПеременнаяНомерСтроки + ")";
КонецФункции
// Пример использования нашей вспомогательной функции.
Процедура ТестоваяПроцедураДляДемонстрации() Экспорт
// Предположим, эта процедура находится в модуле формы "МодульФормы.Документ.ЗаказПокупателя.ФормаДокумента"
// и вызов ниже находится на 20-й строке модуля.
Сообщить("Сообщение из: " + ПолучитьМестоВызова()); // Здесь будет указано место вызова функции ПолучитьМестоВызова()
// Например: "МодульФормы.Документ.ЗаказПокупателя.ФормаДокумента (строка: 20)"
КонецПроцедуры
// Еще один пример, чтобы показать, что функция работает в любом контексте.
Процедура ДругаяПроцедураВДругомМесте() Экспорт
// Эта процедура может быть в другом модуле, например, в общем модуле.
// Если этот вызов на 15-й строке, то результат будет соответствующим.
Сообщить("Привет из: " + ПолучитьМестоВызова());
КонецПроцедуры
В представленном примере, когда мы вызываем ПолучитьМестоВызова(), она "провоцирует" ошибку, перехватывает ее и извлекает точный контекст. Возвращаемая строка будет содержать имя модуля и номер строки, из которой функция ПолучитьМестоВызова() была вызвана. Это позволяет нам точно привязать сообщение к его источнику.
Объект, возвращаемый функцией ИнформацияОбОшибке(), содержит несколько весьма полезных свойств, которые предоставляют нам детальные сведения о последнем исключении. Давайте рассмотрим их подробнее:
ИмяМодуля (ModuleName): Это свойство возвращает строку, содержащую полное имя модуля, в котором произошло исключение. Примеры таких имен могут быть "МодульОбъекта.Документ.РеализацияТоваровУслуг", "Форма.Документ.ЗаказКлиента.ФормаДокумента", "ОбщийМодуль.МойОбщийМодуль", "МодульМенеджера.Справочник.Контрагенты" и так далее. Это позволяет нам однозначно идентифицировать файл, в котором находится проблемный код.ИсходнаяСтрока (SourceLine): Данное свойство предоставляет строку текста самого кода, которая непосредственно вызвала ошибку. Это может быть очень полезно для детального анализа, так как позволяет увидеть не только номер строки, но и само выражение или оператор.НомерСтроки (LineNumber): Это числовое значение, представляющее номер строки в модуле, где возникло исключение. В нашем случае, при искусственном вызове, это будет номер строки, содержащей ВызватьИсключение. Для целей логирования места вызова вспомогательной функции это идеально подходит.Описание (Description): Возвращает строку с текстовым описанием самой ошибки. При использовании ВызватьИсключение "ТекстСообщения", это свойство будет содержать именно "ТекстСообщения", которое мы передали. Это удобно для фильтрации и идентификации наших служебных исключений.Причина (Cause): Это свойство возвращает еще один объект ИнформацияОбОшибке, если текущее исключение является вложенным, то есть произошло внутри блока ПОПЫТКА ... ИСКЛЮЧЕНИЕ, которое уже обрабатывало другое исключение. Это полезно для построения полной цепочки причинно-следственных связей при сложных ошибках.Понимание этих свойств дает нам полный контроль над информацией, которую мы можем извлечь о контексте выполнения кода при возникновении исключений.
Как справедливо было замечено в обсуждении на форуме, подход с искусственным исключением можно обернуть в универсальную процедуру или функцию, чтобы сделать его легкопереиспользуемым по всей конфигурации. Более того, если нам требуется получить не просто место непосредственного вызова, а весь стек вызовов, то есть всю цепочку функций и процедур, которые привели к текущей точке выполнения, мы можем использовать другую функцию – ПодробноеПредставлениеОшибки().
Функция ПодробноеПредставлениеОшибки(), вызванная в блоке ИСКЛЮЧЕНИЕ, возвращает полное текстовое представление стека вызовов. Это представление включает в себя информацию о модулях и номерах строк для каждого звена в цепочке вызовов, от самого начала до момента возникновения исключения. Мы можем использовать это строковое представление, чтобы разобрать его и получить детализацию всех предыдущих вызовов.
Однако важно понимать, что ПодробноеПредставлениеОшибки() возвращает обычную строку. Для извлечения из нее структурированных данных (отдельных имен модулей и номеров строк для каждого вызова в стеке) потребуется дополнительный парсинг этой строки. Это может быть более ресурсоемким по сравнению с прямым получением ИмяМодуля и НомерСтроки, но дает гораздо более полную картину.
Давайте посмотрим на концептуальный пример функции, которая могла бы использовать ПодробноеПредставлениеОшибки():
// Функция для получения полного стека вызовов.
// Возвращает строковое представление стека.
Функция ПолучитьСтекВызовов() Экспорт
СтекВызовов = "";
Попытка
// Искусственно вызываем исключение, чтобы зафиксировать стек.
ВызватьИсключение "ПолучениеСтекаВызовов";
Исключение
// Получаем подробное текстовое представление стека вызовов.
// Оно включает в себя все модули и строки,
// через которые прошло выполнение до текущей точки.
СтекВызовов = ПодробноеПредставлениеОшибки();
КонецПопытки;
// Здесь может быть добавлена логика для "очистки" стека
// от строк, относящихся к самой функции ПолучитьСтекВызовов()
// и ВызватьИсключение, чтобы пользователь видел только
// полезную часть стека, соответствующую его бизнес-логике.
// Например, можно удалить первые несколько строк из СтекВызовов.
Возврат СтекВызовов;
КонецФункции
// Пример использования функции ПолучитьСтекВызовов().
Процедура ГлавнаяПроцедура() Экспорт
Сообщить("Начало ГлавнойПроцедуры");
ПромежуточнаяПроцедура();
Сообщить("Конец ГлавнойПроцедуры");
КонецПроцедуры
Процедура ПромежуточнаяПроцедура() Экспорт
Сообщить("Вызов ПромежуточнойПроцедуры");
КонечнаяПроцедура();
КонецПроцедуры
Процедура КонечнаяПроцедура() Экспорт
// Здесь мы получаем полный стек вызовов,
// который покажет путь от ГлавнаяПроцедура до КонечнаяПроцедура.
Сообщить("Полный стек вызовов из КонечнаяПроцедура:" + Символы.ПС + ПолучитьСтекВызовов());
КонецПроцедуры
Как мы видим, получение полного стека вызовов требует чуть больше усилий, но дает исчерпывающую информацию о пути выполнения кода. Для получения "чистого" стека, который отражает именно последовательность вызовов вашей бизнес-логики, потребуется дополнительная обработка строкового результата ПодробноеПредставлениеОшибки(). Мы должны будем анализировать эту строку и удалять те ее части, которые относятся к вызову нашей служебной функции ПолучитьСтекВызовов и к самому ВызватьИсключение, чтобы не загромождать лог служебной информацией.
Как подчеркнул один из участников обсуждения на форуме, основная ценность такой функциональности заключается в существенном упрощении процессов отладки и поиска источника ошибок. Давайте подробнее разберем, как мы можем использовать полученные имя модуля и номер строки:
ПолучитьМестоВызова() в ключевые точки. Это позволит нам строить своеобразную "карту" выполнения, видеть, какой путь был пройден, какие условия сработали, а какие нет. Такой подход помогает при анализе трудновоспроизводимых ошибок или при оптимизации производительности.Внедрение такой детальной информации в логирование многократно сокращает время на локализацию и исправление ошибок, делая процесс разработки и сопровождения конфигураций 1С гораздо более эффективным и менее трудозатратным.
Хотя метод с искусственным исключением и ИнформацияОбОшибке() является рабочим и относительно простым способом получения номера строки и имени модуля, существуют и другие возможности, особенно в более новых версиях платформы 1С. Также не стоит забывать о мощных встроенных инструментах.
Для интерактивной отладки и анализа кода незаменимым инструментом является встроенный отладчик, доступный в конфигураторе 1С. Мы можем использовать его возможности для получения всей необходимой информации о контексте выполнения:
Ctrl + Alt + C или через меню "Отладка"). Это окно наглядно показывает всю цепочку вызовов процедур и функций, которые привели к текущей строке кода. Мы увидим имена всех модулей и номера строк для каждого вызова в стеке, что позволяет полностью понять контекст выполнения.Отладчик является основным инструментом разработчика для интерактивного анализа, но не подходит для автоматического сбора информации в промышленных решениях.
Начиная с версии платформы 8.3.17, фирма "1С" реализовала новые, более продвинутые и гибкие механизмы обработки ошибок. Методы глобального контекста, такие как КраткоеПредставлениеОшибки(), ПодробноеПредставлениеОшибки() и ПоказатьИнформациюОбОшибке(), были объявлены устаревшими. Вместо них нам рекомендуется использовать одноименные методы, доступные через объект ОбработкаОшибок.
Использование объекта ОбработкаОшибок предоставляет ряд преимуществ:
ОбработкаОшибок облегчает формирование и отправку отчетов об ошибках в централизованные системы мониторинга, что критически важно для крупных проектов и распределенных систем.Хотя объект ОбработкаОшибок в первую очередь предназначен для обработки реальных исключений, его расширенный функционал для получения детальной информации об ошибке может быть также использован для более изощренного логирования, если мы соответствующим образом перехватываем и анализируем исключения.
Для создания действительно эффективной системы логирования и для упрощения поиска ошибок, помимо получения имени модуля и номера строки, мы рекомендуем придерживаться следующих практик:
Внедрение этих подходов позволяет не только быстро локализовать проблему по номеру строки, но и понять полную картину того, что произошло, кто был задействован, какие данные обрабатывались и при каких условиях. Это значительно сокращает время на диагностику и исправление ошибок, делая процесс разработки и сопровождения конфигураций 1С гораздо более прозрачным и эффективным.
Мы убеждены, что применение этих методов поможет вам значительно улучшить качество вашего кода и упростить его поддержку.