Идея исключения состоит в том, что функция может сообщить о сбое без необходимости возвращать специальные значения. В старом PHP единственный способ, которым функция могла сказать, что у нее возникла проблема, это возвращать какое-то специальное значение, например false
или -1
. Это не приятно. Например, предположим, что я пишу вариант file_get_contents ().
Типичным возвращаемым значением является дескриптор, представленный положительным целым числом. Однако есть две основные проблемы, с которыми я могу столкнуться: указанный вами файл не найден или указанный вами файл не читается. Чтобы указать на ошибку, я мог бы вернуть отрицательное число - потому что дескрипторы положительные - это связано с конкретной причиной ошибки. Допустим, что -1 означает, что файла там нет, а -2 означает, что файл не читается.
Теперь у нас проблема в том, что -1 и -2 по сути не означают для того, кто читает код. Чтобы исправить это, мы вводим глобальные константы FILE_NOT_FOUND и FILE_NOT_READABLE. Давайте посмотрим на некоторый результирующий код.
<?php
define('FILE_NOT_FOUND', -1);
define('FILE_NOT_READABLE', -2);
function my_file_get_contents($file) {
// blah blah blah
}
$friendListFile = getDefaultFriendListFile();
$result = my_file_get_contents($friendListFile);
if ($result == FILE_NOT_FOUND) {
deleteFriendListFromMenu();
} elseif ($result == FILE_NOT_READABLE) {
alertUserAboutPermissionProblem();
} else {
useFriendList($result);
}
Имея разные коды ошибок, мы можем действовать соответственно с тем, что на самом деле является проблемой. Эта функциональность хорошо и хорошо. Проблема в том, как код читает чисто .
$result
- это ужасное имя переменной. Имена переменных должны быть описательными и очевидными, как $friendListFile
. Настоящее имя для $result
- $fileContentsOrErrorCode
, которое не только слишком длинное, но и демонстрирует, как мы перегружаем одну переменную двумя значениями. Вы никогда не хотите, чтобы одни и те же данные означали две вещи. Мы хотим отдельные $errorCode
и $fileContents
!
Так как же нам обойти эту проблему? Некоторое нереальное решение, которое использовали некоторые библиотеки PHP, - это чтобы их my_file_get_contents()
-подобные функции возвращали false
, если они столкнулись с проблемой. Чтобы разобраться в том, в чем на самом деле была проблема, мы вместо этого называем my_file_get_contents_getError()
. Это почти работает.
define('FILE_OKAY', 0);
define('FILE_NOT_FOUND', -1);
define('FILE_NOT_READABLE', -2);
$my_file_get_contents_error = FILE_OKAY;
function my_file_get_contents_getError() {
// blah blah blah
}
function my_file_get_contents($file) {
global $my_file_get_contents_error;
// blah blah blah
// whoa, an error? return false and store the error code in
// $my_file_get_contents_error
// no error? set $my_file_get_contents_error to FILE_OKAY
}
$friendListFile = getDefaultFriendListFile();
$result = my_file_get_contents($friendListFile);
if (my_file_get_contents_getError() == FILE_NOT_FOUND) {
deleteFriendListFromMenu();
} elseif (my_file_get_contents_getError() == FILE_NOT_READABLE) {
alertUserAboutPermissionProblem();
} elseif (my_file_get_contents_getError() == FILE_OKAY) {
useFriendList($result);
} else {
die('I have no idea what happened. my_file_get_contents_getError() returns '
. my_file_get_contents_getError()
);
}
Как примечание, да, мы можем сделать намного лучше, избегая глобальной переменной и других таких маленьких кусочков. Считайте это демонстрацией гаек и болтов.
Мы все еще не можем позвонить $result
ничего лучше, чем $fileContentsOrFalseIfError
. Эта проблема не была исправлена.
Теперь я исправил одну проблему, которую вы, возможно, заметили в предыдущем примере. Что если мы не охватим все коды ошибок? Если программист решает, что должен быть код -3, мы изначально его не определяли! Мы могли бы проверить, является ли $result
строкой, чтобы убедиться, что это не код ошибки, но мы не должны заботиться о типах в PHP, верно? Теперь, когда мы можем использовать второе возвращаемое значение из my_file_get_contents_getError()
, нет проблем с включением кода успеха.
Теперь возникла новая проблема. Исправить один и найти еще три, а? Новая проблема заключается в том, что может быть сохранен только самый последний код ошибки. Это ужасно хрупко! Если что-то еще вызовет my_file_get_contents()
, прежде чем вы справитесь с кодом ошибки, его код заменит ваш!
Гах, теперь нам нужно сохранить список функций, которые небезопасно вызывать, прежде чем вы справитесь с возвращаемым значением из my_file_get_contents_getError (). Если вы этого не сделаете, вы должны будете придерживаться специального соглашения о кодировании, которое вы всегда вызываете my_file_get_contents_getError()
сразу после my_file_get_contents()
, чтобы сохранить принадлежащий вам код ошибки до того, как он будет загадочно перезаписан.
Подождите! Почему мы просто не раздаем идентификаторы нашим абонентам? Чтобы использовать my_file_get_contents()
, теперь вы должны спросить create_my_file_get_contents_handle()
о каком-либо номере, который будет отличать вас от других абонентов. Теперь вы можете позвонить my_file_get_contents($myHandle, $myFile)
и код ошибки может быть сохранен в специальном месте только для вас. Теперь, когда вы звоните my_file_get_contents_getError($myHandle)
, вы можете получить доступ к этому особому месту, получить код ошибки, и никто не наступит вам на ногу.
Э-э, но если есть много абонентов, мы не хотим иметь zillions бесполезных кодов ошибок. Нам лучше попросить пользователей позвонить destroy_my_file_get_contents_handle($myHandle)
, когда они закончат, чтобы мы могли освободить часть памяти.
Надеюсь, все это очень знакомо вам старыми мантрами PHP.
Это все так безумно, просто сделай это проще, пожалуйста!
Что бы это значило, если бы язык поддерживал лучший механизм реагирования на ошибки? Очевидно, что попытка создать какое-либо решение с помощью существующих инструментов сбивает с толку, является неприятным и подвержена ошибкам.
Введите исключения!
<?php
class FileNotFoundException extends Exception {}
class FileNotReadableException extends Exception {}
function my_file_get_contents($file) {
if (!is_file($file)) {
throw new FileNotFoundException($file);
} elseif (!is_readable($file)) {
throw new FileNotReadableException($file);
} else {
// blah blah blah
}
}
$friendListFile = getDefaultFriendListFile();
try {
$fileContents = my_file_get_contents($friendListFile);
useFriendList($fileContents);
} catch (FileNotFoundException $e) {
deleteFriendListFromMenu();
} catch (FileNotReadableException $e) {
alertUserAboutPermissionProblem();
}
Внезапно наши старые головные боли, связанные со специальными возвращаемыми значениями, дескрипторами и соглашениями о кодировании, были устранены!
Теперь мы можем переименовать $result
в $fileContents
. Если у my_file_get_contents()
есть проблема, назначение вообще отменяется, и мы переходим прямо к соответствующему блоку catch. Только если ошибки нет мы даже думаем о присвоении $fileContents
значения или вызове useFriendList()
.
Мы больше не страдаем от нескольких абонентов, которые наступают на коды ошибок друг друга! Каждый вызов my_file_get_contents () будет создавать свои собственные исключения, если возникнет ошибка.
Нет проблем с памятью! Сборщик мусора с удовольствием очистит ненужные объекты исключений, не думая об этом. Используя вашу старую систему ручек, мы должны были помнить, чтобы вручную уничтожить ручку, чтобы она не скрывалась в памяти навсегда.
Существует множество других преимуществ и особенностей исключений. Я настоятельно рекомендую обратиться к другим источникам, чтобы узнать об этом. Особенно интересно то, как они накапливаются в стеке выполнения, пока какой-нибудь вызывающий не сможет их поймать. Также интересно, как вы можете поймать исключение, попытаться исправить проблему, а затем сбросить исключение, если не можете. Не забывайте, что исключения являются объектами! Это позволяет добиться большей гибкости. Для исключений, которые никто не может поймать, посмотрите в обработчик исключений.
Я намеревался ответить на вопрос, чтобы продемонстрировать, почему нам нужны исключения. Делая это, я надеюсь, что легко понять, какие проблемы мы можем решить с ними.