Как настроить обработку исключений из общего модуля так, чтобы логика обработки была специфичной для вызывающего кода?

Программист 1С v8.3 (Управляемые формы)
← На главную

При разработке конфигураций на платформе 1С:Предприятие 8 часто возникает необходимость выносить общую логику в отдельные общие модули для повторного использования и упрощения поддержки. Однако при этом мы сталкиваемся с задачей, когда исключения, возникающие в общем модуле, требуют разной обработки в зависимости от места вызова. То есть, основное тело процедуры находится в общем модуле, а действия, которые должны выполняться при возникновении ошибки, необходимо прописывать непосредственно в модуле, который вызывает эту общую процедуру (например, в модуле формы документа, модуля объекта или другом общем модуле).

Рассмотрим несколько подходов к решению этой задачи, от простых до более сложных, которые позволяют делегировать или настраивать обработку исключений.

1. Возвращение результата из общего модуля

Это один из самых простых и надежных способов, который позволяет общему модулю сигнализировать о возникновении исключения, а вызывающему коду — принять решение о дальнейших действиях. Общий модуль в этом случае перехватывает свои внутренние исключения и возвращает некий признак успеха/неудачи, возможно, с дополнительной информацией об ошибке.

Как реализовать:

  1. В общем модуле мы используем конструкцию Попытка...Исключение.
  2. В блоке Исключение мы формируем информацию об ошибке.
  3. Общий модуль возвращает значение (например, булево Истина/Ложь, структуру, объект или строку), которое указывает на успешность выполнения или содержит описание ошибки.
  4. Вызывающий код анализирует это возвращаемое значение и выполняет специфичные действия.

Пример кода:

Предположим, у нас есть процедура в общем модуле, которая выполняет некие сложные действия, и нам нужно сообщить вызывающему модулю, успешно ли они завершились или произошла ошибка.


// Общий модуль (Сервер или Клиент, в зависимости от контекста)
Функция ВыполнитьСложноеДействие(ПараметрыДействия) Экспорт
    // Создаем структуру для возврата результата,
    // включающую признак успеха и, возможно, сообщение об ошибке.
    Перем РезультатОперации;
    РезультатОперации = Новый Структура("Успех, СообщениеОбОшибке", Истина, "");

    Попытка
        // Здесь размещается сложная логика, которая может вызвать исключение
        // Например, запись данных, обращение к внешним системам и т.д.
        // ...
        // Имитация ошибки для демонстрации
        // ВызватьИсключение "Имитация ошибки в общем модуле!";
        // ...

        // Если все успешно
        // РезультатОперации.СообщениеОбОшибке = "Действие успешно завершено.";

    Исключение
        // При возникновении исключения фиксируем его
        РезультатОперации.Успех = Ложь;
        РезультатОперации.СообщениеОбОшибке = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
        // Дополнительно можем записать в журнал регистрации, но это уже внутренняя логика модуля
        ЗаписатьВЖурналРегистрации("Ошибка выполнения сложного действия: " + РезультатОперации.СообщениеОбОшибке, УровеньЖурналаРегистрации.Ошибка);
    КонецПопытки;

    Возврат РезультатОперации;
КонецФункции

// Вызывающий модуль (например, модуль формы документа)
Процедура ОбработатьНажатиеКнопки()
    ПараметрыДляОбщегоМодуля = Новый Структура("Ключ1, Ключ2", "Значение1", 123);

    // Вызываем функцию общего модуля
    Результат = ОбщийМодуль.ВыполнитьСложноеДействие(ПараметрыДляОбщегоМодуля);

    // Анализируем полученный результат
    Если НЕ Результат.Успех Тогда
        Сообщить("Внимание! При выполнении действия произошла ошибка: " + Результат.СообщениеОбОшибке, СтатусСообщения.Важное);
        // Здесь мы можем выполнить специфичные для данного документа действия при ошибке:
        // - Отменить проведение документа
        // - Очистить некоторые поля
        // - Изменить статус документа
        // - Предложить пользователю повторить попытку
        // - Записать дополнительную информацию в лог пользователя
        // ...
        Сообщить("Операция отменена. Проверьте данные и повторите попытку.", СтатусСообщения.Внимание);
    Иначе
        Сообщить("Действие успешно выполнено!");
        // Здесь мы можем выполнить специфичные для данного документа действия при успехе:
        // - Провести документ
        // - Обновить форму
        // - Изменить статус документа на "Выполнен"
        // ...
    КонецЕсли;
КонецПроцедуры

