Перехват исключений в коде

#std499

Область применения: управляемое приложение, мобильное приложение, обычное приложение.

1. В общем случае не рекомендуется перехватывать исключения. В частности не нужно перехватывать исключения только ради выдачи сообщения об ошибке. Необработанное исключение в любом случае будет выдано пользователю в виде сообщения об ошибке (а также будет автоматически записано в журнал регистрации для администратора и отправлено в сервис регистрации ошибок).

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

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

3. Частные случаи некорректного использования и перехвата исключений.

Область применения (уточнение): управляемое приложение, обычное приложение.

3.1. Если имеется некоторая серверная бизнес-логика, которая вызывается с клиента при интерактивной работе пользователя:

&НаСервере
Процедура ВыполнитьОперацию()
    // код, приводящий к вызову исключения
    ....
КонецПроцедуры

то неправильно маскировать от пользователя и администратора исходную проблему:

// на клиенте
Попытка
    ВыполнитьОперацию();
Исключение
    ПоказатьПредупреждение(,НСтр("ru = 'Операция не может быть выполнена.'"));
КонецПопытки;

Правильно записывать в журнал регистрации подробное представление исключения, а краткое представление добавлять в текст сообщения пользователю:

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

и тогда на клиенте:

Попытка
    ВыполнитьОперацию();
Исключение
    ТекстСообщения = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
    ПоказатьПредупреждение(,НСтр("ru = 'Операция не может быть выполнена по причине:'") + Символы.ПС + ТекстСообщения);
КонецПопытки;

Не следует использовать функцию ОписаниеОшибки, т.к. она неинформативна для разработчика, потому что не возвращает стек в тексте ошибки.

3.2. Не следует анализировать текст исключений с целью интерпретации причины ошибки. Текст исключения может меняться в зависимости от локализации. В условиях отсутствия штатных средств (например, типизированных исключений), следует выдавать пользователю тексты исключений «как есть». Для понятности, его можно дополнить пояснением возможных причин.
Например:

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

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

КодОшибки = ЗагрузитьФайлИзИнтернета(...);
Если КодОшибки = 12345 Тогда
...
ИначеЕсли ...

правильно применять строковые литералы (например, "Успешно", "НетМестаНаДиске", "Отменено" и т.п.):

РезультатЗагрузки = ЗагрузитьФайлИзИнтернета(...);
Если РезультатЗагрузки = "Успешно" Тогда
...
ИначеЕсли ...

Строковые литералы кодов ошибок не локализуются.

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

3.3. Если имеется некоторая клиентская бизнес-логика (код выполняется полностью на клиенте):

&НаКлиенте
Процедура СоздатьФайлНаДиске()
    // код, приводящий к вызову исключения
    ....
КонецПроцедуры

то рекомендуется делать дополнительный серверный вызов для протоколирования неудачного результата операции в журнал регистрации:

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

&НаСервереБезКонтекста
Процедура ЗаписатьОшибкуРаботыСФайлами(...)
    ЗаписьЖурналаРегистрации(НСтр("ru = 'Выполнение операции'"),
       УровеньЖурналаРегистрации.Ошибка,,,
       ПодробноеПредставлениеОшибки);
КонецПроцедуры

3.4. Недопустимо перехватывать любые исключения, бесследно для системного администратора:

Попытка
    // код, приводящий к вызову исключения
    ....
Исключение // перехват любых исключений
КонецПопытки;

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

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

См. также Доступ к файловой системе из кода конфигурации, удаление временных файлов

3.5. Неприемлемо в событиях ПриЗаписи, ПередЗаписью, ПередУдалением, ОбработкаПроведения, ОбработкаПроверкиЗаполнения и т.п. вызывать исключения для выдачи останавливающих предупреждений, которые должен отработать пользователь. Вместо этого следует устанавливать параметр Отказ в значение Истина и выводить сообщения пользователю. Тем самым пользователь увидит все блокирующие причины остановки сразу, а не по очереди.

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

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

Подробнее см. пп. 1.1 и 1.3 стандарта Информирование пользователя.

3.6. Недопустимо делать проверки наличия у объекта реквизитов, методов, макетов и т.п., используя для этого исключения, т.к. это может привести к сложно диагностируемым ошибкам, а также затрудняет отладку в режиме «Останавливаться по ошибке».
Вместо перехвата исключений в этом случае рекомендуется:

Например, неправильно:

Попытка
 КонтекстЭДОСервер.ПолучитьМакет("КомпонентаОбмена");
 ПутьВК = КонтекстЭДОСервер.ПутьКОбъекту +  ".Макет.КомпонентаОбмена";
Исключение
КонецПопытки;

Правильно:

МакетКомпонентыОбмена = КонтекстЭДОСервер.Метаданные().Макеты.Найти("КомпонентаОбмена");
Если МакетКомпонентыОбмена <> Неопределено Тогда
 ПутьКМакету = КомпонентаОбмена.ПолноеИмя()
КонецЕсли;

3.7. Порядок обработки исключений при использовании транзакций описан в стандарте Транзакции: правила использования.

3.8. Неправильно использовать исключения для приведения значения к типу. Для таких операций необходимо использовать возможности объекта ОписаниеТипов.

Например, неправильно:

Попытка
 КоличествоДнейРазрешения = Число(Значение);
Исключение
 КоличествоДнейРазрешения = 0; // значение по умолчанию
КонецПопытки;

Правильно:

ОписаниеТипа = Новый ОписаниеТипов("Число");
КоличествоДнейРазрешения = ОписаниеТипа.ПривестиЗначение(Значение);

См. также