Исключения

Общая информация

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

Таким образом, мы понимаем, что информирование об ошибке состоит из двух частей:
  1. Некоторого действия, которое укажет вызывающему коду, что обнаружена ошибка.
  2. Набора данных, который описывает обнаруженную ошибку.

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

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

  • Исполнение программы прервано.
  • Все данные, которые были сформированы (и не сохранены) до возникновения ошибки, потеряны.
  • Пользователь увидит техническое сообщение, которое может не нести никакого прикладного смысла для пользователя.

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

Обработка исключений

Для обработки исключений предназначена специальная конструкция обработки исключений попытка - поймать:

попытка
    безопасный-код

[поймать имя-переменной-1: тип-исключения-1
    обработка-исключения-типа-тип-исключения-1]

[поймать имя-переменной-2: тип-исключения-2|тип-исключения-3
    обработка-исключений-с-типом-тип-исключения-2-или-тип-исключения-3]

[поймать имя-переменной-3: Исключение
    обработка-исключений-остальных-типов]

[вконце
    завершающая-секция]

;

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

  1. Исполнение кода будет безусловно прервано. Станут недоступны те переменные, которые объявлены в той области видимости, где возникло исключение.
  2. Будет определен тип исключения. В зависимости от типа исключения:
    • Подбор обработчика исключений происходит в порядке описания. Обработчик считается подходящим в том случае, если тип исключения обработчика совпадает с типом обрабатываемого исключения. При этом будет учитываться иерархия типов. Обработчиком исключения в данном случае называется блок кода, следующий после конструкции поймать имя-переменной-: тип-исключения-.
    • В обработчик для типа Исключение управление будет передаваться для всех исключений, которые не были перехвачены ранее. Это произойдет потому, что тип Исключение является базовым типом для любого исключения.
    • Если в обработчике исключения нет обработки нужного типа исключения (включая обработчик любого исключения) — значит, исключение будет передано на предыдущий уровень по стеку вызовов методов.
  3. Перед тем как управление будет передано коду, расположенному после синтаксического элемента ;, завершающего блок попытка - поймать, управление передается блоку кода вконце. Сюда же управление предается и в том случае, если никакого исключения в безопасном коде не произошло. В данном блоке имеет смысл размещать код, который должен выполнить какие-то действия вне зависимости от того, удачно или неудачно завершился безопасный фрагмент кода. Блок вконце выполняется даже если в блоках попытка и поймать используются ключевые слова возврат, прервать или продолжить. Тело блока вконце будет выполнено непосредственно перед возвратом значения или продолжением/прерыванием цикла. Хорошим примером может являться открытие файла в безопасном фрагменте и закрытие этого файла в блоке кода вконце. Очевидно, что закрывать файл в безопасном блоке кода в этом случае не требуется.
    Важно: Секция вконце не может содержать ключевых слов, которые могут прервать исполнение секции (возврат, прервать, продолжить).

Как вызвать исключение

Исключения возникают по нескольким причинам:
  • Выполнение операции, недопустимой в языке (например, деление на 0). В этом случае исключение будет сформировано «1С:Шиной».
  • С помощью инструкции выбросить.

Рекомендуемый способ выбрасывания исключения — это применение инструкции выбросить. В качестве параметра данной инструкции выступает экземпляр, описывающий исключение. Тип такого экземпляра является потомком типа Исключение. Этот экземпляр может быть создан с помощью конструктора или получен в обработчике какого-либо исключения. Можно выбрасывать исключения как для стандартных, так и для собственных типов исключений. Стандартное исключение можно создать самостоятельно, если для такого исключения в колонке Конструктор таблицы Типы исключений языка установлена отметка Да.

Объявление типа исключения

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

исключение имя-типа-исключения
    [[модификатор-обязательности]модификатор имя-поля: тип-поля[ = значение-инициализации]]
    [[модификатор-обязательности]модификатор имя-поля: тип-поля[ = значение-инициализации]]
    [[модификатор-обязательности]модификатор имя-поля: тип-поля[ = значение-инициализации]]
    ...
    [конструктор]
    ...
    [метод]
    [метод]
;

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

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

«1С:Шина» неявно генерирует следующие конструкторы:
  • Поле Описание в качестве первого параметра и все поля исключения в порядке объявления — второй и последующие параметры.
  • Поле Описание в качестве первого параметра, поле Причина в качестве последнего параметра и все поля исключения в порядке объявления — начиная со второго параметра и до поля Причина.

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

Рассмотрим пример пользовательского исключения. Допустим, нам необходимо выбросить исключение при ошибке чтения файла. Создадим для этого тип исключения ИсключениеЧтенияФайла, где будет дополнительное поле — имя файла.

исключение ИсключениеЧтенияФайла
    обз пер ИмяФайла: Строка
