web-dev-qa-db-fra.com

Messages d'exception personnalisés: meilleures pratiques

Vous vous demandez combien d'efforts je devrais faire pour forcer des informations de débogage utiles lors de la création de messages d'exception, ou devrais-je simplement faire confiance à l'utilisateur pour fournir les bonnes informations, ou reporter la collecte d'informations à un gestionnaire d'exceptions?

Je vois beaucoup de gens qui font leurs exceptions comme:

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

ou étendre les exceptions par défaut avec des exceptions personnalisées qui ne font pas grand-chose mais changent 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'applique aucun type de formatage avec le message d'erreur. Ainsi, vous pourriez vous retrouver avec exactement la même erreur produisant deux messages d'erreur différents ... par exemple, "La connexion à la base de données a échoué" vs "Impossible de se connecter à db"

Bien sûr, s'il bouillonne vers le haut, il imprimera la trace de la pile, ce qui est utile, mais il ne me dit pas toujours tout ce que je dois savoir et, en général, je dois commencer à tirer sur les instructions var_dump () pour découvrir ce qui a mal tourné et où ... bien que cela puisse être quelque peu compensé par un gestionnaire d'exceptions décent.

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

  • Un niveau minimum d'informations utiles doit être fourni
  • Produit des messages d'erreur quelque peu cohérents
  • Modèles pour les messages d'exception tous dans le même emplacement (classes d'exception), donc plus facile de mettre à jour les messages ...

Mais je vois l'inconvénient d'être plus difficiles à utiliser (vous devez rechercher la définition d'exception), ce qui pourrait décourager d'autres programmeurs d'utiliser les exceptions fournies ...

Je voudrais quelques commentaires sur cette idée et les meilleures pratiques pour un cadre de message d'exception cohérent et flexible.

/**
* @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'exceptions personnalisé pour rendre les choses agréables pour la sortie html.

Vous avez lu: http://msdn.Microsoft.com/en-us/library/cc511859.aspx#

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

67
timoxley

Je recommande fortement les conseils sur le blog de Krzysztof et je noterais que dans votre cas, vous semblez essayer de gérer ce qu'il appelle les erreurs d'utilisation.

Dans ce cas, ce qui est requis n'est pas un nouveau type pour l'indiquer mais un meilleur message d'erreur sur la cause. En tant que telle fonction d'assistance, vous pouvez soit:

  1. générer la chaîne textuelle à placer dans l'exception
  2. générer toute l'exception et le message

C'est ce qui est requis.

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

Notez que les fonctions doivent être extrêmement sûres (elles ne devraient jamais, jamais provoquer elles-mêmes une exception sans rapport) et ne pas forcer la fourniture de données qui est facultative dans certaines utilisations raisonnables.

En utilisant l'une de ces approches, il est plus facile d'internationaliser le message d'erreur ultérieurement si nécessaire.

Une trace de pile au minimum vous donne 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éterminer.

34
ShuggyCoUk

Je ne détournerai pas de l'avis concernant 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 derrière cela (que j'ai emprunté quelque part, excuses à qui que ce soit)

<?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()}";
    }
}
20
Orwellophile

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

11
John Saunders

Ne faites jamais confiance à un utilisateur pour "faire la bonne chose" 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 sont accessibles.

De plus, comme indiqué, s'il est difficile (er) 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 par laquelle vous collectez les informations et les enregistrez, ce qui implique d'utiliser 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. Cela leur permet de signaler facilement une erreur. Vous (en tant que destinataire) obtenez beaucoup de doublons, mais les informations en double valent mieux qu'aucune information.

3
Matthew Farwell

Quel que soit le détail que vous ajoutez, assurez-vous et

  • faciliter le découpage et le collage du tout, ou
  • avoir un bouton qui signalera l'erreur pour eux
0
Mark Harrison