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

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

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

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

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

Ответ 1

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

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

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

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

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

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

Ответ 2

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

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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

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

Ответ 5

Вы должны взглянуть на Исключить лучшие практики в PHP 5.3

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

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