Лучше всего попробовать - поймать весь мой PHP-код или быть как можно более конкретным? - PullRequest
15 голосов
/ 14 мая 2010

У меня не так много исключений в моем проекте.
Прямо сейчас, (мы используем MVC), у меня есть улов try, охватывающий весь мой код:

try{
   fronController::dispatch($somthing...);
}catch(Exception $E){
  //handle errors
}

Интересно, есть ли веская причина использовать блок try-catch как можно более конкретно, насколько это возможно, или просто оставить его в общем виде, как сейчас?

Ответы [ 8 ]

57 голосов
/ 14 мая 2010

Идея исключения состоит в том, что функция может сообщить о сбое без необходимости возвращать специальные значения. В старом 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 () будет создавать свои собственные исключения, если возникнет ошибка.

Нет проблем с памятью! Сборщик мусора с удовольствием очистит ненужные объекты исключений, не думая об этом. Используя вашу старую систему ручек, мы должны были помнить, чтобы вручную уничтожить ручку, чтобы она не скрывалась в памяти навсегда.

Существует множество других преимуществ и особенностей исключений. Я настоятельно рекомендую обратиться к другим источникам, чтобы узнать об этом. Особенно интересно то, как они накапливаются в стеке выполнения, пока какой-нибудь вызывающий не сможет их поймать. Также интересно, как вы можете поймать исключение, попытаться исправить проблему, а затем сбросить исключение, если не можете. Не забывайте, что исключения являются объектами! Это позволяет добиться большей гибкости. Для исключений, которые никто не может поймать, посмотрите в обработчик исключений.

Я намеревался ответить на вопрос, чтобы продемонстрировать, почему нам нужны исключения. Делая это, я надеюсь, что легко понять, какие проблемы мы можем решить с ними.

4 голосов
/ 15 мая 2010

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

 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
    }
 }
3 голосов
/ 14 мая 2010

Помните, что исключения для исключительных случаев. Насколько я понимаю, это происходит, когда ошибка находится вне вашего контроля. Например, недопустимые параметры передаются в общедоступную функцию API, деление на ноль, такие ситуации, как «потеря сетевого подключения», «файл не найден» ... такие вещи.

Как правило, вы должны перехватывать исключения, которые вы знаете, как обрабатывать, например, восстановление после ошибки, запись ошибки в журнал и ее распространение и т. Д. Если вы не знаете, как ее обработать, лучше дать это не удалось. В противном случае ваше приложение может быть в состоянии ошибки, которое вы можете не захотеть.

Итак, отвечая на ваш вопрос, лучше быть как можно более конкретным, поскольку каждое исключение следует обрабатывать, только если вы знаете, что с ним делать (тихое глотание - плохая идея). Если нет, просто позвольте исключению уведомить пользователя, что что-то пошло не так. Или, если хотите, перехватите исключение, чтобы зарегистрировать ошибку и повторно ее выбросить.

Здесь хорошее обсуждение здесь для C ++, но общие концепции применимы. Я нашел Java-учебники по исключениям тоже очень хорошо.

3 голосов
/ 14 мая 2010

Вы должны быть как можно более конкретны с обнаружением ошибок в вашем коде. Улавливание определенных ошибок соответствующим образом повышает удобство сопровождения кода, делает ваш код структурированным и организованным.

Это также хорошая практика в качестве соглашения, особенно если вы позже работаете над командными проектами, и вы не единственный, кто смотрит на код.

Личное отбрасывание всего в один try catch блок кажется запахом кода .

2 голосов
/ 14 мая 2010

Если вы используете блок try для всего своего кода, вы также можете определить обработчик исключений по умолчанию (см. документы ).

Кроме этого, размер блока try зависит от вас, это зависит от того, насколько хорошо вы хотите, чтобы ваша обработка ошибок была. Если вы не можете восстановить ни одно из исключений, на самом деле нет никаких причин быть конкретными, если только вы не хотите регистрировать сообщения об ошибках, которые являются конкретными (но сообщения об исключении и трассировке стека, вероятно, будет достаточно).

1 голос
/ 14 мая 2010

Для разных ошибок могут потребоваться разные ответы.

Вы не выпрыгнете из самолета в ответ на все возможные проблемы, которые могут возникнуть. Не могли бы вы?

Ну, вот что делает ваше приложение.

В некоторых ситуациях может быть обнаружено исключение, и приложение может продолжить работу. Скорее всего, приложению может потребоваться по-разному реагировать на один и тот же класс исключений в разных ситуациях. Возможно, в одной функции исключение ввода-вывода не является вредным, но в другой оно есть.

1 голос
/ 14 мая 2010

Если вы обработаете все свои ошибки одним общим уловом, вы получите минимальную обратную связь и варианты, когда ошибка возникнет, это может быть хорошо во время разработки, но когда она находится на переднем крае, это может привести к бесконечным проблемам.

Будьте конкретны и охватывайте все ваши базы, где необходима обратная связь и возможно восстановление.

0 голосов
/ 14 мая 2010

Будьте конкретны и обрабатывайте определенные ошибки соответствующим образом.

...