78 votes

Messages d'exception personnalisés : Meilleures pratiques

Je me demande dans quelle mesure je dois m'efforcer de forcer l'obtention d'informations de débogage utiles lors de la création de messages d'exception, ou dois-je simplement faire confiance à l'utilisateur pour fournir les bonnes informations, ou encore confier la collecte d'informations à un gestionnaire d'exception ?

Je vois beaucoup de gens qui font leurs exceptions comme :

throw new RuntimeException('MyObject is not an array')

ou d'étendre les exceptions par défaut avec des exceptions personnalisées qui ne font pas grand-chose à part changer le nom de l'exception :

throw new WrongTypeException('MyObject is not an array')

Mais cela ne fournit pas beaucoup d'informations de débogage... et n'impose aucune forme de formatage du message d'erreur. Vous pouvez donc vous retrouver avec exactement la même erreur produisant deux messages d'erreur différents... par exemple "Database connection failed" ou "Could not connect to db".

Bien sûr, s'il s'agit d'une bulle, il affichera la trace de la pile, ce qui est utile, mais cela ne me dit pas toujours tout ce que j'ai besoin de savoir et je finis généralement par devoir lancer des instructions var_dump() pour découvrir ce qui s'est mal passé et où... bien que cela puisse être quelque peu compensé par un gestionnaire d'exception décent.

Je commence à penser à quelque chose comme le code ci-dessous, où je exiger le lanceur de l'exception doit fournir les arguments nécessaires pour produire le message d'erreur correct. Je pense que cela pourrait être la voie à suivre dans ce cas :

  • Un niveau minimum d'informations utiles doit être fourni
  • Produit des messages d'erreur assez cohérents
  • Les modèles de messages d'exception se trouvent tous au même endroit (classes d'exception), ce qui facilite la mise à jour des messages...

