web-dev-qa-db-fra.com

Quelles sont les meilleures pratiques pour capturer et rediffuser des exceptions?

Les exceptions interceptées doivent-elles être rejetées directement ou doivent-elles être regroupées autour d'une nouvelle exception?

C'est, devrais-je faire ceci:

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

ou ca:

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

Si votre réponse est de lancer directement, veuillez suggérer l'utilisation de chaînage d'exceptions, je ne suis pas en mesure de comprendre un scénario du monde réel dans lequel nous utilisons un chaînage d'exceptions.

140
Rahul Prasad

Vous ne devriez pas attraper l'exception sauf si vous avez l'intention de faire quelque chose de significatif.

"Quelque chose de significatif" pourrait être l'un de ceux-ci:

Gérer l'exception

L'action significative la plus évidente consiste à gérer l'exception, par exemple. en affichant un message d'erreur et en abandonnant l'opération:

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

Enregistrement ou nettoyage partiel

Parfois, vous ne savez pas comment gérer correctement une exception dans un contexte spécifique; vous manquez peut-être d'informations sur la "vue d'ensemble", mais vous souhaitez enregistrer l'échec aussi près que possible du point où il s'est produit. Dans ce cas, vous voudrez peut-être attraper, connecter et relancer:

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

Un scénario associé est celui où vous êtes au bon endroit pour effectuer un nettoyage pour l'opération ayant échoué, mais pas pour décider de la manière dont l'incident doit être traité au niveau supérieur. Dans les versions antérieures PHP, cela serait implémenté comme

$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 a introduit le mot-clé finally. Pour les scénarios de nettoyage, il existe maintenant une autre façon de procéder. Si le code de nettoyage doit être exécuté quoi qu'il soit arrivé (c'est-à-dire à la fois en cas d'erreur et en cas de succès), il est désormais possible de le faire tout en permettant à toute exception levée de se propager:

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

Abstraction d'erreur (avec chaînage d'exceptions)

Un troisième cas est celui où vous souhaitez logiquement regrouper de nombreuses défaillances possibles sous un parapluie plus grand. Un exemple de regroupement logique:

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

