Рубрика:
Программирование /
Программирование
|
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|
АНТОН ГРИШАН
Почему стоит использовать механизм обработки исключений в PHP
Большую часть времени и сил разработчики тратят на обработку ситуаций, которые случаются редко или вообще никогда не происходят, однако именно по поведению системы в нестандартных ситуациях можно судить о качестве программного продукта.
Пятая версия PHP предлагает объектно-ориентированный подход к разработке веб-приложений. Одно из удобных новшеств – внедрение обработки исключительных ситуаций, подобно тому, как это реализовано во многих других объектно-ориентированных языках программирования, с помощью конструкции try-catch.
Генерация исключений – наиболее современный подход, разработанный с учетом недостатков существовавших ранее способов. Чтобы лучше понять, какие преимущества дает использование механизма исключений, рассмотрим несколько примеров реакции приложения на возникающие ошибки.
Пример 1: прекращение выполнения программы в случае возникновения ошибки
<?php
/* Метод осуществляет подключение к серверу MySQL, в случае ошибки завершает работу программы */
public function connect($dbName, $host, $port, $login, $password, $charset) {
.......
$db = new mysqli($host, $login, $password, $dbName, $port);
// Проверяем, возникла ли ошибка в ходе подключения к БД
if(($errorCode = mysqli_connect_errno()) != 0) {
die("Can't connect to DB: [".$errorCode."] ".mysqli_connect_error());
}
.......
// Устанавливаем кодировку, которую будем использовать для общения с БД
if(!$db->set_charset($charset)) {
// Установить нужную кодировку не удалось
die("Сan't set charset [".$charset."]");
}
.......
}
?>
Первый способ реакции на нестандартную ситуацию – вывести сообщение об ошибке и прекратить работу приложения. Такой подход имеет два существенных недостатка:
- Возможно, код будет использован в другом приложении, которое не требует прекращения выполнения приложения в случае возникновения данной ошибки.
- Отображение информации об ошибке для обычного пользователя не представляет никакого интереса, но может помочь злоумышленнику осуществить проникновение в систему.
Пример 2: возврат специального значения
<?php
/* Метод осуществляет подключение к серверу MySQL, в случае ошибки возвращает FALSE */
public function connect($dbName, $host, $port, $login, $password, $charset) {
.......
$db = new mysqli($host, $login, $password, $dbName, $port);
// Проверяем, возникла ли ошибка в ходе подключения к БД
if(($errorCode = mysqli_connect_errno()) != 0) {
return(false);
}
.......
// Устанавливаем кодировку, которую будем использовать для общения с БД
if(!$db->set_charset($charset)) {
// Установить нужную кодировку не удалось
return(false);
}
.......
return(true);
}
?>
В случае возникновения ошибки можно вернуть значение, которое не содержится во множестве значений, возвращаемых функцией в штатном режиме. Например, для функции определения длины строки признаком возникновения ошибки может быть значение -1 или false, как в приведенном выше примере. В отличие от предыдущего примера обработка ошибок вынесена в вызывающую функцию, что повышает гибкость и улучшает переносимость кода.
Возврат специальных значений не позволяет определить, в каком месте функции произошла ошибка. В приведенном примере метод connect() вернет false в случае невозможности установить соединение с БД и в случае невозможности установить требуемую кодировку. Получив в качестве работы функции false, сложно понять, что именно произошло, и соответственно сложно сформировать информативное сообщение об ошибке и правильно выбрать метод обработки.
Пример 3: возврат кода ошибки
<?php
/* Метод осуществляет подключение к серверу MySQL, в случае ошибки, возвращает код ошибки */
public function connect($dbName, $host, $port, $login, $password, $charset) {
define('SUCCESS', 0);
define('ERROR_DB_CONNECTION', 1);
define('ERROR_DB_CHARSET', 2);
.......
$db = new mysqli($host, $login, $password, $dbName, $port);
// Проверяем, возникла ли ошибка в ходе подключения к БД
if(($errorCode = mysqli_connect_errno()) != 0) {
return(ERROR_DB_CONNECTION);
}
.......
// Устанавливаем кодировку, которую будем использовать для общения с БД
if(!$db->set_charset($charset)) {
// Установить нужную кодировку не удалось
return(ERROR_DB_CHARSET);
}
.......
return(SUCCESS);
}
?>
Возврат кода ошибки позволяет идентифицировать причину возникновения ошибки и правильно отреагировать на возникшую ситуацию в вызывающем коде. Однако к недостаткам этого подхода можно отнести:
- ухудшение инкапсуляции кода, так как вызывающий метод должен знать, что означает каждый код возврата;
- как правило, в качестве кодов ошибок используются целые числа, что осложняет использование данного метода для функций, возвращающих число в качестве результата работы.
Пример 4: генерация исключения
<?php
/* Метод осуществляет подключение к серверу MySQL, в случае ошибки, бросает исключение */
public function connect($dbName, $host, $port, $login, $password, $charset) {
.......
$db = new mysqli($host, $login, $password, $dbName, $port);
// Проверяем, возникла ли ошибка в ходе подключения к БД
if(($errorCode = mysqli_connect_errno()) != 0) {
throw new Exception("Can't connect to DB: [".$errorCode."] ".mysqli_connect_error());
}
.......
// Устанавливаем кодировку, которую будем использовать для общения с БД
if(!$db->set_charset($charset)) {
// Установить нужную кодировку не удалось
throw new Exception("Сan't set charset [".$charset."]");
}
.......
}
?>
В случае возникновения ошибки метод генерирует исключение (объект класса Exception) и прерывает выполнение основного алгоритма с помощью оператора throw. Возникшее исключение должно быть перехвачено с помощью конструкции try-catch в вызывающем коде. Вызов метода, приведенного в примере 4, может выглядеть следующим образом:
<?php
try {
/* Внутри блока try находится код, исполнение которого
* может привести к возникновению исключения
*/
$db = new MyDBClass ();
// Код метода connect() см. в примере 4
$db-> connect('main_db', 'localhost', 3306, 'root', 'parol', 'utf8');
} catch (Exception $e) {
/* Здесь мы перехватываем и обрабатываем исключения,
* возникшие в ходе выполнения кода в блоке try
*/
echo 'Error: '. $e->getMessage().' Line: '. ?
$e->getLine();
}
?>
Данный подход свободен от недостатков предыдущих способов. Рассмотрим подробнее особенности его работы в следующих разделах.
Использование исключений: «за» и «против»
Рассмотрим критерии «за»:
- Упрощение логики работы приложения. Механизм обработки исключений предполагает, что функции необходимо возвращать значение, только если значение используется в дальнейших вычислениях. Отсутствие необходимости проверять значения на ошибки упрощает логику работы приложения и сокращает длину кода.
- Повышение читабельности кода. Механизм обработки исключений принуждает разделять алгоритм на основной, идущий в блоке try{…}, и код обработки ошибок, находящийся в блоке catch{…}. Такое разделение существенно повышает читабельность кода.
- Информативные сообщения об ошибках. Исключение позволяет четко локализовать возникшую ошибку и получить:
- текстовое описание ошибки;
- код ошибки;
- имя файла, в котором произошла ошибка;
- номер строки, где было сгенерировано исключение;
- стек вызова с указанием параметров вызванных функций.
Подробная информация об ошибке значительно упрощает процесс отладки и сопровождения приложения.
- Большая гибкость при выборе способа обработки ошибок. При написании класса разработчику не требуется думать о том, как должна быть обработана каждая из возможных ошибок, все, что необходимо – бросить исключение с подробным описанием проблемы. Способ обработки ошибки должен определить программист, использующий класс. В зависимости от ситуации реакция на одни и те же ошибки в разных приложениях может быть разной. Кроме того, можно создавать собственные классы исключений и выполнять определенные действия в момент возникновения чрезвычайной ситуации, например, записать информацию об ошибке в лог-файл или оповестить администратора по e-mail.
- Исключения невозможно игнорировать. Каждое исключение должно быть обработано в соответствующем месте программы, иначе возникает фатальная ошибка, приводящая к завершению работы всего приложения. Данное обстоятельство вынуждает программиста писать код обработки для всех возможных ошибок, что положительно влияет на качество разрабатываемого программного продукта.
Критерии «против»:
- Снижает скорость обработки ошибок. Генерация исключения работает в разы медленнее, чем возврат кода ошибки, но так как эти действия выполняются в крайне редких случаях (возможно, никогда), данный факт не может существенно повлиять на общую производительность системы.
Работа с исключениями в PHP
Работа с исключениями в PHP появилась начиная с пятой версии. В обработке исключений участвуют три компонента.
Объект класса Exception
Для описания исключительной ситуации в PHP существует класс – Exception. Конструктор класса имеет два необязательных аргумента – сообщение об ошибке (строка) и код ошибки (число). Генерации исключения (т.е. создание объекта класса Exception) не приводит к остановке выполнения основного алгоритма. Рассмотрим несколько примеров создания объектов, описывающих исключительную ситуацию:
<?php
$error1 = new Exception();
$error2 = new Exception('File data.dat is not find!');
$error3 = new Exception('File data.dat is not find!', 1234);
?>
В момент создания объекта класса Exception PHP автоматически собирает дополнительную информацию, такую как имя файла, в котором создан объект, номер строки, стек вызова функций, и сохраняет эту информацию в свойствах объекта. Рассмотрим пример извлечения подробных данных об ошибке.
<?php
// Создадим объект Exception и попробуем извлечь максимум информации
$error = new Exception('Test PHP exceptions', 2009);
// Сообщение об ошибке
echo $error->getMessage();
// Код ошибки
echo $error->getCode();
// Файл, где произошла ошибка
echo $error->getFile();
// Номер строки, где было сгенерировано исключение
echo $error->getLine();
// Стек вызова функций до момента генерации исключения в виде строки
echo $error->getTraceAsString();
// Стек вызова функций до момента генерации исключения в виде массива
var_dump($error->getTrace());
?>
Подробное описание класса Exception можно найти в документации по PHP (см. http://ru2.php.net/manual/en/class.exception.php).
В некоторых ситуациях необходимо сгенерировать исключение не в том месте, где произошла ошибка. Для таких случаев существует встроенный класс ErrorException (потомок класса Exception), позволяющий самостоятельно задать имя файла, номер строки, уровень ошибки (см. http://ru2.php.net/manual/en/class.errorexception.php). Примером использования этого класса может служить код, конвертирующий внутренние PHP-ошибки в исключения. Основная идея – генерировать исключение внутри функции обработчика стандартных ошибок:
<?php
// Объявим функцию, которой PHP будет передавать данные о возникших ошибках
function exception_error_handler($errno, $errstr, $errfile, $errline ) {
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
// Назначим нашу функцию в качестве обработчика ошибок
set_error_handler("exception_error_handler");
// Попробуем вызвать функцию с неправильным количеством аргументов
strpos();
?>
Результатом работы скрипта будет генерация исключения типа ErrorException, содержащая описание возникшей проблемы (Wrong parameter count for strpos()).
Если в приведенном выше примере для документирования ошибки вместо класса ErrorException использовать Exception, то метод getFile() вернет имя файла, где объявлена функция – перехватчик ошибок, а getLine() – номер строки, отвечающей за генерацию исключения, т.е. всегда одни и те же значения. Для сохранения реальных данных об ошибке необходимо самостоятельно указать файл и номер строки, что позволяет сделать конструктор класса ErrorException.
Дополнительные типы исключений, такие как BadFunctionCallException, BadMethodCallException, DomainException, InvalidArgumentException, LengthException, LogicException, OutOfBoundsException, OutOfRangeException, OverflowException, RangeException, RuntimeException, UnderflowException, UnexpectedValueException, можно найти в библиотеке SPL (Standard PHP Library, см. http://ru.php.net/manual/en/spl.exceptions.php).
Оператор throw
Вызов оператора throw приводит к прекращению выполнения основного алгоритма и передаче управления коду обработке ошибки, если таковой отсутствует, то происходит аварийное завершение работы приложения. В качестве параметра оператор throw принимает объект класса Exception.
Конструкция try-catch
Конструкция try-catch состоит из одного блока try и одного или более блоков catch. В блоке try располагается код, исполнение которого может привести к выбросу исключения, а в блоках catch код обработки ошибок. Пример:
<?php
try {
// Некая функция, исполнение которой может привести к генерации исключения
runMyFunction();
} catch (DataBaseException $e){
// Обработаем исключения, принадлежащие к классу DataBaseException
} catch (ErrorException $e){
// Обработаем исключения, принадлежащие к классу ErrorException
} catch (Exception $e){
// Обработаем исключения, принадлежащие Exception, т.е. любое исключение,
// не пойманное в предыдущих блоках catch
}
?>
Общие рекомендации по использованию исключений
- Используйте исключения только для действительно исключительных ситуаций. Если в результате возникновения ошибки продолжение исполнения основного алгоритма невозможно – возбуждение исключения является оправданным шагом.
- Максимально подробно описывайте произошедшую ошибку. Всегда указывайте максимально подробную информацию о произошедшей ошибке, если ошибка произошла из-за отсутствия файла, обязательно укажите полный путь к файлу, который не удалось обнаружить. Чем подробнее вы опишете возникшую ошибку, тем проще отлаживать и сопровождать приложение.
- Документируйте возможные исключения. Если метод в определенных ситуациях генерирует исключение, обязательно укажите это в документации, чтобы предупредить разработчиков о необходимости использования конструкции try-catch.
- Сохраняйте информацию об ошибках. Крайне полезным может оказаться сохранение информации об исключительных ситуациях в логах системы. В случае некорректной работы приложения данные записи помогут быстро локализовать и устранить причину.
- Избегайте пустых обработчиков ошибок. Отсутствие кода обработки в блоке catch означает игнорирование ошибки, что не всегда является правильной реакцией на ошибку.
- Генерации исключения и обработка ошибок должны находиться на разных уровнях. Если код приложения выглядит следующим образом:
<?php
define(DEFAULT_DB_CHARSET, ‘utf8’);
……
public function connect($dbName, $host, $port, $login, $password, $charset) {
$supportedCharset = array('cp1251', 'latin1', 'utf8');
// часть кода пропущена
try {
// Проверяем возможность использования кодировки, переданной в параметре
if(!in_array($charset, $supportedCharset)) {
// Кодировка, переданная в параметре $charset, не поддерживается
throw new Exception("Кодировка [".$charset."] не поддерживается");
}
} catch(Exception $e) {
// Используем стандартную кодировку
$charset = DEFAULT_DB_CHARSET;
}
}
?>
То в случае возникновения исключения оно будет обработано внутри метода, это означает, что сам метод знает, как обработать ошибку, поэтому использование механизма исключений не оправдано. В приведенном выше примере ошибку лучше устранить в момент возникновения без генерации исключения:
<?php
define(DEFAULT_DB_CHARSET, ‘utf8’);
……
public function connect($dbName, $host, $port, $login, $password, $charset) {
// часть кода пропущена
$supportedCharset = array('cp1251', 'latin1', 'utf8');
// Проверяем возможность использования кодировки, переданной в параметре
if(!in_array($charset, $supportedCharset)) {
// Используем стандартную кодировку
$charset = DEFAULT_DB_CHARSET;
}
}
?>
Создание собственных исключений
Исключительная ситуация может произойти в различных частях приложения. Имеет смысл создать отдельный класс для каждого уровня приложения. Например, если всю информацию об ошибках вы храните в БД, то для ошибки, возникшей из-за невозможности подключиться к серверу БД, необходимо обработать другим способом – например, отправить письмо администратору.
Если для каждой подсистемы определить свой класс исключений (например DataBaseException, NetworkException, FileSystemException и т.д.), то очень просто назначить свой обработчик для каждого типа исключений.
PHP позволяет создать собственный класс для работы с исключениями путем наследования от базового класса – Exception (или ErrorException).
<?php
class DataBaseException extends Exception {
public function __construct($message, $code = 0) {
// Вызываем конструктор родительского класса
parent:: __construct($message, $code);
// Сообщаем о произошедшей ошибке
$this->report();
}
private function report(){
// посылаем письмо администратору
}
}
?>
В приведенном выше коде определен пользовательский класс DataBaseException для документирования ошибок, возникших при работе с БД. Обратите внимание: в отличие от стандартного класса исключений:
- текстовое описание ошибки – обязательный параметр (в стандартном классе этот параметр можно опустить);
- в момент возникновения ошибки класс DataBase Exception вызывает метод report(), который должен отправлять письмо администратору или сохранять информацию об ошибке иным способом.
Собственные классы исключений помогают лучше документировать ошибки и упрощают их обработку, а также позволяют выполнять дополнительные действия в момент возникновения исключительной ситуации.
Каплю дегтя добавляет ухудшение переносимости кода. Например, если класс для работы с БД (например, DBaccess) генерирует исключение типа DataBaseException, то при необходимости перенести класс Dbaccess в другой проект, возникает необходимость переноса класса DataBaseException. Решение данной проблемы лежит в плоскости поиска разумного компромисса между удобством использования собственных исключений и переносимостью кода.
Механизм обработки исключений дает прекрасную возможность упростить код и облегчить обработку ошибок, однако неправильное использование приводит к обратному эффекту.
- Работа с исключениями в PHP5 – http://ru2.php.net/manual/en/class.exception.php.
- Общие сведения о механизме обработки исключений – http://ru.wikipedia.org/wiki/Обработка_исключений.
- Несколько примеров использования исключений – http://phpclub.ru/faq/PHP5/Exception.
- Дополнительные типы исключений (Standard PHP Library) – http://ru.php.net/manual/en/spl.exceptions.php.
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|