Преимущества:

  1. Простота и понятность: Код легко читается и поддерживается.
  2. Гибкость: Позволяет передавать разнообразную информацию об ошибке (код ошибки, подробное описание, объект-источник ошибки).
  3. Независимость от контекста: Работает одинаково хорошо как на клиенте, так и на сервере.

Недостатки:

  1. Общий модуль всегда должен возвращать результат, даже если в нем не было исключения, что может быть нелогично для некоторых функций, которые традиционно ничего не возвращают (например, процедур).
  2. Требует явной проверки результата в каждом месте вызова.

2. Не перехватывать исключение в общем модуле или перехватывать и выбрасывать специфическое

Этот подход предполагает, что общий модуль не обрабатывает исключение сам, а позволяет ему "вылететь" к вызывающему коду. Таким образом, вся ответственность за обработку исключения ложится на вызывающую сторону. Если же в общем модуле могут возникать разные типы ошибок, мы можем перехватить их и перебросить уже свои, более специфические исключения.

Как реализовать:

  1. Вариант 1 (не перехватываем): В общем модуле мы не используем конструкцию Попытка...Исключение. Любое исключение, возникшее внутри, автоматически передается по стеку вызовов выше, до тех пор, пока не будет перехвачено или не вызовет аварийное завершение.
  2. Вариант 2 (перехватываем и выбрасываем специфическое): В общем модуле мы используем Попытка...Исключение, но в блоке Исключение вместо обработки мы используем оператор ВызватьИсключение, возможно, с добавлением контекста или преобразованием стандартной ошибки в более специфическую.
  3. Вызывающий код обертывает вызов общего модуля в свою собственную конструкцию Попытка...Исключение и обрабатывает ошибку там.

Пример кода:


// Общий модуль (Сервер или Клиент)
// Вариант 1: не перехватываем исключение
Функция ВыполнитьДействиеБезПерехвата(Данные) Экспорт
    // Здесь сложная логика, которая может вызвать исключение.
    // Если исключение произойдет, оно "вылетит" из этой функции к вызывающему коду.
    // ...
    // Имитация ошибки
    // Если Данные.Пустое() Тогда
    //    ВызватьИсключение "Пустые данные не допускаются!";
    // КонецЕсли;
    // ...
    Возврат Истина; // Если дошло до сюда, значит, все успешно
КонецФункции

// Вариант 2: перехватываем и выбрасываем специфическое исключение
Функция ВыполнитьДействиеИВыброситьСпецифическое(Данные) Экспорт
    Попытка
        // Здесь сложная логика
        // ...
        // Имитация ошибки
        // Если Данные.Пустое() Тогда
        //    ВызватьИсключение "Внутренняя ошибка обработки данных!";
        // КонецЕсли;
        // ...
        Возврат Истина;
    Исключение
        // Мы перехватили внутреннюю ошибку и выбрасываем свою, более информативную или специфическую
        ВызватьИсключение "Ошибка в общем модуле 'СложныеДействия' при обработке данных: " + ОписаниеОшибки();
    КонецПопытки;
КонецФункции

// Вызывающий модуль (например, модуль объекта документа)
Процедура ПриЗаписиНаСервере(Отказ)
    Попытка
        // Вызываем функцию общего модуля
        // ОбщийМодуль.ВыполнитьДействиеБезПерехвата(ЭтотОбъект.Данные);
        ОбщийМодуль.ВыполнитьДействиеИВыброситьСпецифическое(ЭтотОбъект.Данные);

        // Если исключения не произошло, значит, все успешно
        Сообщить("Документ успешно обработан.");

    Исключение
        // Здесь мы перехватываем исключение, которое "вылетело" из общего модуля
        Отказ = Истина; // Отменяем запись документа
        Сообщить("Произошла ошибка при записи документа: " + ОписаниеОшибки(), СтатусСообщения.Важное);
        // Специфичные для модуля объекта действия при ошибке:
        // - Запись ошибки в реквизит документа
        // - Отмена проведения
        // - Запись в регистр информации об ошибках
        // ...
    КонецПопытки;
КонецПроцедуры

Преимущества:

  1. Прямолинейность: Если обработка исключения всегда нужна в вызывающем коде, это самый прямой путь.
  2. Гибкость для вызывающего кода: Вызывающий код может обрабатывать исключения по-разному в зависимости от своих нужд, используя единую точку входа (Попытка...Исключение).
  3. Семантика ошибок: Вариант с ВызватьИсключение позволяет стандартизировать типы ошибок, выбрасываемых общим модулем.

