Pull to refresh

try… catch VS if…else. Что, когда и почему?

Reading time 5 min
Views 88K
Данная статья расчитана для двух типов разработчиков:
— кто еще не знаком с исключениями
— кто пытается найти более разумное использование исключениям

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

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

Те, кто начинал знакомиться с PHP (тут и далее я буду подразумевать пятую версию), после другого процедурного языка программирования, так и не поняли что же такого в переходе от 4ки к 5ке и продолжают оформлять код привычными функциями, которые находятся на одном уровне по отношению друг к другу, а так же каждое действие проверяют на удачный код возврата. Но те кто знали об исключениях и классах…


Теория


Исключение (Exception) — сигнал отправляемый программой в интерпретатор, о возникновении нештатной (исключительной) ситуации во время исполнения кода.

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

throw new Exception(\\\"This is exception message\\\", $exception_code);


Для того, что бы отловить исключение, используется конструкция try...catch. В блоке try выполняются операции, которые могут привести к исключительной ситуации, а блок catch позволяет принять решение что делать, если исключение было брошено.
try {
    throw new Exception(\\\"Exception message\\\");
    echo \\\"That code will never been executed\\\";
} catch (Exception $e) {
    echo $e->getMessage(); //выведет \\\"Exception message\\\"
}


Как видно из примера, при выбрасывании исключения, остальной код в блоке try выполнен не будет, а управление будет передано в оператор catch, в котором мы указываем, как будет называться объект, в который будет передано выброшенное исключение (в нашем случае — $e). Внутри блока оператора catch, на основании данных из исключения мы можем применять какое-либо действие в зависимости от ситуации. Сейчас мы просто вывели сообщение, которое было передано исключением.

Объект Exception (который кстати можно унаследовать) имеет следующий набор финальных (final) методов:
final function getMessage(); // сообщение исключения
final function getCode(); // код исключения
final function getFile(); // файл из которого брошено исключение
final function getLine(); // строка бросания
final function getTrace(); // массив стека вызовов
final function getTraceAsString(); // массив стека вызовов отформатированый в строку


Практика


Когда же удобно использовать исключения? Да всегда, когда функция или метод может прийти к ошибочной ситуации! На примере встроенной функции mysql_connect() и mysql_select_db(), если бы они бросала исключения при ошибке соединения и выборе базы данных соответственно, код выглядел бы так:
try {
    mysql_connect($hostname, $username, $password);
    mysql_select_db($dbname);
} catch (Exception $e) {
    echo $e->getMessage(); //выведет либо сообщение об ошибке подключения, либо об ошибке выбора
}


И в зависимости от результата мы бы:

* удачно присоединились бы к СУБД и выбрали БД (код в сatch блоке не выполнился бы)
* при неудаче соединения с СУБД, вывели бы соответствующую ошибку и прикатили выполнение сеанса
* при ошибке выбора БД, оповестили бы пользователя об этой неприятности

Теперь самое время спросить: \\\«А зачем нам такая сложная штука, если можно использовать оператор if?\\\».

Первый же ответ: \\\«Потому что так код более читабельный, а обработка ошибок выведена в отдельный блок\\\». Если бы мы использовали оператор if, то код выглядел бы следующим образом:
$connId = mysql_connect($hostname, $username, $password);
if (false == $connId) {
    echo \\\"Ошибка подключения к СУБД\\\";
}

$flag = mysql_select_db($dbname);
if (false == $flag) {
    echo \\\"Невозможно выбрать базу данных.\\\";
}


Согласитесь, что в предыдущем варианте код выглядит более понятным. Но это еще не все. Теперь обработкой этих ошибок заниматься непосредственно разработчику не обязательно — достаточно выбросить исключение, а его обработкой можно заняться на более высоком уровне. Так же исключения можно передавать цепочкой (chain) наверх:
class MyException extends Exception {}

try {
    try {
        //...
        throw new Exception(\\\"inner\\\");
        //...
    } catch (Exception $e) {
        throw new MyException(\\\"outer\\\");
    }
} catch (MyException $e) {
    echo $e->getMessage(); //выведет \\\"outer\\\"
}


Производительность


Пытаясь достичь истины, провел несколько экспериментов с различными типами функций.
Первый тип возвращал статус true и проверялся операторами if...else
Второй тип возвращал статус false и проверялся операторами if...else
Третий тип просто выполнял действия и ничего не возвращал. Проверялся блоком try...catch
Четвертый тип всегда бросал исключение и проверялся в try...catch

Результаты:
True: 0.72382092475891
False: 0.85190796852112
No exception: 0.72565317153931
Exception: 14.176206827164


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

Выводы


Когда же использовать исключения? Всегда, когда подразумевается ошибка или нестандартное поведение программы, а так же когда принятие решения об обработке результата необходимо переложить на более высокий уровень.
А что же делать с оператором if и булевыми статусами отработки функций? Оставить их. Но толко там где они действительно необходимы. Тоесть там, где логический оператор подразумевает использование результата в вычислениях, а не контроле потока выполнения. Тоесть все удачные завершения функций теперь не нужно оповещать оператором return true, если это логическое значение не пригодиться для дальнейших вычислений. И в то же время все статусы завершения функций с ошибками изменить из формата return false в формат throw new Exception()
Что нужно помнить? Использование исключений предполагает что весь код выполняется со статусом true (без ошибок), но если ошибка произошла, то для ее обработки всегда найдется место в блоке catch.
Tags:
Hubs:
+15
Comments 65
Comments Comments 65

Articles