;
Теперь для того, чтобы создать экземпляр, описывающий это исключение, следует написать следующий код:
новый ИсключениеЧтенияФайла("Ошибка чтения файла", "path\file.txt")
Первым параметром конструктора идет текстовое представление ошибки, затем идут столько значений, сколько полей указано у нашего исключения, и последним, необязательным параметром, является исключение, которое стало причиной выбрасываемого исключения. В этом случае будет образовываться цепочка вложенных исключений. Наиболее вложенное исключение (или самое нижнее) будет описывать исходную причину ошибки, а самое верхнее исключение в цепочке будет содержать текст ошибки для пользователя. Когда необходимо сохранить не только исходную причину ошибки, но и сам факт исключения затем, чтобы его обработкой занимались соответствующие обработчики в вызывающем коде, следует создать свой тип исключения, а перехваченное исключение указать в качестве причины создаваемого. В рассматриваемом примере создание исключения ИсключениеЧтенияФайла будет выглядеть следующим образом:
новый ИсключениеЧтенияФайла("Ошибка чтения файла", "path\file.txt", ФайловаяОшибка)
В этом примере ФайловаяОшибка — это экземпляр, описывающий исключение, перехват которого привел к необходимости выбрасывания исключения ИсключениеЧтенияФайла.

Описание исключения

Каждое исключение обладает следующими стандартными свойствами:

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

Работа с закрываемыми ресурсами

Если в одной области видимости требуется отслеживать состояние нескольких закрываемых экземпляров, то все эти экземпляры необходимо явно объявить с использованием модификатора исп. При выходе из области видимости «1С:Шина» автоматически закроет все экземпляры, объявленные с таким модификатором.

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

Поведение «1С:Шины» зависит от того, сколько закрываемых ресурсов используется в безопасном коде инструкции попытка. Если в безопасном коде используется один закрываемый ресурс, то:
  • Если инициализация закрываемого ресурса выполнена с ошибкой Искл1 - весь блок попытка попытка завершается преждевременно по причине выбрасывания исключения Искл1.
  • Если инициализация ресурса завершена успешно, а блок безопасного кода завершает преждевременно с исключением Искл1:
    • Если закрытие ресурса выполняется успешно, то блок безопасного кода завершается преждевременно по причине выбрасывания исключения Искл1.
    • Если автоматическое закрытие ресурса завершается исключением Искл2, то блок безопасного кода завершается преждевременно по причине выбрасывания исключения Искл1, а исключение Искл2 попадает в список подавленных исключений Искл1.
  • Если и инициализация ресурса и его использование завершены успешно, а закрытие ресурса завершается преждевременно с выбрасывания исключения Искл1, то безопасный код завершает «преждевременно» с выбрасыванием исключения Искл1.
Если в безопасном коде используется несколько закрываемых ресурсов:
  • Если инициализация какого-либо ресурса завершается исключением Искл1:
    • Если автоматическое закрытие всех ранее инициализированных ресурсов завершено успешно, то безопасный блок завершается преждевременно с выбрасыванием исключения Искл1.
    • Если автоматическое закрытие всех ранее инициализированных ресурсов завершается преждевременно с выбрасыванием исключений Искл2 - ИсклN, то безопасный блок завершается преждевременно с выбрасыванием исключения Искл1, а исключения Искл2 - ИсклN будут добавлены в список подавленных исключений для исключения Искл1.
  • Если инициализация всех ресурсов завершилась успешно, а исключение Искл1 возникло в блоке безопасного кода:
    • Если автоматическое закрытие всех ранее инициализированных ресурсов завершено успешно, то безопасный блок завершается преждевременно с выбрасыванием исключения Искл1.
    • Если автоматическое закрытие всех ранее инициализированных ресурсов завершается преждевременно с выбрасыванием исключений Искл2 - ИсклN, то безопасный блок завершается преждевременно с выбрасыванием исключения Искл1, а исключения Искл2 - ИсклN будут добавлены в список подавленных исключений для исключения Искл1.
  • Если безопасный блок завершился успешно:
    • Если автоматическое закрытие всех, кроме одного, ранее инициализированных ресурсов завершено успешно, то безопасный блок завершается «преждевременно» с выбрасыванием исключения, которое возникло при аварийном закрытии единственного ресурса.
    • Если преждевременно завершается закрытие более чем одного закрываемого ресурса, то безопасный блок завершается «преждевременно» с выбрасыванием того исключения, которое при закрытии ресурсов возникло первым, а остальные исключения будут размещены в списке подавленных исключений первого исключения. Следует помнить, что закрытие ресурсов (в том числе автоматическое) выполняется в порядке, обратном объявлению ресурсов. Таким образом, «первым» будет исключение, которое возникло при закрытии последнего закрываемого ресурса в порядке объявления.

Возможно вложенное использование конструкций обработки исключений.

Пример использования закрываемого типа

метод ПримерНаЗакрываемыйТип()
    попытка 
        пер Файл = Файлы.Создать(СредаИсполнения.ПолучитьПеременную("temp") + "\\test.txt")
        исп Поток = Файл.ОткрытьПотокЗаписи()
        Поток.Записать("1-я строка\н")
        Поток.Записать("2-я строка\н")
        Поток.Записать("3-я строка\н")
        пер Успех = "Файл записан!"
    поймать Искл: Исключение
        // попадем сюда, если хоть какое-то исключение случилось
        // здесь поток записи уже закрыт
        // переменная Поток здесь уже отсутствует
        пер Ошибка = Искл.ПоследовательностьВызовов + "\н" + Искл.Описание
    вконце
        // попадем сюда в любом случае
        // здесь поток записи уже закрыт!
        // переменная Поток здесь уже отсутствует
        пер Конец = "Файловый поток закрыт, переменная недоступна (выход из области видимости)"
    ;
;

Встроенные типы исключений

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