Le fait de renvoyer une exception qui fuit une abstraction?


12

J'ai une méthode d'interface qui indique dans la documentation qu'elle lèvera un type spécifique d'exception. Une implémentation de cette méthode utilise quelque chose qui lève une exception. L'exception interne est interceptée et l'exception déclarée par le contrat d'interface est levée. Voici un petit exemple de code pour mieux expliquer. Il est écrit en PHP mais est assez simple à suivre.

// in the interface

/**
 * @return This method returns a doohickey to use when you need to foo
 * @throws DoohickeyDisasterException
 */
public function getThatDoohickey();

// in the implementation

public function getThatDoohickey() {

    try {
        $SomethingInTheClass->doSomethingThatThrowsAnException();
    } catch (Exception $Exc) {
        throw new DoohickeyDisasterException('Message about doohickey failure');
    }

    // other code may return the doohickey

}

J'utilise cette méthode pour empêcher l'abstraction de fuir.

Mes questions sont les suivantes: le fait de passer l'exception interne levée comme l'exception précédente entraînerait-il une fuite dans l'abstraction? Sinon, serait-il approprié de simplement réutiliser le message de l'exception précédente? En cas de fuite de l'abstraction, pourriez-vous nous expliquer pourquoi vous pensez que c'est le cas?

Juste pour clarifier, ma question impliquerait de passer à la ligne de code suivante

throw new DoohickeyDisasterException($Exc->getMessage(), null, $Exc);

Les exceptions devraient porter sur les causes et non sur qui les jette . Si votre code peut avoir un file_not_found, alors il devrait lancer un file_not_found_exception. Les exceptions ne doivent pas être spécifiques à la bibliothèque.
Mooing Duck

Réponses:


11

Mes questions sont les suivantes: le passage de l'exception interne levée comme exception précédente entraînerait-il une fuite dans l'abstraction? Sinon, serait-il approprié de simplement réutiliser le message de l'exception précédente?

La réponse est: "ça dépend".

Plus précisément, cela dépend de l'exception et de ce que cela signifie. Essentiellement, le renverser signifie que vous l'ajoutez à votre interface. Le feriez-vous si vous écriviez le code que vous appelez vous-même, ou est-ce juste pour éviter de renommer l'exception?

Si votre exception inclut une trace jusqu'à la cause racine, dans le code appelé, cela peut entraîner une fuite de données en dehors de l'abstraction - mais, en général, je pense que cela est acceptable car l'exception est soit gérée par l'appelant (et personne ne se soucie où il est venu de) ou montré à l'utilisateur (et vous voulez connaître la cause racine, pour le débogage.)

Fréquemment, cependant, le simple fait de laisser passer l'exception est un choix d'implémentation très raisonnable, et non un problème d'abstraction. Demandez simplement "est-ce que je lancerais ceci si le code que j'appelle ne le faisait pas?"


8

A proprement parler, si l'exception « intérieure » révèle quoi que ce soit sur le fonctionnement interne d'une implémentation, vous êtes une fuite. Donc, techniquement, vous devez toujours tout attraper et lancer votre propre type d'exception.

MAIS.

Plusieurs arguments importants peuvent être avancés contre cela. Notamment:

  • Les exceptions sont des choses exceptionnelles ; ils violent déjà les règles du «fonctionnement normal» de plusieurs façons, de sorte qu'ils pourraient aussi bien violer l'encapsulation et l'abstraction au niveau de l'interface.
  • L'avantage d'avoir une trace de pile significative et des informations d'erreur au point l' emporte souvent sur l'exactitude de la POO, tant que vous ne divulguez pas d'informations de programme internes au-delà de la barrière de l'interface utilisateur (ce qui serait la divulgation d'informations).
  • Envelopper chaque exception interne dans un type d'exception externe approprié peut conduire à de nombreux codes passe-partout inutiles , ce qui va à l'encontre de l'objectif des exceptions (à savoir, implémenter la gestion des erreurs sans polluer le flux de code normal avec la gestion des erreurs passe-partout).

