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

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

То есть, должен ли я это сделать:

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); } 

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

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

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

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

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

 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(); } 

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

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

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

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

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

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

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

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

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

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

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

Вы должны взглянуть на 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