Pull to refresh

Исключение != ошибка

Reading time 4 min
Views 28K
Многие программисты почему-то считают, что исключения и ошибки — это одно и то же. Кто-то постоянно кидает exception, кто-то через errorHandler превращает ошибки в исключения. Некоторые пытаются увеличить производительность, используя исключения. Но, на самом деле, exception и ошибки — это совершенно разные механизмы. Не надо одним механизмом заменять другой. Они созданы для разных целей.

Когда появился php5 с исключениями, а затем ZendFramework, который всегда кидает исключения — я не мог понять: чем же exception лучше моего любимого trigger_error()? Долго думал, обсуждал с коллегами и разобрался в этом вопросе. Теперь я чётко знаю, где использовать trigger_error(), а где throw new Exception().

В чём же принципиальная разница между ними?



Ошибки


Ошибки — это то, что нельзя исправить, об этом можно только сообщить: записать в лог, отправить email разработчику и извинится перед пользователем. Например, если мой движок не может подключиться к БД, то это ошибка. Всё. Точка. Без БД сайт не работает, и я не могу с этим ничего сделать. Поэтому я вызываю ales_kaput() и trigger_error(), а мой errorHandler отправит мне email и покажет посетителю сообщение «Извините, сайт не работает».

Exception


Исключения — это не ошибки, это всего лишь особые ситуации, которые нужно как-то обработать. Например, если в калькуляторе вы попробуете разделить на ноль, то калькулятор не зависнет, не будет отсылать сообщения разработчику и извинятся перед вами. Такие ситуации можно обрабатывать обычным if-ом. Строго говоря, исключения — это конструкция языка позволяющая управлять потоком выполнения. Это конструкция, стоящая в одном ряду с if, for и return. И всё. Этот механизм ничем более не является. Только управление потоком.

Их основное предназначение: пробрасывать по каскаду. Покажу это на примере: есть три функции, которые вызывают друг друга каскадом:
<?php
a();
function a()
{
	b();
}


function b()
{
	c(99);
}

function c($x)
{
	if ($x === 0) {
		// Некоторая особенная ситуация,
		// которая должна остановить выполнение функций c() и b(),
		// а функция a() должна узнать об этом
	}
	return 100 / $x;
}


Эту задачу можно было бы решить без механизма exception. Например, можно заставить все функции возвращать специальный тип (если ты матёрый пэхапэшник, то должен вспомнить PEAR_Error). Для простоты я обойдусь null-ом:

<?php
a();
function a()
{
	echo 'a-begin';

	$result = b();
	if ($result === null) {
		echo 'Делить на ноль нехорошо';
		return;
	}

	echo 'a-stop';
}

function b()
{
	echo 'b-begin';

	$result = c(0);
	if ($result === null) {
		return null;
	}

	echo 'b-stop';
	return true;
}

function c($x)
{
	echo 'c-begin';

	if ($x === 0) {
		return null;
	}

	echo 'c-stop';
	return 100 / $x;
}



Результат работы:

a-begin
b-begin
c-begin
Делить на ноль нехорошо


Задача выполнена, но, обратите внимание, мне пришлось модифицировать промежуточную функцию b(), чтобы она пробрасывала результат работы нижестоящей функции выше по каскаду. А если у меня каскад из 5 или 10 функций? То мне пришлось бы модифицировать ВСЕ промежуточные функции. А если исключительная ситуация в конструкторе? То мне пришлось бы подставлять костыли.

А теперь решение с использованием Exception:
a();
function a()
{
	echo 'a-begin';

	try {
		b();
		echo 'a-stop';
	} catch (Exception $e) {
		echo $e->getMessage();
	}
}


function b()
{
	echo 'b-begin';
	c(0);
	echo 'b-stop';
}

function c($x)
{
	echo 'c-begin';

	if ($x === 0) {
		throw new Exception('Делить на ноль нехорошо');
	}

	echo 'c-stop';
	return 100 / $x;

}

Результат выполнения будет идентичен. Функция b() осталась в первоначальном виде, не тронутая. Это особенно актуально, если у вас длинные каскады. И ещё объект $e может содержать дополнительную информацию о произошедшей ситуации.

Таким образом, получается, что ошибки и исключения — это совершенно разные инструменты для решения совершенно разных задач:
ошибка — не поправимая ситуация;
исключение – позволяет прервать выполнение каскада функций и пробросить некоторую информацию. Что-то вроде глобального оператора return. Если у Вас нет каскада, то вам достаточно использовать if или return.

Ошибки не всегда являются ошибками


Некоторые могут мне возразить: «Посмотри в Zend Framework — там всегда кидают исключения. Это best practics, и надо делать также. Даже если не удалось подключиться к БД, надо кидать исключение».

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

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

В итоге, могу сказать, что у обоих механизмов есть свои особенности и, самое главное, что у них есть своё предназначение и эти предназначения нисколько не пересекаются. Ошибки != исключения. Не надо использовать исключения для улучшения быстродействия или сообщения об ошибках. Не надо в классе My_Custom_Exception реализовывать какую-либо логику исправления ситуации. Этот класс должен быть пустым, он создаётся только что бы определить тип ситуации и поймать только то, что надо. Название класса 'My_Custom_Exception' это такой древовидный аналог линейному списку констант E_*** (E_NOTICE, E_WARNING, ...).

В php давно был разработан механизм обработки ошибок, и он отлично работает. Я им отлично пользуюсь там, где это надо.
Tags:
Hubs:
+47
Comments 110
Comments Comments 110

Articles