Обработка исключений — различия между версиями
(→Исключения и работа с ресурсами) |
|||
Строка 136: | Строка 136: | ||
То есть, не стоит ловить исключение только потому что это можно сделать. Смысл всей технологии в том, что ошибки обрабатываются в тех местах, где их можно исправить. <!--Приведем парочку примеров разных ситуаций, иллюстрирующих проблему. Допустим, мы пишем библиотечный код работы с сетевыми соединениями, который представляет собой надстройку над сокетами. В процессе чтения из сокеты мы можем столкнуться с ошибкой чтения.--> | То есть, не стоит ловить исключение только потому что это можно сделать. Смысл всей технологии в том, что ошибки обрабатываются в тех местах, где их можно исправить. <!--Приведем парочку примеров разных ситуаций, иллюстрирующих проблему. Допустим, мы пишем библиотечный код работы с сетевыми соединениями, который представляет собой надстройку над сокетами. В процессе чтения из сокеты мы можем столкнуться с ошибкой чтения.--> | ||
− | + | Yup, that shuold defo do the trick! | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + |
Версия 13:08, 3 ноября 2011
Как уже было отмечено во введении, исключения — это мощный механизм, позволяющий в разы сократить время, требуемое на написание программ и значительно повысить их качество. В основном, это достигается за счет того что, программист пишет более простой код, не отвлекаясь от основной задачи на разного рода рутинные операции, вроде обработки ошибок, а значит, его внимание сконцентрировано на самой проблеме. В итоге, это приводит к написанию более качественного кода. В этой главе будет рассмотрена сущность механизма исключений, а так же способы их применения на практике. Будут даны типовые схемы разработки программ и даны соответствующие пояснения.
Содержание |
Идеология исключений
С философской точки зрения, исключением называется некоторое событие, произошедшее при работе программы и несущее негативный характер. Имеется в виду, что это событие не должно происходить при нормальных условиях. Примером таких событий могут послужить следующие ситуации:
- Обрыв линии связи и, как следствие, прекращение передачи
- Внезапный конец файла конфигурации (или БД)
- Ввод данных, не соответствующий требуемому формату (например строки, когда ожидалось число)
- Ошибка выделения памяти
- Падение метеорита, война, нашествие инопланетян и т. д.
Все вышеперечисленные события так или иначе могут произойти в ходе работы программы, однако, с точностью предсказать их практически невозможно, можно только оценить вероятность. Хорошая программа так и делает: в тех местах, где могут потенциально произойти исключительные ситуации, вставляется код, проверяющий состояние и производящий некоторые действия по "ликвидации последствий". Например, в случае ошибки связи, программа может попытаться установить соединение заново, в то время как при ошибке внезапного конца файла, остается только уведомить об этом пользователя, показав соответствующее сообщение об ошибке.
Проблема заключается в том, что не всегда можно угадать место, где может произойти ошибка. Проверять же состояние ошибки буквально в каждой операции — утомительно и практически невозможно. Это засоряет код алгоритма всевозможными вспомогательными конструкциями (условиями проверки на ошибку), делает его менее читаемым и усложняют и без того нелегкую задачу отладки.
При использовании концепции исключений, все выглядит совсем иначе. Большую часть кода, программист пишет из соображения, что "все в порядке". При этом, код получается кратким и содержит только те действия, ради которых он создавался.
В некоторых местах, где заранее предусматривается возможность проявления ошибки (например при открытии файла), вставляется специальная конструкция исключения. Она состоит из основного блока и одного или нескольких блоков — перехватчиков исключений. Выглядит эта конструкция так: <source lang="kpp"> try {
/* try-блок (основное тело) */
} catch (/* переменная для объекта исключения 1 */) {
/* код перехватчика 1 */
} catch (/* переменная для объекта исключения 2 */) {
/* код перехватчика 2 */
} </source>
Ключевое слово try объявляет начало защищенного блока. Затем, идет основное тело или try-блок, в котором содержатся конструкции, соответствующие нормальной работе. В примере с файлом, здесь будет находиться код чтения содержимого файла и, возможно, его обработки. Если в ходе выполнения этого блока, возникнет исключительная ситуация, то управление будет передано одному из блоков обработки, соответствующему типу возникшего исключения (об этом чуть позже). В круглых скобках указывается имя переменной, которой будет назначен объект исключения — объект некоторого класса, который был "выброшен" из кода в качестве исключения.
Объект исключения
С точки зрения языка К++, объект исключения — это такой же объект (или класс объектов), как и все остальные, только он несет строго определенный смысл, привязанный к концепции исключений. Иначе говоря, объект исключения — это специальная сущность, которая содержит в себе некоторую известную информацию об ошибке. Например, в случае ошибки чтения файла, в объекте может содержаться путь к файлу. В случае ошибки формата исходных данных, он может содержать информацию о том, какие были входные данные и какие ожидались на самом деле. Словом, объект исключения содержит информацию, которая отражает возникшую ошибку. Как правило, все объекты исключения имеют строковое поле, в которое записывается текстовая информация, описывающая проблему. Например, оно может содержать строки вида: "не могу открыть файл", "отказано в доступе" или "так сложились звезды". Впоследствии, эти строки могут быть записаны в лог файл, либо показаны пользователю в виде сообщения.
Генерация исключения
Код, который первым обнаруживает исключительную ситуацию (то есть, стоит у ее истоков) и желающий сообщить о ней "наверх", должен создать объект исключения и "выбросить" его. Создается объект так же, как и любой другой объект в языке К++: либо с помощью оператора new, либо с помощью конструктора (если он предусмотрен). "Выбрасывание" объекта осуществляется с помощью специального оператора throw, который принимает объект в качестве параметра. Обычно, эти операции совмещают в одном действии.
Приведем пример некоторой функции, которая проверяет правильность передаваемых ей данных и выбрасывает исключение в случае несоответствия: <source lang="kpp"> function Process(const int idx) {
unless (idx in 5 .. 10) throw "индекс должен быть в диапазоне от 5 до 10"; /* нормальный код обработки */
} </source>
В первой строке тела функции, проверяется условие, необходимое функции для работы. Если условие не выполняется, то производится генерация исключения, причем в качестве объекта исключения выступает объект строки.
На практике применяются специальные классы распространенных исключений, которые объявлены в стандартной библиотеке. Таким образом, все языки использующие стандартную библиотеку и умеющие работать с исключениями, смогут успешно взаимодействовать с помощью этого механизма.
Приведем некоторые из наиболее распространенных классов исключений и поясним их назначение:
EInvalidCall неверный вызов (входные данные неверны) EFailed операция не удалась (и все тут) EAgain по тем или иным причинам требуется повтор операции EDenied действие запрещено! ECancelled операция была отменена (например пользователем) EAlready операция уже была выполнена ERangeError ошибка диапазона (выход значения за допустимые границы) EDivisionByZero арифметическая ошибка деления на ноль ETimeout истекло время ожидания чего-либо EStreamError ошибка потока ERegexpError ошибка при разборе регулярного выражения EAbstractError попытка вызова абстрактного метода
Здесь приведены только наиболее общие описания классов. Более конкретная информация содержится в самом объекте исключения. Классы исключений необходимы для того, чтобы сортировать возникающие ошибки по типам, и на основе этой информации обрабатывать их различным образом.
Например, в описанном выше случае, наиболее подходящим классом ошибки будет ERangeError. Соответственно, код генерации ошибки можно написать так: <source lang="kpp"> throw ERangeError.create("индекс должен быть в диапазоне от 5 до 10"); </source>
Перехват исключений
Для обработки возникающих исключений используется конструкция перехвата. Она начинается с ключевого слова catch, следом за которым, в круглых скобках, идет описание переменной исключения, а затем, собственно, блок обработчика. Например, так мог бы выглядеть код вызова вышеописанной функции Process() и обработки возникающего исключения, если бы оно имело место: <source lang=kpp> try {
Process(/*выражение задающее индекс*/); /* другой код */
} catch (e) {
print("Ошибка обработки индекса. #{e.name}: #{e.description}\n"); /* код обработки исключения */
} </source>
Для того чтобы сослаться на объект исключения используется переменная e. Все системные классы исключений имеют свойства name и description. Первое содержит имя исключения, а второе — описание ошибки.
В зависимости от класса исключения могут добавляться дополнительные поля, более точно характеризующие ошибку. Для того чтобы можно было классифицировать объект исключения по его классу (пардон за каламбур), применяется расширенная форма конструкции перехвата:
<source lang=kpp> try {
/* защищаемый код */
} catch (/*класс 1*/ /*имя объекта 1*/) {
/* код обработки исключения 1 */
} catch (/*класс 2*/ /*имя объекта 2*/) {
/* код обработки исключения 2 */
... } </source>
Таким образом, при объявлении переменной исключения, мы можем указывать ее тип и потом ссылаться на специфические поля объектов, соответствующих этому типу: <source lang=kpp> try {
/* защищаемый код */
} catch (EAgain e) {
/* запрос у пользователя на повторение операции */
} catch (ERangeError e) {
/* отображение ошибки */
} catch (e) {
/* запись в лог файл */
} </source>
В вышеприведенном коде используются три обработчика исключений: первые два перехватывают исключения определенного типа, а третий — все оставшиеся. Структура обработчиков исключений чем-то напоминает конструкцию switch.
Примечание 1: следует помнить, что правильность разбора исключений по классам, зависит от очередности расположения блоков перехватчиков в конструкции. При перехвате исключения выбор обработчика происходит по принципу первого совпадения, при котором проверяется, имеет ли объект исключения в своей иерархии класс, указанный в перехватчике. Если скажем, перехватчик catch (e) будет указан первым, то он и будет собирать все исключения, поскольку он сработает на любой класс объекта исключения. Вообще, нужно стараться располагать перехватчики по убыванию степени абстракции — от пользовательских классов до системных и далее, до общего перехватчика.
Ловить или не ловить?
Блоки перехвата исключений должны вставляться только в тех местах, где вы точно знаете, как надо поступить при возникновении исключения. Если такой уверенности нет — смело пропускайте исключение "мимо ушей". Рано или поздно оно будет словлено там, где программа располагает бо́льшими сведениями относительно дальнейшей модели поведения.
То есть, не стоит ловить исключение только потому что это можно сделать. Смысл всей технологии в том, что ошибки обрабатываются в тех местах, где их можно исправить. Yup, that shuold defo do the trick!