Mais je vois l'inconvénient d'être plus difficile à utiliser (il faut chercher la définition de l'exception), et donc de décourager les autres programmeurs d'utiliser les exceptions fournies...

J'aimerais avoir des commentaires sur cette idée et sur les meilleures pratiques pour un cadre cohérent et flexible de messages d'exception.

/**
* @package MyExceptions
* MyWrongTypeException occurs when an object or 
* datastructure is of the incorrect datatype.
* Program defensively!
* @param $objectName string name of object, eg "\$myObject"
* @param $object object object of the wrong type
* @param $expect string expected type of object eg 'integer'
* @param $message any additional human readable info.
* @param $code error code.
* @return Informative exception error message.
* @author secoif
*/
class MyWrongTypeException extends RuntimeException {
    public function __construct($objectName, $object, $expected, $message = '', $code = 0) {
        $receivedType = gettype($object) 
        $message = "Wrong Type: $objectName. Expected $expected, received $receivedType";
        debug_dump($message, $object);
        return parent::__construct($message, $code);
    }
}

....

/**
 * If we are in debug mode, append the var_dump of $object to $message
 */
function debug_dump(&$message, &$object) {
     if (App::get_mode() == 'debug') {
         ob_start();
         var_dump($object);
         $message = $message . "Debug Info: " . ob_get_clean();
    }
}

Puis utilisé comme :

// Hypothetical, supposed to return an array of user objects
$users = get_users(); // but instead returns the string 'bad'
// Ideally the $users model object would provide a validate() but for the sake
// of the example
if (is_array($users)) {
  throw new MyWrongTypeException('$users', $users, 'array')
  // returns 
  //"Wrong Type: $users. Expected array, received string
}

et nous pourrions faire quelque chose comme un nl2br dans un gestionnaire d'exception personnalisé pour rendre les choses agréables pour la sortie html.

Je lisais : http://msdn.microsoft.com/en-us/library/cc511859.aspx#

Et il n'y a aucune mention de quelque chose comme ça, donc peut-être que c'est une mauvaise idée...

1 votes

Je suis sûr que le constructeur ici devrait correspondre à RuntimeException::__construct()

37voto

ShuggyCoUk Points 24204

Je recommande vivement les conseils sur Le blog de Krzysztof et note que dans votre cas, vous semblez essayer de traiter ce qu'il appelle des erreurs d'utilisation.

Dans ce cas, il ne faut pas créer un nouveau type pour l'indiquer, mais un meilleur message d'erreur sur ce qui l'a provoqué. En tant que tel, une fonction d'aide pour soit :

  1. générer la chaîne de texte à placer dans l'exception
  2. générer l'ensemble de l'exception et du message

C'est ce qui est requis.

L'approche 1 est plus claire, mais peut conduire à une utilisation un peu plus verbeuse, l'approche 2 est l'inverse, échangeant une syntaxe plus courte contre moins de clarté.

Notez que les fonctions doivent être extrêmement sûres (elles ne doivent jamais, au grand jamais, provoquer elles-mêmes une exception non liée) et ne pas obliger à fournir des données qui sont facultatives dans certaines utilisations raisonnables.

En utilisant l'une ou l'autre de ces approches, vous faciliterez l'internationalisation ultérieure du message d'erreur si nécessaire.

Un suivi de pile vous donne au minimum la fonction, et éventuellement le numéro de ligne. Vous devez donc vous concentrer sur la fourniture d'informations qui ne sont pas faciles à déduire à partir de là.

0 votes

Oui, maintenant je vois. Je fais beaucoup d'erreurs d'utilisation et de contrôles d'intégrité, même si les contrôles d'intégrité devraient probablement être déplacés vers les appels assert(). php.net/manual/fr/function.assert.php .

0 votes

Le blog de Krzysztof suggère de réutiliser des types existants, par exemple FileNotFoundException, NullArgumentException, etc. PHP, cependant, n'a qu'un seul type d'exception - Exception. Il peut être étendu, mais c'est le seul qu'il inclut par défaut et je ne sais pas si cela est particulièrement utile en soi...

0 votes

Conseil : La bibliothèque standard de PHP (SPL) fournit un bon nombre d'exceptions intégrées. php.net/manual/fr/spl.exceptions.php

23voto

Orwellophile Points 2695

Je ne vais pas m'écarter des conseils donnés sur le blog de Krzysztof, mais voici un moyen très simple de créer des exceptions personnalisées.

Exemple :

<?php
   require_once "CustomException.php";
   class SqlProxyException extends CustomException {}

   throw new SqlProxyException($errorMsg, mysql_errno());     
?>

Le code qui se cache derrière (que j'ai emprunté quelque part, mes excuses à qui de droit)

<?php

interface IException
{
    /* Protected methods inherited from Exception class */
    public function getMessage();                 // Exception message
    public function getCode();                    // User-defined Exception code
    public function getFile();                    // Source filename
    public function getLine();                    // Source line
    public function getTrace();                   // An array of the backtrace()
    public function getTraceAsString();           // Formated string of trace

    /* Overrideable methods inherited from Exception class */
    public function __toString();                 // formated string for display
    public function __construct($message = null, $code = 0);
}

abstract class CustomException extends Exception implements IException
{
    protected $message = 'Unknown exception';     // Exception message
    private   $string;                            // Unknown
    protected $code    = 0;                       // User-defined exception code
    protected $file;                              // Source filename of exception
    protected $line;                              // Source line of exception
    private   $trace;                             // Unknown

    public function __construct($message = null, $code = 0)
    {
        if (!$message) {
            throw new $this('Unknown '. get_class($this));
        }
        parent::__construct($message, $code);
    }

    public function __toString()
    {
        return get_class($this) . " '{$this->message}' in {$this->file}({$this->line})\n"
                                . "{$this->getTraceAsString()}";
    }
}

2 votes

+1 pour être le premier endroit que j'ai trouvé qui m'a réellement expliqué comment définir une exception PHP et comment la lancer.

0 votes

Ma grande question ici est comment diable avez-vous remplacé FINAL public function getCode() ?

0 votes

Bon Dieu, ce code a été écrit il y a 7 ans (je viens de trouver qui l'a écrit). C'est toujours le deuxième commentaire en haut de la page de manuel des exceptions de php.net. J'utilisais php 5.3 quand je l'ai copié, et j'utilise maintenant php 5.4. Ce qui pourrait être ou ne pas être une méthode finale dans php 8 (pour assurer l'avenir de mon commentaire) ne me concerne pas :) php.net/manual/fr/language.exceptions.php#91159

12voto

John Saunders Points 118808

Voir Comment concevoir des hiérarchies d'exceptions sur le blog de Krzysztof Cwalina, coauteur de "Framework Design Guidelines".

0 votes

Il y a de bons points là-dedans, mais je ne suis pas convaincu à 100%. J'aime ce concept :

0 votes

"les exceptions qui se produisent dans la couche d'accès aux données sont enregistrées et ensuite enveloppées dans une autre exception qui fournit des informations plus significatives à la couche appelante...".

0 votes

... . Dans la couche des composants métier, les exceptions sont consignées avant d'être propagées. Toutes les exceptions qui se produisent dans la couche des composants métier et qui contiennent des informations sensibles sont remplacées par des exceptions qui ne contiennent plus ces informations. ...

3voto

Matthew Farwell Points 31257

Ne faites jamais, au grand jamais, confiance à un utilisateur pour "faire ce qu'il faut", et incluez des informations pour le débogage. Si vous voulez des informations, vous devez les recueillir vous-même et les stocker quelque part où elles seront accessibles.

Comme nous l'avons déjà dit, s'il est difficile de faire quelque chose, les utilisateurs éviteront de le faire, donc encore une fois, ne dépendez pas de leur bonne volonté et de leur connaissance de ce qu'ils doivent envoyer.

Cette réflexion implique une méthode permettant de collecter les informations et de les enregistrer, ce qui implique l'utilisation de var_dump() quelque part.

En outre, comme l'a dit Mark Harrison, un bouton qui permet d'envoyer facilement un message d'erreur quelque part est fantastique pour vous et pour les utilisateurs. Il leur permet de signaler facilement une erreur. Vous (en tant que destinataire) recevez beaucoup de doublons, mais une information en double vaut mieux que pas d'information du tout.

0voto

Mark Harrison Points 77152

Quelle que soit la quantité de détails que vous ajoutez, veillez à ce que soit

  • pour faciliter le copier-coller de l'ensemble du texte, ou
  • avoir un bouton qui signalera l'erreur pour eux

2 votes

...bien qu'il s'agisse davantage d'une mesure de finition que d'un problème d'architecture.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X