Оператор Попытка. Структурная обработка ошибок

Материал из ТХАБ.РФ
Перейти к: навигация, поиск

Говорят, что программ без ошибок не бывает. Оператор Попытка позволяет перехватить, проанализировать и обработать возникшие ошибки и предотвратить аварийное завершение программы.

При написании программ думайте о возможных ошибках. Проверяйте исходные данные.

Как бы мы не старались, но написать программу, в которой бы не случалось ничего не предвиденного невозможно. Впрочем, в обычной жизни без неожиданностей тоже не бывает. У кого-то даже может случиться инфаркт. Такой же «инфаркт» может случиться и в программе, в которой не предусмотрели обработку ошибочной ситуации.

Самый простой путь избежать ошибок в программе это проверка на корректность всех исходных данных. Если исходные данные корректны, то ошибок не произойдет, а если не корректны, то и выполнять действие с ними не надо. Однако, не все программисты одинаково профессиональны и внимательны. Что, если вы используете чужой код, а в нём возникает ошибка? Вот для этого случая в языке Перфолента.Net и предусмотрен оператор Попытка, который перехватывает случившиеся ошибки и позволяет избежать краха программы восстановив нормальное её выполнение.

Самый простой вариант оператора Попытка выглядит так:

Попытка
   //блок кода, в котором возможно возникновение ошибки
КонецПопытки    

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

А = 5
Б = 0
Попытка
   В = А / Б        //здесь произойдет деление на ноль
КонецПопытки    

Секция Исключение

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

Попытка
   //блок кода, в котором возможно возникнет ошибка
Исключение
   //блок кода, в который мы попадем только при возникновении ошибки
КонецПопытки    

Представьте, что в предыдущем примере мы хотим сообщить об ошибке пользователю.

А = 5
Б = 0
Попытка
       В = А / Б        //здесь произойдет деление на ноль
Исключение
       ВыводСтроки "Произошло деление на ноль!"
КонецПопытки    

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

Попытка
       ВызовФункцииЧужойБиблиотеки()       //здесь может произойти не известная ошибка
Исключение Ош       //заведём переменную с именем Ош для перехвата ошибки
       ВыводСтроки "Произошла ошибка: "+Ош.ОписаниеОшибки()
КонецПопытки    

Переменная Ош будет иметь встроенный в язык тип Ошибка. А у этого типа есть метод ОписаниеОшибки, который возвращает строку с текстовым описанием произошедшей ошибки. Тест описания мог быть написан автором вызываемой библиотеки или автоматически составлен средой выполнения .Net.

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

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

Попытка
       ВызовФункцииЧужойБиблиотеки()       //здесь может произойти не известная ошибка
Исключение Ош       //заведём переменную с именем Ош для перехвата ошибки
       Если ТипЗнч(Ош)=Тип("ОшибкаВводаВывода")
               ВыводСтроки "Произошла ошибка ввода-вывода: "+Ош.ОписаниеОшибки()
       ИначеЕсли ТипЗнч(Ош)=Тип("ОшибкаЧтенияДанных")
               ВыводСтроки "Произошла ошибка чтения данных: "+Ош.ОписаниеОшибки()
       Иначе
               ВыводСтроки "Произошла не известная ошибка: "+Ош.ОписаниеОшибки()
       КонецЕсли
КонецПопытки    

Если присмотреться, то можно заметить, что оператор Выбор Для подойдет для этого случая лучше.

Попытка
       ВызовФункцииЧужойБиблиотеки()       //здесь может произойти не известная ошибка
Исключение Ош       //заведём переменную с именем Ош для перехвата ошибки
       Выбор Для Ош
       Когда ЭтоТип ОшибкаВводаВывода
               ВыводСтроки "Произошла ошибка ввода-вывода: "+Ош.ОписаниеОшибки
       Когда ЭтоТип ОшибкаЧтенияДанных
               ВыводСтроки "Произошла ошибка чтения данных: "+Ош.ОписаниеОшибки
       Иначе
               ВыводСтроки "Произошла не известная ошибка: "+Ош.ОписаниеОшибки
       КонецВыбора
КонецПопытки    

Обратите внимание, что в трех последних примерах ошибка произошла где-то в коде метода ВызовФункцииЧужойБиблиотеки(), который нам не доступен для изучения. Ошибка не была перехвачена внутри метода и была передана вызывающему коду, т.е. нашему методу, где мы её и перехватили. Если бы мы её не перехватили, то управление было бы немедленно передано коду, вызвавшему наш метод, и ошибка была бы передана уже туда. Ошибка, не перехваченная ни где, приведет к немедленному краху программы и закрытию её операционной системой.

Секция Завершение.

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

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

Попытка
   //блок кода, в котором возможно возникнет ошибка
Исключение
   //блок кода, в который мы попадем только при возникновении ошибки
Завершение
   //блок кода, который выполнится в любом случае, была ошибка или нет
   //он выполнится даже если в секциях Попытка или Исключение произошел возврат из метода
   //или, когда ошибка произойдет в секции Исключение и не будет перехвачена
КонецПопытки    

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

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

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

Попытка
       //сделаем ошибку
       А = 5 / 0
       ВыводСтроки "Этот текст не будет выведен!"
Завершение
       ВыводСтроки "Завершение без секции Исключение... после ошибки..."
КонецПопытки

Оператор ВызватьИсключение

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

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

Существует 3 варианта оператора ВызватьИсключение.

//вызов исключения без параметров
ВызватьИсключение 
//вызов исключения с параметром типа Строка
ВызватьИсключение "Моё сообщение об ошибке" 
//вызов исключения с параметром типа Ошибка
ВызватьИсключение Новый Ошибка("Моё сообщение об ошибке")

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

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

Вывод:

Оператор Попытка позволяет исключить крах программы при возникновении критических ошибок, возникших при выполнении программы.

'Показать Содержание Описание языка Перфолента.NET'

Ссылки