Dans ce cas, vous ne voulez pas que les utilisateurs de Component sachent qu'il est implémenté à l'aide d'une connexion à une base de données (vous souhaitez peut-être garder vos options ouvertes et utiliser le stockage basé sur fichier à l'avenir). Ainsi, votre spécification pour Component dira que "dans le cas d’un échec d’initialisation, ComponentInitException sera lancé". Cela permet aux utilisateurs de Component d'attraper des exceptions du type attendu tout en permettant également au code de débogage d'accéder à tous les détails (dépendant de l'implémentation).

Fournir un contexte plus riche (avec chaînage d'exceptions)

Enfin, il peut arriver que vous souhaitiez fournir plus de contexte à l'exception. Dans ce cas, il est judicieux de placer l'exception dans une autre qui contient davantage d'informations sur ce que vous tentiez de faire lorsque l'erreur s'est produite. Par exemple:

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

Ce cas est semblable au précédent (et l’exemple n’est probablement pas le meilleur possible), mais il illustre bien l’utilité de fournir davantage de contexte: si une exception est levée, cela nous indique que la copie du fichier a échoué. Mais pourquoi at-il échoué? Cette information est fournie dans les exceptions encapsulées (il pourrait y avoir plus d'un niveau si l'exemple était beaucoup plus compliqué).

La valeur de cette opération est illustrée si vous envisagez un scénario où, par exemple, La création d'un objet UserProfile entraîne la copie des fichiers, car le profil de l'utilisateur est stocké dans des fichiers et prend en charge la sémantique des transactions: vous pouvez "annuler" les modifications, car elles ne sont effectuées que sur une copie du profil.

Dans ce cas, si vous avez fait

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

en conséquence, une erreur d’exception "Le répertoire cible n’a pas pu être créé" vous donne le droit d’être confondue. Si cette exception "principale" est encapsulée dans des couches d’autres exceptions fournissant un contexte, il sera beaucoup plus facile de traiter l’erreur ("La création de la copie du profil a échoué" -> "La copie du fichier a échoué" -> "Le répertoire cible n’a pas pu être créé").

265
Jon

Eh bien, il s’agit de maintenir l’abstraction. Donc, je suggère d'utiliser un chaînage d'exceptions pour lancer directement. En ce qui concerne pourquoi, laissez-moi vous expliquer le concept de abstractions qui fuient

Disons que vous construisez un modèle. Le modèle est censé extraire toute la persistance et la validation des données du reste de l'application. Alors maintenant, que se passe-t-il lorsque vous obtenez une erreur de base de données? Si vous relancez le DatabaseQueryException, vous perdez l'abstraction. Pour comprendre pourquoi, réfléchis à l’abstraction une seconde. Vous ne vous souciez pas comment le modèle stocke les données, juste qu'il le fait. De même, vous ne vous souciez pas exactement de ce qui ne va pas dans les systèmes sous-jacents du modèle, juste que vous savez que quelque chose ne va pas et à peu près ce qui ne va pas.

Ainsi, en réadressant DatabaseQueryException, vous faites abstraction de l'abstraction et vous demandez au code d'appel de comprendre la sémantique de ce qui se passe dans le modèle. Au lieu de cela, créez un ModelStorageException générique, et enveloppez le DatabaseQueryException capturé à l'intérieur. De cette façon, votre code d'appel peut toujours essayer de traiter l'erreur sémantiquement, mais la technologie sous-jacente du modèle n'a pas d'importance, car vous exposez uniquement les erreurs de cette couche d'abstraction. Mieux encore, puisque vous avez enveloppé l'exception, si elle bouillonne jusqu'au bout et doit être journalisée, vous pouvez suivre jusqu'à l'exception racine levée (parcourir la chaîne) afin de disposer de toutes les informations de débogage dont vous avez besoin!

Ne vous contentez pas d'attraper et de renvoyer la même exception à moins que vous ne deviez faire du post-traitement. Mais un bloc comme } catch (Exception $e) { throw $e; } est inutile. Mais vous pouvez reformuler les exceptions pour un gain d'abstraction significatif.

34
ircmaxell

IMHO, attraper une exception pour la relancer est inutile. Dans ce cas, il suffit de ne pas l'attraper et de laisser les méthodes appelées précédemment le gérer (c'est-à-dire les méthodes qui sont "supérieures" dans la pile d'appels) .

Si vous le relancez, enchaîner l'exception capturée dans la nouvelle que vous allez lancer est certainement une bonne pratique, car il conservera les informations contenues dans l'exception capturée. Cependant, le renverser n’est utile que si vous ajoutez des informations ou gérez quelque chose à l’exception interceptée, qu’il s’agisse de contexte, de valeurs, de journalisation, de libération de ressources, peu importe.

Une façon d’ajouter des informations est d’étendre la classe Exception, pour avoir des exceptions comme NullParameterException, DatabaseException, etc. De plus, cela permet au développeur de ne capturer que quelques exceptions. qu'il peut gérer. Par exemple, on ne peut attraper que DatabaseException et essayer de résoudre la cause de Exception, comme pour se reconnecter à la base de données.

9
Clement Herreman

Vous devez jeter un coup d'œil sur Meilleures pratiques en matière d'exception dans PHP 5.

La gestion des exceptions dans PHP n'est pas une nouvelle fonctionnalité. Dans le lien suivant, vous verrez deux nouvelles fonctionnalités dans PHP 5.3 basées sur des exceptions. Le premier est constitué d’exceptions imbriquées et le second est un nouvel ensemble de types d’exception proposés par l’extension SPL (qui est maintenant une extension principale de PHP runtime). Ces deux nouvelles fonctionnalités ont dans le livre des meilleures pratiques et méritent d’être examinés en détail.

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

2
HMagdy

Vous pensez généralement de cette façon.

Une classe peut lancer plusieurs types d'exceptions qui ne correspondront pas. Donc, vous créez une classe d'exception pour cette classe ou ce type de classe et vous le lancez.

Le code qui utilise la classe ne doit donc intercepter qu'un type d'exception.

1
Ólafur Waage