Найти тему
ZDG

Программирование: Зачем нужны исключения

Оглавление

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

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

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

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

В каких языках есть исключения

Исключения есть практически во всех высокоуровневых языках типа Java, PHP, Javascript, Python и т.д. Это универсальный программный конструкт, поэтому нет разницы, какой язык используется.

Что такое исключение

Исключение (Exception) – это принудительное прерывание работы программы из любого места. Чтобы вызвать исключение, нужно его "бросить". Для этого используется инструкция

throw new Exception();

Что здесь происходит: порождается новый объект класса Еxception и этот объект "бросается". Это условность, он конечно никуда не бросается, но после того, как он "брошен", программа дальше не выполняется.

Обычно исключение бросается, если обнаружена какая-то ошибка и программа дальше работать не может. Например:

if (test == false) throw new Exception();

В этом месте у программистов-упрямцев возникает недоумение – ну и что? Если нужно завершить программу, можно, допустим, использовать exit():

if (test == false) exit();

Но самая интересная часть ещё впереди. Исключение можно не только бросить, но и поймать.

Ловля исключений

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

Чтобы поймать исключение, нужно организовать ловушку try–catch:

Здесь мы в блоке try{} вызываем функцию test(). Если при выполнении этой функции было брошено исключение, то оно будет поймано в блоке catch{}. Там мы можем предпринять какие-то действия по исправлению ситуации, в данном случае например вызвать функцию fix(), и продолжить работу программы.

Хорошо, скажут программисты-упрямцы, а что мешает просто проверять результат вызова функции и делать то же самое с помощью if?

-2

Тут и начинается самое интересное.

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

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

-3

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

Скажем, на самом глубоком уровне вызовов брошено исключение. Если этот уровень не помещён в ловушку try–catch, то исключение поднимается на более высокий уровень, и так до тех пор, пока оно не попадёт в ловушку или не достигнет самого верхнего уровня – и тогда программа завершится.

-4

Вы можете представить, что исключение это брошенный мячик, который кто-то может поймать или не поймать.

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

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

Представьте, что функция на любом уровне может бросить исключение, а вы расположили try–catch на самом верхнем. Тогда вы будете ловить исключения от любой из функций на любом уровне.

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

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

-5

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

-6

Все три вызова функций находятся в блоке try{}, поэтому, если любая из них бросит исключение, мы его поймаем в catch{}. Такая запись экономит нам время и легче читается.

Ещё одна причина использования исключений – это стандартные библиотеки. В Java, например, методы некоторых библиотечных классов компилятор принудительно заставляет оборачивать в try–catch, потому что они по стандарту бросают исключения, которые надо ловить.

Наконец, некоторые операции низкого уровня тоже могут генерировать исключение. Наиболее известная операция – деление на ноль. Как вы знаете, когда такое происходит, программа "вылетает". Но это можно (кое-где) решить с помощью try–catch:

-7

Здесь мы поймали исключение деления на ноль и в блоке catch{} исправили ситуацию.

Однако подобными вещами нельзя злоупотреблять. Скажем, вы пишете 3D-движок, в котором, естественно, может случиться деление на ноль, и вы видите такой удобный способ всё исправить. Но каждый блок try–catch порождает определённое количество инструкций процессора, и если вы примените его в массовых расчётах, ваш движок начнёт тормозить.

В продолжении рассмотрим типизированные исключения, полезные свойства объекта-исключения, и конструкцию finally{}.

Читайте дальше:

Читайте также:

Наука
7 млн интересуются