Benutzerdefinierte Ausnahmemeldungen: Best Practices

72

Sie fragen sich, wie viel Aufwand ich betreiben sollte, um nützliche Debugging-Informationen beim Erstellen von Ausnahmemeldungen zu erzwingen, oder ob ich dem Benutzer nur vertrauen soll, dass er die richtigen Informationen bereitstellt, oder die Erfassung von Informationen an einen Ausnahmehandler verschieben soll?

Ich sehe viele Leute, die ihre Ausnahmen machen wie:

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

oder Erweitern der Standardausnahmen um benutzerdefinierte Ausnahmen, die nicht viel bewirken, aber den Namen der Ausnahme ändern:

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

Dies liefert jedoch nicht viele Debugging-Informationen ... und erzwingt keinerlei Formatierung mit der Fehlermeldung. Es könnte also zu genau demselben Fehler kommen, der zwei verschiedene Fehlermeldungen hervorruft ... z. B. "Datenbankverbindung fehlgeschlagen" vs "Verbindung zu Datenbank konnte nicht hergestellt werden"

Sicher, wenn es nach oben sprudelt, wird die Stapelverfolgung gedruckt, was nützlich ist, aber es sagt mir nicht immer alles, was ich wissen muss, und normalerweise muss ich anfangen, var_dump () -Anweisungen abzuschießen, um sie zu entdecken Was ist schief gelaufen und wo ... obwohl dies mit einem anständigen Ausnahmebehandler etwas ausgeglichen werden könnte.

Ich fange an, über so etwas wie den Code unten zu denken, wo ich erfordern den Werfer der Ausnahme notwendig args zuzuführen , um die richtige Fehlermeldung zu erzeugen. Ich denke, dies könnte der richtige Weg sein:

  • Es muss ein Mindestmaß an nützlichen Informationen angegeben werden
  • Erzeugt etwas konsistente Fehlermeldungen
  • Vorlagen für Ausnahmemeldungen an einem Ort (Ausnahmeklassen), damit die Nachrichten einfacher aktualisiert werden können ...

Aber ich sehe den Nachteil darin, dass sie schwieriger zu verwenden sind (erfordert, dass Sie die Ausnahmedefinition nachschlagen) und daher andere Programmierer davon abhalten können, mitgelieferte Ausnahmen zu verwenden ...

Ich möchte einen Kommentar zu dieser Idee und zu Best Practices für ein konsistentes, flexibles Framework für Ausnahmemeldungen.

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

Dann verwendet wie:

// 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
}

und wir könnten so etwas wie ein nl2br in einem benutzerdefinierten Ausnahmebehandler tun, um die Dinge für die HTML-Ausgabe schön zu machen.

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

Und so etwas wird nicht erwähnt, also ist es vielleicht eine schlechte Idee ...

Timoxley
quelle
1
Ziemlich sicher, dass der Konstrukteur hier übereinstimmen müssteRuntimeException::__construct()
Jacob Thomason

Antworten:

33

Ich kann den Rat auf Krzysztofs Blog nur empfehlen und möchte darauf hinweisen, dass Sie in Ihrem Fall anscheinend versuchen, mit dem umzugehen, was er als Nutzungsfehler bezeichnet.

In diesem Fall ist kein neuer Typ erforderlich, um dies anzuzeigen, sondern eine bessere Fehlermeldung darüber, was ihn verursacht hat. Als solche Helferfunktion entweder:

  1. Generieren Sie die Textzeichenfolge, die in die Ausnahme eingefügt werden soll
  2. Generieren Sie die gesamte Ausnahme und Nachricht

Ist was erforderlich.

Ansatz 1 ist klarer, kann aber zu einer etwas ausführlicheren Verwendung führen. 2 ist das Gegenteil und tauscht eine Terser-Syntax gegen weniger Klarheit.

Beachten Sie, dass die Funktionen äußerst sicher sein müssen (sie sollten niemals selbst eine nicht verwandte Ausnahme verursachen) und nicht die Bereitstellung von Daten erzwingen müssen, die bei bestimmten angemessenen Verwendungszwecken optional sind.

Wenn Sie einen dieser Ansätze verwenden, können Sie die Fehlermeldung später bei Bedarf einfacher internationalisieren.

Ein Stack-Trace mit mindestens einem Minimum gibt Ihnen die Funktion und möglicherweise die Zeilennummer. Daher sollten Sie sich darauf konzentrieren, Informationen bereitzustellen, die daraus nicht einfach zu ermitteln sind.

