Каковы лучшие практики для ловли и повторного исключения? - PullRequest
137 голосов
/ 05 апреля 2011

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

То есть я должен сделать следующее:

try {
  $connect = new CONNECT($db, $user, $password, $driver, $host);
} catch (Exception $e) {
  throw $e;
}

или это:

try {
  $connect = new CONNECT($db, $user, $password, $driver, $host);
} catch (Exception $e) {
  throw new Exception("Exception Message", 1, $e);
}

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

Ответы [ 5 ]

260 голосов
/ 05 апреля 2011

Вы не должны ловить исключение , если вы не собираетесь делать что-то значимое .

«Что-то значимое» может быть одним из них:

Обработка исключения

Наиболее очевидным значимым действием является обработка исключения, например, отображая сообщение об ошибке и прерывая операцию:

try {
    $connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
    echo "Error while connecting to database!";
    die;
}

Регистрация или частичная очистка

Иногда вы не знаете, как правильно обрабатывать исключение в определенном контексте; возможно, вам не хватает информации о «большой картине», но вы хотите записать сбой как можно ближе к точке, где это произошло. В этом случае вам может понадобиться поймать, зарегистрировать и перебросить:

try {
    $connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
    logException($e); // does something
    throw $e;
}

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

$connect = new CONNECT($db, $user, $password, $driver, $host);
try {
    $connect->insertSomeRecord();
}
catch (Exception $e) {
    $connect->disconnect(); // we don't want to keep the connection open anymore
    throw $e; // but we also don't know how to respond to the failure
}

В PHP 5.5 введено ключевое слово finally, поэтому для сценариев очистки теперь есть другой способ приблизиться к этому. Если код очистки должен выполняться независимо от того, что произошло (то есть как при ошибке, так и при успехе), теперь можно сделать это, прозрачно разрешая распространяться любым сгенерированным исключениям:

$connect = new CONNECT($db, $user, $password, $driver, $host);
try {
    $connect->insertSomeRecord();
}
finally {
    $connect->disconnect(); // no matter what
}

Ошибка абстракции (за исключением цепочки)

В третьем случае вы хотите логически сгруппировать множество возможных сбоев под большим зонтиком. Пример для логической группировки:

class ComponentInitException extends Exception {
    // public constructors etc as in Exception
}

class Component {
    public function __construct() {
        try {
            $connect = new CONNECT($db, $user, $password, $driver, $host);
        }
        catch (Exception $e) {
            throw new ComponentInitException($e->getMessage(), $e->getCode(), $e);
        }
    }
}

В этом случае вы не хотите, чтобы пользователи Component знали, что это реализовано с использованием соединения с базой данных (возможно, вы хотите оставить свои параметры открытыми и использовать хранилище на основе файлов в будущем). Таким образом, в вашей спецификации для Component будет сказано, что «в случае сбоя инициализации будет выброшено ComponentInitException». Это позволяет потребителям Component перехватывать исключения ожидаемого типа , а также позволяет отладочному коду получать доступ ко всем (зависящим от реализации) деталям .

Обеспечение более богатого контекста (за исключением цепочки)

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

class FileOperation {
    public static function copyFiles() {
        try {
            $copier = new FileCopier(); // the constructor may throw

            // this may throw if the files do no not exist
            $copier->ensureSourceFilesExist();

            // this may throw if the directory cannot be created
            $copier->createTargetDirectory();

            // this may throw if copying a file fails
            $copier->performCopy();
        }
        catch (Exception $e) {
            throw new Exception("Could not perform copy operation.", 0, $e);
        }
    }
}

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

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

В этом случае, если вы сделали

try {
    $profile = UserProfile::getInstance();
}

и в результате возникнет ошибка исключения «Целевой каталог не может быть создан», у вас будет право на путаницу. Обтекание этого «основного» исключения слоями других исключений, предоставляющих контекст, значительно облегчит возникновение ошибки («Ошибка создания копии профиля» -> «Ошибка операции копирования файла» -> «Не удалось создать целевой каталог»).

32 голосов
/ 05 апреля 2011

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

Допустим, вы строите модель. Предполагается, что модель абстрагирует всю устойчивость данных и проверку от остальной части приложения. Итак, что происходит, когда вы получаете ошибку базы данных? Если вы отбрасываете DatabaseQueryException, вы пропускаете абстракцию. Чтобы понять почему, подумайте об абстракции на секунду. Вам все равно, как модель хранит данные, только то, что она делает. Точно так же вас не волнует, что именно пошло не так в базовых системах модели, просто вы знаете, что что-то пошло не так, и примерно то, что пошло не так.

Итак, перебрасывая исключение DatabaseQueryException, вы пропускаете абстракцию и требуете от вызывающего кода понять семантику того, что происходит в модели. Вместо этого создайте общий ModelStorageException и оберните пойманный DatabaseQueryException внутри него. Таким образом, ваш вызывающий код все еще может попытаться обработать ошибку семантически, но это не имеет значения для базовой технологии Модели, поскольку вы только выявляете ошибки из этого уровня абстракции. Более того, поскольку вы обернули исключение, если оно вспыхнуло полностью и требует регистрации, вы можете проследить до брошенного корневого исключения (пройтись по цепочке), чтобы у вас все еще была вся необходимая отладочная информация!

Не перехватывайте и не перебрасывайте одно и то же исключение, если только вам не нужна постобработка. Но блок, подобный } catch (Exception $e) { throw $e; }, не имеет смысла. Но вы можете переписать исключения для некоторого значительного усиления абстракции.

9 голосов
/ 05 апреля 2011

ИМХО, ловить исключение, чтобы просто сбросить его, бесполезно .В этом случае, просто не перехватывайте его, и пусть ранее вызванные методы обрабатывают его (он же «верхний» метод в стеке вызовов) .

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

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

2 голосов
/ 25 ноября 2013

Вы должны взглянуть на Exception Best Practices в PHP 5.3

Обработка исключений в PHP не является новой особенностью. По следующей ссылке вы увидите две новые функции в PHP 5.3, основанные на исключениях. Первый - это вложенные исключения, а второй - новый набор типов исключений, предлагаемых расширением SPL (которое теперь является основным расширением среды выполнения PHP). Обе эти новые функции вошли в книгу лучших рекомендаций и заслуживают подробного изучения.

http://ralphschindler.com/2010/09/15/exception-best-practices-in-php-5-3

1 голос
/ 05 апреля 2011

Обычно вы думаете об этом так.

Класс может выдавать много типов исключений, которые не будут совпадать.Таким образом, вы создаете класс исключения для этого класса или типа класса и выбрасываете его.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...