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