ShuggyCoUk
quelle
Ja, jetzt verstehe ich. Ich mache viele Verwendungsfehler und Sanity Checks, obwohl die Sanity Checks wahrscheinlich verschoben werden sollten, um assert () -Aufrufe php.net/manual/en/function.assert.php aufzurufen .
Timoxley
Krzysztofs Blog schlägt vor, vorhandene Typen wiederzuverwenden. dh. FileNotFoundException, NullArgumentException usw. PHP hat jedoch nur einen Ausnahmetyp - Exception. Es kann erweitert werden, aber das ist das einzige, das es standardmäßig enthält und idk, das an und für sich besonders nützlich ist ...
neubert
Tipp: Die Standard-PHP-Bibliothek (SPL) bietet eine Reihe integrierter Ausnahmen. php.net/manual/en/spl.exceptions.php
GDmac
21

Ich werde nicht von den Ratschlägen in Bezug auf Krzysztofs Blog ablenken, aber hier ist eine kinderleichte Möglichkeit, benutzerdefinierte Ausnahmen zu erstellen.

Beispiel:

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

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

Der Code dahinter (den ich irgendwo ausgeliehen habe, entschuldige mich bei wem auch immer das war)

<?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()}";
    }
}
Orwellophil
quelle
1
+1 für den ersten Ort, den ich gefunden habe, der mir tatsächlich sagte, wie man eine PHP-Ausnahme definiert und wie man sie auslöst
Brian Underwood
Meine große Frage hier ist, wie zum Teufel haben Sie die öffentliche Funktion getCode () überschrieben ?
Alex Barker
Guter Gott, dieser Code wurde ursprünglich vor 7 Jahren geschrieben (ich habe gerade herausgefunden, wer ihn geschrieben hat). Es ist immer noch der zweitbeste Kommentar auf der Ausnahmeseite von php.net. Ich habe PHP 5.3 ausgeführt, als ich es kopiert habe, und ich verwende jetzt PHP 5.4. Was in PHP 8 eine endgültige Methode sein kann oder nicht (mein Kommentar dort zukunftssicher), geht mich nichts an :) php.net/manual/en/language.exceptions.php#91159
Orwellophile
3
Wenn Sie diese Antwort ablehnen, tun Sie dies bitte nicht mit einer Erklärung. Ich habe es gerade gegen PHP 7.0.6 und PHP 5.4,43 ohne Probleme überprüft.
Orwellophile
11

Weitere Informationen zum Entwerfen von Ausnahmehierarchien finden Sie im Blog von Krzysztof Cwalina, einem Mitautor der "Framework Design Guidelines".

John Saunders
quelle
Es gibt einige gute Punkte, aber ich bin nicht zu 100% verkauft. Ich mag dieses Konzept:
Timoxley
"Ausnahmen, die in der Datenzugriffsschicht auftreten, werden protokolliert und dann in eine andere Ausnahme eingeschlossen, die der aufrufenden Schicht aussagekräftigere Informationen liefert ...
timoxley
.... Innerhalb der Geschäftskomponentenschicht werden die Ausnahmen protokolliert, bevor sie weitergegeben werden. Alle Ausnahmen, die in der Geschäftskomponentenschicht auftreten und vertrauliche Informationen enthalten, werden durch Ausnahmen ersetzt, die diese Informationen nicht mehr enthalten. ...
Timoxley
... Diese werden an die Benutzeroberfläche gesendet und dem Benutzer angezeigt. "Von msdn.microsoft.com/en-us/library/cc511859.aspx# Dies lässt mich über die Verantwortungskette nachdenken Entwurfsmuster: en.wikipedia.org/wiki/Chain_of_responsibility_pattern#PHP
timoxley
1
@ Mickmackusa Im Jahr 2009 nicht so sehr.
John Saunders
3

Vertrauen Sie niemals einem Benutzer, dass er das Richtige tut, und geben Sie Informationen zum Debuggen an. Wenn Sie Informationen wünschen, müssen Sie diese selbst sammeln und an einem Ort speichern, an dem sie zugänglich sind.

Wie bereits erwähnt, werden die Benutzer es vermeiden, wenn es schwierig ist, etwas zu tun. Verlassen Sie sich also auch hier nicht auf ihren guten Willen und ihr Wissen darüber, was sie senden müssen.

Dieses Denken impliziert eine Methode, mit der Sie die Informationen sammeln und protokollieren, was impliziert, dass var_dump () irgendwo verwendet wird.

Wie von Mark Harrison gesagt, ist eine Schaltfläche, die es einfach macht, irgendwo eine Fehlermeldung zu senden, für Sie und die Benutzer fantastisch. Es macht es ihnen leicht, einen Fehler zu melden. Sie (als Empfänger) erhalten viele Duplikate, aber doppelte Informationen sind besser als keine Informationen.

Matthew Farwell
quelle
0

Egal wie viele Details Sie hinzufügen, seien Sie sicher und beides

  • machen Sie es einfach, das Ganze auszuschneiden und einzufügen, oder
  • haben eine Schaltfläche, die den Fehler für sie meldet
Mark Harrison
quelle
2
... obwohl dies eher eine letzte Maßnahme als ein Architekturproblem ist
Timoxley