Каждый разработчик 1С рано или поздно сталкивается со стандартной, но крайне неприятной ситуацией: при попытке записать или провести документ в управляемой форме пользователь видит безликое системное окно с сообщением: «Ошибка при вызове метода контекста (Записать)». При этом реальная причина ошибки — например, проверка даты запрета изменения данных или нехватка остатков — остается скрытой глубоко в серверном коде, а функция ОписаниеОшибки() на клиенте возвращает пустую строку.
В этой статье мы подробно разберем, почему платформа 1С «съедает» текст исключения при записи объектов базы данных, как работает механизм транзакций на сервере и как правильно спроектировать обработку ошибок, чтобы пользователь всегда получал понятные и информативные сообщения.
Для начала проанализируем ситуацию, описанную в теме. Разработчик пытается обернуть вызов системного метода Форма.Записать() в блок Попытка...Исключение на клиенте:
Попытка
Результат = Форма.Записать(ПараметрыЗаписи);
Исключение
ОбщегоНазначенияКлиентСервер.СообщитьПользователю(ОписаниеОшибки());
Результат = Ложь;
КонецПопытки;
При возникновении ошибки в коде проведения или записи регистров управление передается в блок Исключение, однако функция ОписаниеОшибки() не возвращает ожидаемый текст («Изменяемые данные взаиморасчетов находятся в закрытом периоде»). Это происходит по трем основным причинам:
Записать() выполняет сложную последовательность действий, включающую серверные вызовы. Если на сервере происходит аварийное завершение транзакции из-за оператора ВызватьИсключение, платформа прерывает выполнение серверного вызова и возвращает на клиент общую ошибку выполнения метода. Исходный стек исключений СУБД при этом часто очищается, чтобы избежать передачи сырых системных данных на сторону клиента.Исключение и попытались вычислить выражение ОписаниеОшибки() через Табло или форму «Вычислить выражение», платформа вернет пустую строку! Сам факт обращения отладчика к контексту исполнения сбивает указатель на последнее зарегистрированное исключение. Для корректной отладки всегда необходимо присваивать результат функции локальной переменной в первой же строке блока: ТекстОшибки = ОписаниеОшибки();.Рассмотрим глубже процесс, происходящий на сервере при записи движений документа. В примере автора темы цепочка вызовов выглядит следующим образом:
Форма.Записать().РасчетыСКлиентами.ПриЗаписи этого регистра вызывается запись другого набора — РасчетыСКлиентамиПоСрокам.ПриЗаписи второго регистра срабатывает проверка даты запрета изменения, которая вызывает оператор ВызватьИсключение.Когда вложенный обработчик генерирует исключение через ВызватьИсключение, вся транзакция записи помечается СУБД как «обреченная на откат» (абортированная). Дальнейшие попытки платформы или прикладного кода выполнить какие-либо действия в рамках этой транзакции (например, записать другие движения или обновить статус документа) приводят к возникновению каскадной системной ошибки: «В данной транзакции уже происходили ошибки». Эта техническая ошибка полностью перекрывает собой исходное сообщение о закрытом периоде, и именно ее (в неявном виде) получает клиентское приложение.
Использование оператора ВызватьИсключение внутри транзакционных обработчиков (таких как ПередЗаписью, ПриЗаписи, ОбработкаПроведения) для вывода бизнес-предупреждений категорически противоречит стандартам разработки фирмы «1С». Исключения должны использоваться только для обработки непредвиденных аппаратных или системных сбоев (например, отсутствие связи с базой данных).
Для штатных бизнес-проверок мы должны использовать установку параметра Отказ = Истина в сочетании с выводом сообщений в специализированную очередь платформы через модуль ОбщегоНазначенияКлиентСервер. Перепишем проблемный участок кода в модуле набора записей регистра:
// Вместо ВызватьИсключение ОписаниеОшибки;
Если ДатыЗапретаИзменения.НайденЗапретИзмененияДанных(ДанныеДляПроверки, ЭтотОбъект) Тогда
Отказ = Истина;
// Формируем понятный текст для пользователя
ТекстСообщения = НСтр("ru = 'Изменяемые данные взаиморасчетов находятся в закрытом периоде.'");
// Регистрируем сообщение в очереди платформы
ОбщегоНазначенияКлиентСервер.СообщитьПользователю(ТекстСообщения);
КонецЕсли;
Как это работает:
Отказ = Истина сообщает платформе о необходимости корректно и штатно свернуть транзакцию без вызова аварийного сбоя.СообщитьПользователю() помещает текст ошибки в специальный буфер сообщений, привязанный к текущему сеансу.Форма.Записать() просто возвращает значение Ложь, форма не закрывается, а пользователь видит красивое и понятное сообщение на экране без каких-либо системных окон об ошибках контекста.Если код проверки даты запрета изменения находится в закрытом или типовом модуле, который крайне нежелательно снимать с поддержки, мы можем применить стратегию «упреждающего удара». Вместо того чтобы позволять форме инициировать запись и падать на этапе записи движений, мы выполним проверку до начала транзакции.
Для этого в модуле формы документа перед вызовом записи добавим вызов серверной проверки:
&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
// Выполняем интерактивную проверку даты запрета на сервере
Если ПроверитьДатуЗапретаНаСервере() Тогда
Отказ = Истина;
Возврат;
КонецЕсли;
КонецПроцедуры
&НаСервере
Функция ПроверитьДатуЗапретаНаСервере()
// Вызываем штатные механизмы БСП для проверки дат запрета
ДанныеДляПроверки = ПолучитьДанныеДляПроверкиДатыЗапрета();
Если ДатыЗапретаИзменения.НайденЗапретИзмененияДанных(ДанныеДляПроверки, Объект) Тогда
ТекстОшибки = НСтр("ru = 'Внимание! Изменяемые данные взаиморасчетов находятся в закрытом периоде.'");
ОбщегоНазначенияКлиентСервер.СообщитьПользователю(ТекстОшибки, , "Объект.Дата");
Возврат Истина;
КонецЕсли;
Возврат Ложь;
КонецФункции
Этот подход гарантирует, что пользователь вообще не зайдет в транзакцию записи, если данные некорректны. Это экономит ресурсы сервера СУБД и исключает появление нечитаемых системных ошибок. Для отслеживания порядка срабатывания процедур при записи объектов пригодится инструмент для изучения последовательности событий 1С.
Если вы все же вынуждены работать с перехватом исключений в нетиповом коде (например, при интеграции со сторонними сервисами или чтении внешних файлов) на платформе версии 8.3.17 и выше, настоятельно рекомендуется отказаться от использования устаревшей функции ОписаниеОшибки().
В современных версиях Библиотеки стандартных подсистем (БСП) реализован мощный программный интерфейс для детального логирования и форматирования исключений. Используем общий модуль ОбработкаОшибок:
Попытка
// Какой-то небезопасный код обработки данных
ИнтеграцияССервисомСервер.ОтправитьДанные(Объект.Ссылка);
Исключение
// Получаем структурированную информацию об ошибке
ИнформацияОбОшибке = ИнформацияОбОшибке();
// Формируем понятный текст для пользователя (без технических деталей)
ТекстДляПользователя = ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке);
ОбщегоНазначенияКлиентСервер.СообщитьПользователю(ТекстДляПользователя);
// Записываем детальную техническую информацию со стеком вызовов в Журнал регистрации
ЗаписатьВЖурналРегистрации(
"Интеграция",
УровеньЖурналаРегистрации.Ошибка,
,
,
ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке)
);
КонецПопытки;
Метод ОбработкаОшибок.ПодробноеПредставлениеОшибки() извлекает полную информацию из стека вызовов, включая точный номер строки и имя модуля, где произошел сбой, что значительно упрощает дальнейший поиск и локализацию проблемы разработчиком. Для быстрого подбора процедур и примеров их вызова можно использовать справочник по методам БСП.
Подведем итог нашему исследованию. Для того чтобы навсегда избавить пользователей от непонятных системных сообщений при записи документов, придерживайтесь следующих правил:
ВызватьИсключение в модулях объектов и регистров для обработки штатных бизнес-проверок.Отказ = Истина и метод ОбщегоНазначенияКлиентСервер.СообщитьПользователю(). Это сохраняет транзакционную целостность платформы и гарантирует доставку сообщения пользователю.Попытка...Исключение всегда присваивайте результат ОписаниеОшибки() или ИнформацияОбОшибке() локальной переменной на первой строчке кода, чтобы избежать потери контекста отладчиком.