Недостатки:

  1. Если вызывающий код не обернет вызов в Попытка...Исключение, то произойдет аварийное завершение операции (остановка работы программы с сообщением об ошибке).
  2. Менее "дружелюбный" для пользователя, так как требует от вызывающей стороны большего контроля.

3. Использование механизма коллбэков (ОписаниеОповещения или эмуляция через Выполнить())

Этот подход наиболее гибкий, но и самый сложный в реализации. Он позволяет передавать в общий модуль "ссылку" на процедуру (коллбэк), которая должна быть выполнена при возникновении исключения или после завершения операции. Таким образом, общий модуль вызывает эту переданную процедуру, делегируя ей дальнейшие действия.

3.1. Использование ОписаниеОповещения (преимущественно для клиентских операций)

Объект ОписаниеОповещения в 1С используется для организации отложенного вызова процедуры, чаще всего после асинхронных операций на клиенте (например, после показа вопроса пользователю или после выполнения серверной функции, требующей ожидания). Хотя сам объект ОписаниеОповещения создается на клиенте, его можно использовать для определения обработчиков, которые будут вызваны на клиенте после получения результата от сервера.

Как реализовать:

  1. На клиенте: Мы создаем объект ОписаниеОповещения, указывая имя клиентской экспортной процедуры-обработчика и контекст (например, ЭтотОбъект).
  2. Передача на сервер: Мы вызываем серверную функцию общего модуля, передавая туда этот объект ОписаниеОповещения.
  3. На сервере (в общем модуле): Серверный общий модуль выполняет свою логику. В случае возникновения исключения или успешного завершения, он не может сам напрямую вызвать ВыполнитьОбработкуОповещения (так как это клиентская функция). Вместо этого, он должен вернуть результат на клиент, который затем инициирует ВыполнитьОбработкуОповещения.
  4. На клиенте (после получения результата): Получив результат от сервера, клиентский код анализирует его и вызывает ВыполнитьОбработкуОповещения, передавая туда параметры об успехе/ошибке.
  5. Клиентская процедура-обработчик: Эта процедура, указанная в ОписаниеОповещения, будет вызвана и выполнит специфичные для вызывающего модуля действия.

Пример кода (имитация коллбэка через сервер-клиент взаимодействие):


// Модуль формы (Клиент)
Процедура НачатьСложнуюОперациюНаСервере()
    // 1. Создаем ОписаниеОповещения для обработки результата на клиенте
    ОписаниеОповещения = Новый ОписаниеОповещения("ОбработатьРезультатСервернойОперации", ЭтотОбъект);
    
    // 2. Вызываем серверную процедуру, передавая туда ОписаниеОповещения.
    // На сервере мы не можем "выполнить" оповещение,
    // но можем вернуть данные, чтобы клиент его "выполнил".
    // Важно: сам объект ОписаниеОповещения не может быть передан напрямую
    // как параметр серверной функции и выполнен на сервере.
    // Передача объекта ОписаниеОповещения в серверную функцию осуществляется
    // неявно через механизм вызова клиент-сервер, если сама серверная функция
    // затем возвращает данные на клиент для ВыполнитьОбработкуОповещения.
    // Более реалистичный сценарий: серверная функция просто возвращает структуру с результатом.
    
    // Вызываем серверный метод, который вернет результат
    РезультатОтСервера = ВызватьСервернуюФункциюСОбработчикомНаСервере(ОписаниеОповещения);
    
    // 4. Клиентский код анализирует результат и вызывает ВыполнитьОбработкуОповещения
    Если РезультатОтСервера.Успех Тогда
        ВыполнитьОбработкуОповещения(ОписаниеОповещения, Истина, РезультатОтСервера.Сообщение);
    Иначе
        ВыполнитьОбработкуОповещения(ОписаниеОповещения, Ложь, РезультатОтСервера.Сообщение);
    КонецЕсли;
КонецПроцедуры