Cela dit, la capture et l' emballage des exceptions souvent ne du sens, si seulement d'ajouter des informations supplémentaires à vos journaux, ou pour empêcher une fuite des informations internes à l'utilisateur final. La gestion des erreurs est toujours une forme d'art, et il est difficile de la résumer à quelques règles générales. Certaines exceptions devraient être propagées, d'autres non, et la décision dépend également du contexte. Si vous codez une interface utilisateur, vous ne voulez rien laisser passer à l'utilisateur non filtré; mais si vous codez une bibliothèque qui implémente un protocole réseau, la propagation de certaines exceptions est probablement la bonne chose à faire (après tout, si le réseau est en panne, vous ne pouvez pas faire grand-chose pour améliorer les informations ou récupérer-et-continuer ; traduire NetworkDownExceptionen FoobarNetworkDownExceptionest tout simplement dépourvu de sens).


6

Tout d'abord, ce que vous faites ici n'est pas de lever une exception. Renverser serait littéralement lever la même exception, comme ceci:

try {
    $SomethingInTheClass->doSomethingThatThrowsAnException();
} catch (Exception $Exc) {
    throw $Exc;
}

Évidemment, c'est un peu inutile. La reprise est destinée aux circonstances dans lesquelles vous souhaitez libérer certaines ressources ou consigner l'exception ou tout ce que vous souhaitez faire, sans empêcher l'exception de propager la pile.

Ce que vous faites déjà, c'est de remplacer toutes les exceptions, quelle qu'en soit la cause, par une DoohickeyDisasterException. Ce qui peut ou non être ce que vous avez l'intention de faire. Mais je suggère que chaque fois que vous faites cela, vous devez également faire ce que vous suggérez et passer l'exception d'origine comme exception interne, juste au cas où cela serait utile dans la trace de la pile.

Mais il ne s'agit pas d'abstractions qui fuient, c'est un autre problème.

Pour répondre à la question de savoir si une exception renvoyée (ou bien une exception non interceptée) peut être une abstraction qui fuit, je dirais que cela dépend de votre code appelant. Si ce code nécessite la connaissance du cadre abstrait, c'est une abstraction qui fuit, car si vous remplacez ce cadre par un autre, vous devez changer le code en dehors de votre abstraction.

Par exemple, si DoohickeyDisasterException est une classe du framework et que votre code appelant ressemble à ceci, alors vous avez une abstraction qui fuit:

try {
    $wrapper->getThatDoohickey();
} catch (DoohickeyDisasterException $Exc) {
    echo "The Doohickey is all wrong!";
    echo $Exc->getMessage();
    gotoAHappyPlaceAndSingHappySongs();
}

Si vous remplacez votre infrastructure tierce par une autre, qui utilise un nom d'exception différent, et vous devez trouver toutes ces références et les modifier. Mieux vaut créer votre propre classe d'exception et envelopper l'exception du framework en elle.

Si, d'autre part, DoohickeyDisasterException est une de vos propres créations, vous ne fuyez pas les abstractions, vous devriez vous préoccuper davantage de mon premier point.


2

La règle que j'essaie de suivre est la suivante: envelopper si vous l'avez provoquée. De même, si un bogue devait être créé en raison d'une exception d'exécution, il devrait s'agir d'une sorte de DoHickeyException ou MyApplicationException.

Ce que cela implique, c'est que si la cause de l'erreur est quelque chose d'extérieur à votre code, et que vous ne vous attendez pas à ce qu'il échoue, échouez gracieusement et recommencez. De même, s'il s'agit d'un problème avec votre code, enveloppez-le dans une exception (potentiellement) personnalisée.

Par exemple, si un fichier est censé se trouver sur le disque ou qu'un service devrait être disponible, gérez-le correctement et relancez l'erreur afin que le consommateur de votre code puisse comprendre pourquoi cette ressource n'est pas disponible. Si vous avez créé trop de données pour certains types, c'est votre erreur de boucler et de jeter.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.