Почему при перехвате ошибки внутри Попытка — Исключение срабатывает внешнее исключение в 1С?

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

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

Как работает вложенная конструкция Попытка — Исключение

Рассмотрим стандартное поведение платформы 1С при возникновении исключений. Если внутри вызываемой процедуры код заключен в блок Попытка ... Исключение, возникшая системная ошибка должна быть подавлена и не передаваться на уровень выше, если только разработчик явно не вызвал повторную генерацию ошибки с помощью оператора ВызватьИсключение.

Посмотрим на два базовых примера:


// Пример 1: Исключение перехвачено и погашено
Процедура ВыполнитьДеление()
    Попытка
        Результат = 1 / 0; 
    Исключение    
        // Ошибка перехвачена внутри, во внешний блок исключение не идет
    КонецПопытки; 
КонецПроцедуры

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

Если же внутри блока Исключение используется повторный выброс ошибки, управление передается вышестоящему обработчику:


// Пример 2: Явный проброс ошибки выше
Процедура ВыполнитьДелениеСПробросом()
    Попытка
        Результат = 1 / 0; 
    Исключение    
        // Выполняем сопутствующие действия и пробрасываем ошибку
        ВызватьИсключение;
    КонецПопытки; 
КонецПроцедуры

Попытка
    ВыполнитьДелениеСПробросом();
Исключение
    // Сюда управление попадет гарантированно
    Сообщить("Перехвачена ошибка из ВыполнитьДелениеСПробросом");
КонецПопытки;

Причина проблемы: неявные транзакции и нехватка прав доступа

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

Причина кроется в транзакционной модели платформы 1С:

  1. Запись любого ссылочного объекта (справочника, документа) платформой всегда выполняется внутри неявной системной транзакции (события ПередЗаписью, ПриЗаписи, ОбработкаПроведения).
  2. Если внутри активной транзакции происходит ошибка базы данных (нарушение прав доступа RLS, конфликт блокировок СУБД, нарушение ссылочной целостности), текущая транзакция на уровне СУБД сразу же помечается как недействительная (сбойная).
  3. Даже если в коде 1С мы перехватили ошибку через Попытка ... Исключение, восстановить целостность транзакции уже невозможно. При попытке завершения (фиксации) транзакции платформа выбрасывает критическую ошибку: «В данной транзакции происходили ошибки» или «Транзакция отменена». Это исключение перехватывается уже внешним блоком.

Способ 1. Использование привилегированного режима

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

Разберем реализацию через метод УстановитьПривилегированныйРежим():


УстановитьПривилегированныйРежим(Истина);

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

УстановитьПривилегированныйРежим(Ложь);

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

Способ 2. Программная проверка прав доступа перед записью

Если запись в регистр не должна выполняться при отсутствии прав у пользователя, нельзя полагаться на перехват исключения внутри транзакции. Необходимо заранее проверить доступность операции с помощью функции ПравоДоступа():


// Проверяем право на запись в регистр без провоцирования ошибки СУБД
Если ПравоДоступа("Запись", Метаданные.РегистрыСведений.ЗначенияСвойствНоменклатуры) Тогда
    НаборЗаписей = РегистрыСведений.ЗначенияСвойствНоменклатуры.СоздатьНаборЗаписей();
    НаборЗаписей.Отбор.Объект.Установить(СсылкаНаНоменклатуру);
    // Заполнение и запись набора
    НаборЗаписей.Записать();
КонецЕсли;

Такой подход гарантирует, что при отсутствии прав платформа не будет отправлять заведомо ошибочный запрос к СУБД, транзакция сохранения номенклатуры останется валидной и успешно зафиксируется.

Способ 3. Вынос некритичных записей за пределы транзакции

Если запись вспомогательных данных не должна влиять на сохранение самого объекта, вынесем эту операцию за пределы транзакции записи:

  1. Выполнять запись регистра в обработчиках формы (например, ПослеЗаписиНаСервере или ПослеЗаписи на клиенте), когда транзакция объекта уже зафиксирована.
  2. Использовать функцию ТранзакцияАктивна(), чтобы не выполнять рискованные операции внутри открытых транзакций.
  3. Передавать задачу записи во вспомогательный регистр в фоновое или регламентное задание.
← На главную