&НаСервере
Функция ВызватьСервернуюФункциюСОбработчикомНаСервере(ОписаниеОповещенияДляКлиента)
    // 2. В серверной функции общего модуля мы выполняем основную логику.
    // Параметр ОписаниеОповещенияДляКлиента здесь будет иметь тип "Произвольный".
    // Сам объект ОписаниеОповещения не может быть выполнен на сервере.
    // Мы вернем структуру, которую клиент использует для ВыполнитьОбработкуОповещения.
    
    Перем РезультатСервернойОперации;
    РезультатСервернойОперации = Новый Структура("Успех, Сообщение", Истина, "");

    Попытка
        // Здесь выполняется сложная логика в общем модуле на сервере
        // ...
        // Имитация ошибки
        // ВызватьИсключение "Ошибка на сервере при обработке данных!";
        // ...
        РезультатСервернойОперации.Сообщение = "Серверная операция завершена успешно.";
    Исключение
        РезультатСервернойОперации.Успех = Ложь;
        РезультатСервернойОперации.Сообщение = "Ошибка на сервере: " + ОписаниеОшибки();
        ЗаписатьВЖурналРегистрации("Ошибка в серверной части: " + ОписаниеОшибки(), УровеньЖурналаРегистрации.Ошибка);
    КонецПопытки;
    
    Возврат РезультатСервернойОперации;
КонецФункции

// Модуль формы (Клиент)
// 5. Процедура-обработчик, которая будет вызвана через ОписаниеОповещения
Процедура ОбработатьРезультатСервернойОперации(Успех, Сообщение, ДополнительныеПараметры) Экспорт
    Если НЕ Успех Тогда
        Сообщить("Специфичная для этой формы обработка ошибки: " + Сообщение, СтатусСообщения.Важное);
        // Например, можно подсветить поля, выдать специфичное предупреждение,
        // или перевести фокус на другой элемент.
    Иначе
        Сообщить("Успешно: " + Сообщение);
        // Действия после успешного выполнения
    КонецЕсли;
КонецПроцедуры

Преимущества:

  1. Гибкость: Позволяет выполнять специфичные действия в вызывающем коде, сохраняя централизованную логику в общем модуле.
  2. Асинхронность: Хорошо подходит для сценариев с асинхронными вызовами, где не требуется немедленное прерывание работы.

Недостатки:

  1. Сложность: Более сложен в реализации и отладке по сравнению с первыми двумя подходами.
  2. Контекст выполнения: ОписаниеОповещения и ВыполнитьОбработкуОповещения являются клиентскими функциями. Если общая логика полностью серверная и не требует взаимодействия с клиентом после исключения, этот подход может быть избыточным или неподходящим.

3.2. Эмуляция коллбэков на сервере с помощью Выполнить()

Если нам нужно выполнить процедуру-обработчик непосредственно на сервере (например, из серверного общего модуля в модуль объекта или менеджер), и нам не подходит возврат значения, можно использовать функцию Выполнить(). Этот метод позволяет выполнить код из строки или вызвать процедуру по имени. Это более продвинутый и потенциально менее безопасный подход, чем первые два, но он предоставляет мощный механизм коллбэков на сервере.

Как реализовать:

  1. Вызывающий модуль передает в общий модуль строку с именем экспортной процедуры-обработчика и, возможно, ссылку на объект, который содержит эту процедуру (например, ЭтотОбъект для модуля объекта).
  2. Общий модуль, при возникновении исключения, использует Выполнить() для вызова переданной процедуры-обработчика, передавая ей информацию об ошибке.

Пример кода:


// Общий модуль (Сервер)
Функция ВыполнитьДействиеССервернымОбработчиком(ПараметрыДействия, ИмяПроцедурыОбработчика = "", КонтекстОбъекта = Неопределено) Экспорт
    Перем РезультатУспех;
    РезультатУспех = Истина;

    Попытка
        // Сложная логика общего модуля
        // ...
        // Имитация ошибки
        // ВызватьИсключение "Критическая ошибка серверной операции!";
        // ...

    Исключение
        РезультатУспех = Ложь;
        СообщениеОбОшибке = ОписаниеОшибки();
        ЗаписатьВЖурналРегистрации("Ошибка в серверном модуле: " + СообщениеОбОшибке, УровеньЖурналаРегистрации.Ошибка);

        // Если передан обработчик, вызываем его
        Если ЗначениеЗаполнено(ИмяПроцедурыОбработчика) И КонтекстОбъекта <> Неопределено Тогда
            Попытка
                Выполнить(КонтекстОбъекта, ИмяПроцедурыОбработчика, СообщениеОбОшибке);
            Исключение
                // Ошибка при выполнении самого обработчика - крайне нежелательно
                ЗаписатьВЖурналРегистрации("Ошибка при вызове обработчика исключения '" + ИмяПроцедурыОбработчика + "': " + ОписаниеОшибки(), УровеньЖурналаРегистрации.Ошибка);
                ВызватьИсключение "Критическая ошибка: не удалось выполнить обработчик исключения! " + ОписаниеОшибки();
            КонецПопытки;
        Иначе
            // Если обработчик не передан, или передан некорректно,
            // выбрасываем исключение по умолчанию
            ВызватьИсключение СообщениеОбОшибке;
        КонецЕсли;
    КонецПопытки;
    
    Возврат РезультатУспех; // Можно вернуть булево, чтобы вызывающий код мог знать, был ли обработчик или нет
