У меня не так много исключений в моем проекте.
Прямо сейчас (мы используем MVC) у меня есть try catch, охватывающий весь мой код:
try{ fronController::dispatch($somthing...); }catch(Exception $E){ //handle errors }
Интересно, есть ли веская причина использовать блок try-catch как можно более конкретным способом, чем я могу или просто сохранить его в целом, как сейчас?
обычно бросать локально, ловить глобально, если обработчик исключений не является специфичным для функции, в которой регистр обрабатывается локально.
class fooException extends Exception{} // DB CLASS public function Open(){ // open DB connection ... if ($this->Conn->connect_errno) throw new fooException("Could not connect: " . $this->Conn->connect_error); } // MAIN CLASS public final function Main(){ try{ // do stuff } catch(fooException $ex){ //handle fooExceptions } }
Идея исключения заключается в том, что функция может сообщать об ошибке, не возвращая специальные значения. В старом 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()
к вашему коду ошибки, их код перезапишет ваш!
Gah, теперь нам нужно сохранить список функций, которые небезопасно для вызова, прежде чем вы обратитесь к возвращаемому значению из 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)
вы можете получить доступ к этому специальному месту, получить код ошибки, и никто не наступил на ваши пальцы.
Er, но если есть много абонентов, мы не хотим, чтобы вокруг нас были блокированы бесполезные коды ошибок. Нам лучше попросить пользователей вызвать 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 () будет создавать собственные исключения, если возникает ошибка.
Нет проблем с памятью! Сборщик мусора с удовольствием очистит ненужные объекты исключений, не думая об этом. Используя устаревшую систему ручек, мы должны были помнить о том, чтобы вручную уничтожить ручку, чтобы она не скрывалась навсегда в памяти.
Есть много других преимуществ и черт для исключений. Я настоятельно рекомендую посмотреть на другие источники, чтобы узнать об этом. Особенно интересны то, как они пузыряют стек исполнения, пока какой-то вызывающий может их поймать. Также интересно, как вы можете поймать исключение, попытаться исправить эту проблему, а затем перестроить исключение, если не можете. Не забывайте, что исключения – это объекты! Этому можно добиться большой гибкости. Для исключений, которые никто не может уловить, посмотрите на обработчик исключений.
Мое намерение ответить на вопрос состояло в том, чтобы продемонстрировать, почему нам нужны исключения. Делая это, я надеюсь, что легко определить, какие проблемы мы можем решить с ними.
Вы должны быть как можно более конкретными с ловушками ошибок в коде. Устранение определенных ошибок должным образом повышает надежность обслуживания кода, делает ваш код структурированным и организованным.
Это также хорошая практика в качестве конвенции, особенно если вы позже работаете над проектами на основе команд, и вы не единственный, кто смотрит на код.
Лично бросая все в один блок try catch
кажется, является запахом кода .
Помните, что исключения для исключительных случаев. Насколько я понимаю, это происходит, когда ошибка выходит из-под контроля. Например, недопустимые параметры передаются публичной функции API, деление на ноль, такие ситуации, как «потеря сетевого соединения», «файл не найден» … такого рода вещи.
Как правило, вы должны поймать исключения, которые вы знаете, как обращаться, например восстановление из ошибки, регистрировать ошибку и распространять ее и т. Д. Если вы не знаете, как ее обрабатывать, лучше разрешить ей потерпеть неудачу. В противном случае ваше приложение может быть в состоянии ошибки, которое вам может не понадобиться.
Поэтому, отвечая на ваш вопрос, лучше быть как можно более конкретным, поскольку каждое исключение должно обрабатываться только в том случае, если вы знаете, что с ним делать (тихое глотание – плохая идея). Если не просто позволить исключению уведомить пользователя о том, что что-то пошло не так. Или, если хотите, поймайте исключение, чтобы зарегистрировать ошибку и восстановить ее.
Здесь есть хорошая дискуссия для C ++, но применяются общие понятия. Я нашел, что java-учебники по исключениям тоже очень хороши.
Если вы используете блок try для всего своего кода, вы также можете определить обработчик исключений по умолчанию (см. Документы ).
Кроме того, размер блока try зависит от вас, это зависит от того, насколько вы хотите, чтобы ваша обработка ошибок была. Если вы не можете восстановить какое-либо из исключений, нет особых причин быть конкретными, если вы не хотите регистрировать сообщения об ошибках, которые являются конкретными (но сообщение об исключении и трассировке стека, вероятно, будет достаточно).
Если вы обработаете все свои ошибки одним общим уловом, вы получите минимальную обратную связь и варианты при возникновении ошибки, это может быть хорошо во время разработки, но когда она на первой линии может не вызвать у вас никаких проблем.
Будьте конкретны и охватывайте все свои базы, где необходима обратная связь, и возможно восстановление.
Различные ошибки могут потребовать разных ответов.
Вы не выпрыгнете из самолета в ответ на все возможные проблемы, которые могут возникнуть. Не могли бы вы?
Ну, вот что делает ваше приложение.
Бывают ситуации, когда исключение можно поймать, и приложение может продолжать работать. Скорее всего, приложение, возможно, придется реагировать на один и тот же класс исключений по-разному в разных ситуациях. Возможно, в одной функции исключение I / O не вредно, а в другом.
Будьте конкретны и надлежащим образом обрабатывайте определенные ошибки.