КонецФункции

// Модуль объекта документа (Сервер)
Процедура ПриЗаписиНаСервере(Отказ)
    ПараметрыДляОбщегоМодуля = Новый Структура("Данные", ЭтотОбъект.Данные);

    // Вызываем общий модуль, передавая имя своей экспортной процедуры-обработчика
    // и ссылку на себя (ЭтотОбъект) как контекст
    УспехОперации = ОбщийМодуль.ВыполнитьДействиеССервернымОбработчиком(
        ПараметрыДляОбщегоМодуля,
        "ОбработатьОшибкуПриЗаписи",
        ЭтотОбъект
    );

    Если НЕ УспехОперации Тогда
        Отказ = Истина;
        Сообщить("Документ не записан из-за ошибки (см. подробности выше).", СтатусСообщения.Важное);
    Иначе
        Сообщить("Документ успешно записан.");
    КонецЕсли;
КонецПроцедуры

// Модуль объекта документа (Сервер)
// Экспортная процедура, которая будет вызвана общим модулем при исключении
Процедура ОбработатьОшибкуПриЗаписи(СообщениеОбОшибке) Экспорт
    Сообщить("Специфичная для модуля объекта документа обработка ошибки записи: " + СообщениеОбОшибке, СтатусСообщения.Важное);
    // Здесь мы можем выполнить действия, специфичные для модуля объекта:
    // - Установить определенные значения в реквизиты документа
    // - Записать дополнительную информацию в регистр сведений
    // - Заблокировать документ для дальнейшей обработки
    // ...
КонецПроцедуры

Преимущества:

  1. Высокая гибкость: Позволяет полностью делегировать логику обработки ошибок в вызывающий код, даже на уровне серверных процедур.
  2. Сохранение контекста: Обработчик выполняется в контексте вызывающего объекта, что дает доступ ко всем его данным и методам.

Недостатки:

  1. Безопасность: Использование Выполнить() может быть потенциально небезопасным, если имя процедуры или контекст передаются из ненадежного источника, так как позволяет выполнить произвольный код.
  2. Сложность: Самый сложный из представленных методов, требует внимательности при реализации и отладке.
  3. Производительность: Вызов через Выполнить() может быть несколько медленнее прямого вызова, но для большинства задач это не критично.

Общие рекомендации по обработке исключений в 1С:Предприятии

  1. Не перехватывайте исключения без необходимости: Если стандартное поведение платформы (вывод сообщения об ошибке и откат транзакции) приемлемо, то лучше не использовать Попытка...Исключение, чтобы не загромождать код. Перехватывайте только тогда, когда вам нужно изменить поведение, например, выдать другое сообщение, записать в журнал или выполнить специфические компенсирующие действия.
  2. Регистрируйте ошибки: Обязательно записывайте подробную информацию о возникших исключениях в журнал регистрации 1С. Это крайне важно для анализа проблем и поддержки системы. Используйте ИнформацияОбОшибке() и КраткоеПредставлениеОшибки() для получения детальных сведений.
  3. Используйте разные уровни логирования: Для обычных информационных сообщений используйте УровеньЖурналаРегистрации.Информация, для предупреждений — УровеньЖурналаРегистрации.Предупреждение, а для критических ошибок — УровеньЖурналаРегистрации.Ошибка или .КритическаяОшибка.
  4. Понятные сообщения для пользователя: Если ошибка отображается пользователю, делайте сообщение максимально информативным и понятным, избегая технических деталей, не относящихся к пользователю. Предложите пользователю возможные действия для исправления ситуации (например, "Проверьте заполнение поля X" или "Обратитесь к администратору").
  5. Восстановимые и невосстановимые исключения: Различайте исключения, после которых система может продолжить работу (например, ошибка блокировки данных), и те, после которых продолжение невозможно (например, ошибка компиляции). Соответственно, реагируйте на них по-разному.

Выбор конкретного подхода зависит от множества факторов: от сложности логики общего модуля, контекста выполнения (клиент/сервер), количества мест вызова и требований к гибкости обработки ошибок. Для большинства случаев достаточно будет возврата структурированного результата или делегирования исключения вызывающему коду. Более сложные механизмы коллбэков стоит применять в ситуациях, когда необходима очень тонкая и динамическая настройка поведения при ошибках.

← На главную