Was sind die besten Methoden zum Abfangen und erneuten Auslösen von Ausnahmen?


156

Sollten abgefangene Ausnahmen direkt erneut ausgelöst werden oder sollten sie um eine neue Ausnahme gewickelt werden?

Das heißt, sollte ich das tun:

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

oder dieses:

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

Wenn Sie direkt werfen möchten, schlagen Sie bitte die Verwendung der Ausnahmeverkettung vor . Ich kann kein reales Szenario verstehen, in dem wir die Ausnahmeverkettung verwenden.

Antworten:


287

Sie sollten die Ausnahme nicht abfangen, es sei denn, Sie beabsichtigen, etwas Sinnvolles zu tun .

"Etwas Sinnvolles" könnte eine davon sein:

Behandlung der Ausnahme

Die naheliegendste sinnvolle Aktion besteht darin, die Ausnahme zu behandeln, z. B. indem eine Fehlermeldung angezeigt und der Vorgang abgebrochen wird:

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

Protokollierung oder teilweise Bereinigung

Manchmal wissen Sie nicht, wie Sie eine Ausnahme in einem bestimmten Kontext richtig behandeln sollen. Vielleicht fehlen Ihnen Informationen über das "große Ganze", aber Sie möchten den Fehler so nah wie möglich an dem Punkt protokollieren, an dem er aufgetreten ist. In diesem Fall möchten Sie möglicherweise Folgendes fangen, protokollieren und erneut werfen:

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

In einem verwandten Szenario sind Sie am richtigen Ort, um eine Bereinigung für den fehlgeschlagenen Vorgang durchzuführen, aber nicht um zu entscheiden, wie der Fehler auf der obersten Ebene behandelt werden soll. In früheren PHP-Versionen wurde dies als implementiert

$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 hat das finallySchlüsselwort eingeführt. Für Bereinigungsszenarien gibt es jetzt eine andere Möglichkeit, dies zu erreichen. Wenn der Bereinigungscode ausgeführt werden muss, unabhängig davon, was passiert ist (dh sowohl bei Fehlern als auch bei Erfolg), ist dies jetzt möglich, während die Auslösung von ausgelösten Ausnahmen transparent ist:

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

Fehlerabstraktion (mit Ausnahme der Verkettung)

Ein dritter Fall ist, wenn Sie viele mögliche Fehler logisch unter einem größeren Dach zusammenfassen möchten. Ein Beispiel für eine logische Gruppierung:

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

In diesem Fall möchten Sie nicht, dass die Benutzer von Componentwissen, dass es über eine Datenbankverbindung implementiert wird (möglicherweise möchten Sie Ihre Optionen offen halten und in Zukunft dateibasierten Speicher verwenden). Ihre Spezifikation für Componentwürde also sagen, dass "im Falle eines Initialisierungsfehlers ComponentInitExceptionausgelöst wird". Dies ermöglicht es Verbrauchern Component, Ausnahmen des erwarteten Typs abzufangen und gleichzeitig dem Debugging-Code den Zugriff auf alle (implementierungsabhängigen) Details zu ermöglichen .

Bereitstellung eines umfassenderen Kontexts (mit Ausnahme der Verkettung)

Schließlich gibt es Fälle, in denen Sie möglicherweise mehr Kontext für die Ausnahme bereitstellen möchten. In diesem Fall ist es sinnvoll, die Ausnahme in eine andere zu packen, die weitere Informationen darüber enthält, was Sie versucht haben, als der Fehler aufgetreten ist. Beispielsweise:

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

Dieser Fall ähnelt dem obigen (und das Beispiel ist wahrscheinlich nicht das beste, das man sich vorstellen kann), zeigt jedoch, warum mehr Kontext bereitgestellt werden muss: Wenn eine Ausnahme ausgelöst wird, wird angezeigt, dass die Dateikopie fehlgeschlagen ist. Aber warum ist es gescheitert? Diese Informationen werden in den umschlossenen Ausnahmen bereitgestellt (von denen es mehr als eine Ebene geben könnte, wenn das Beispiel viel komplizierter wäre).

Der Wert dieser Vorgehensweise wird veranschaulicht, wenn Sie an ein Szenario denken, in dem beispielsweise beim Erstellen eines UserProfileObjekts Dateien kopiert werden, weil das Benutzerprofil in Dateien gespeichert ist und die Transaktionssemantik unterstützt: Sie können Änderungen "rückgängig machen", da sie nur für a ausgeführt werden Kopie des Profils, bis Sie festschreiben.

In diesem Fall, wenn Sie es getan haben

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

Wenn als Ergebnis ein Ausnahmefehler "Zielverzeichnis konnte nicht erstellt werden" abgefangen wird, besteht ein Recht auf Verwirrung. Das Umschließen dieser "Kern" -Ausnahme in Schichten anderer Ausnahmen, die Kontext bereitstellen, erleichtert die Behandlung des Fehlers erheblich ("Erstellen der Profilkopie fehlgeschlagen" -> "Dateikopiervorgang fehlgeschlagen" -> "Zielverzeichnis konnte nicht erstellt werden").


Ich bin nur mit den letzten 2 Gründen einverstanden: 1 / Behandlung der Ausnahme: Sie sollten es nicht auf dieser Ebene tun, 2 / Protokollierung oder Bereinigung: Verwenden Sie schließlich und protokollieren Sie die Ausnahme über Ihrer Datenschicht
Remi Bourgarel

1
@remi: außer dass PHP das finallyKonstrukt nicht unterstützt (zumindest noch nicht) ... Das ist also raus, was bedeutet, dass wir auf schmutzige Dinge wie diese zurückgreifen müssen ...
ircmaxell

@remibourgarel: 1: Das war nur ein Beispiel. Natürlich sollten Sie es auf dieser Ebene nicht tun, aber die Antwort ist lang genug. 2: Wie @ircmaxell sagt, gibt es finallyin PHP kein .
Jon

3
Schließlich implementiert PHP 5.5 nun endlich.
OCDev

12
Es gibt einen Grund, den Sie meiner Meinung nach von Ihrer Liste hier verpasst haben - Sie können möglicherweise nicht feststellen, ob Sie eine Ausnahme behandeln können, bis Sie sie abgefangen haben und die Möglichkeit hatten, sie zu überprüfen. Beispielsweise kann ein Wrapper für eine untergeordnete API, die Fehlercodes verwendet (und zig Millionen davon enthält), eine einzelne Ausnahmeklasse haben, von der eine Instanz für jeden Fehler ausgelöst wird, mit einer error_codeEigenschaft, die überprüft werden kann, um den zugrunde liegenden Fehler abzurufen Code. Wenn Sie nur in der Lage sind, einige dieser Fehler sinnvoll zu behandeln, möchten Sie sie wahrscheinlich abfangen, überprüfen und wenn Sie den Fehler nicht behandeln können - erneut werfen.
Mark Amery

37

Nun, es geht darum, die Abstraktion aufrechtzuerhalten. Daher würde ich vorschlagen, die Ausnahmeverkettung zu verwenden, um direkt zu werfen. Lassen Sie mich das Konzept der undichten Abstraktionen erklären

Angenommen, Sie bauen ein Modell. Das Modell soll die gesamte Datenpersistenz und -validierung vom Rest der Anwendung abstrahieren. Was passiert nun, wenn Sie einen Datenbankfehler erhalten? Wenn Sie das erneut werfen DatabaseQueryException, verlieren Sie die Abstraktion. Um zu verstehen, warum, denken Sie eine Sekunde über die Abstraktion nach. Es ist Ihnen egal, wie das Modell die Daten speichert, nur dass dies der Fall ist. Ebenso ist es Ihnen egal, was in den zugrunde liegenden Systemen des Modells schief gelaufen ist, nur dass Sie wissen, dass etwas schief gelaufen ist und ungefähr, was schief gelaufen ist.

Wenn Sie also die DatabaseQueryException erneut auslösen, verlieren Sie die Abstraktion und benötigen den aufrufenden Code, um die Semantik der Vorgänge unter dem Modell zu verstehen. Erstellen Sie stattdessen ein Generikum ModelStorageExceptionund wickeln Sie das gefangene DatabaseQueryExceptiondarin ein. Auf diese Weise kann Ihr aufrufender Code weiterhin versuchen, den Fehler semantisch zu behandeln, aber es spielt keine Rolle, welche Technologie dem Modell zugrunde liegt, da Sie nur Fehler aus dieser Abstraktionsschicht offenlegen. Noch besser ist, dass Sie, da Sie die Ausnahme umbrochen haben, wenn sie bis zum Anschlag sprudelt und protokolliert werden muss, die ausgelöste Root-Ausnahme verfolgen können (entlang der Kette gehen), sodass Sie immer noch alle Debugging-Informationen haben, die Sie benötigen!

Fangen Sie nicht einfach dieselbe Ausnahme ab und werfen Sie sie erneut, es sei denn, Sie müssen eine Nachbearbeitung durchführen. Aber ein Block wie } catch (Exception $e) { throw $e; }ist sinnlos. Sie können die Ausnahmen jedoch erneut einschließen, um einen signifikanten Abstraktionsgewinn zu erzielen.


2
Gute Antwort. Es scheint, dass einige Leute um Stack Overflow (basierend auf Antworten usw.) sie irgendwie falsch verwenden.
James

8

IMHO, eine Ausnahme zu fangen, um sie einfach wieder zu werfen, ist nutzlos . In diesem Fall fangen Sie es einfach nicht ab und lassen Sie früher aufgerufene Methoden damit umgehen (auch bekannt als Methoden, die im Aufrufstapel 'oben' sind) .

Wenn Sie es erneut werfen, ist es auf jeden Fall eine gute Vorgehensweise, die abgefangene Ausnahme mit der neuen zu verketten, die Sie auslösen, da die Informationen, die die abgefangene Ausnahme enthält, beibehalten werden. Ein erneutes Werfen ist jedoch nur dann sinnvoll, wenn Sie der abgefangenen Ausnahme Informationen hinzufügen oder etwas behandeln. Dies kann ein Kontext, Werte, Protokollierung, Freigabe von Ressourcen usw. sein.

Ein Weg , um einige Informationen hinzuzufügen ist , die zu verlängern ExceptionKlasse, haben Ausnahmen wie NullParameterException, DatabaseExceptionusw. Mehr über dieses ermöglichen die developper nur einige Ausnahmen zu fangen , dass er verarbeiten kann. Zum Beispiel kann man nur fangen DatabaseExceptionund versuchen zu lösen, was das verursacht hat Exception, wie das Wiederverbinden mit der Datenbank.


2
Es ist nicht nutzlos, es gibt Zeiten, in denen Sie etwas für eine Ausnahme tun müssen, z. B. in der Funktion, die es auslöst, und es dann erneut auslösen müssen, damit ein höherer Fang etwas anderes tun kann. In einem der Projekte, an denen ich arbeite, fangen wir manchmal eine Ausnahme in einer Aktionsmethode ab, zeigen dem Benutzer eine freundliche Benachrichtigung an und werfen sie dann erneut, damit ein weiter unten im Code stehender try catch-Block sie erneut abfangen kann, um den Fehler zu protokollieren Ein Holzklotz.
MitMaro

1
Wie gesagt, Sie fügen der Ausnahme einige Informationen hinzu (Anzeigen eines Hinweises, Protokollieren). Sie werfen es nicht einfach wie im Beispiel des OP neu.
Clement Herreman

2
Nun, Sie können es einfach erneut werfen, wenn Sie Ressourcen schließen müssen, aber keine zusätzlichen Informationen hinzufügen müssen. Ich bin damit einverstanden, dass es nicht die sauberste Sache der Welt ist, aber es ist nicht schrecklich
ircmaxell

2
@ircmaxell Einverstanden, bearbeitet, um zu reflektieren, dass es nur dann nutzlos ist, wenn Sie nichts tun, außer es erneut zu werfen
Clement Herreman

1
Das Wichtige ist, dass Sie die Datei- und / oder Zeileninformationen darüber verlieren, wo die Ausnahme ursprünglich ausgelöst wurde, indem Sie sie erneut auslösen. Daher ist es normalerweise besser, einen neuen zu werfen und den alten weiterzugeben, wie im zweiten Beispiel der Frage. Andernfalls zeigt es nur auf den Catch-Block und lässt Sie erraten, was das eigentliche Problem war.
DanMan

2

Sie müssen sich die Best Practices für Ausnahmen in PHP 5.3 ansehen

Die Ausnahmebehandlung in PHP ist keineswegs neu. Unter dem folgenden Link sehen Sie zwei neue Funktionen in PHP 5.3, die auf Ausnahmen basieren. Die erste ist verschachtelte Ausnahmen und die zweite ist eine neue Reihe von Ausnahmetypen, die von der SPL-Erweiterung angeboten werden (die jetzt eine Kernerweiterung der PHP-Laufzeit ist). Beide neuen Funktionen haben ihren Weg in das Buch der Best Practices gefunden und verdienen eine eingehende Prüfung.

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


1

Normalerweise denken Sie so.

Eine Klasse kann viele Arten von Ausnahmen auslösen, die nicht übereinstimmen. Sie erstellen also eine Ausnahmeklasse für diese Klasse oder diesen Klassentyp und werfen diese aus.

Der Code, der die Klasse verwendet, muss also nur einen Ausnahmetyp abfangen.


1
Hey, kannst du bitte mehr Details oder einen Link bereitstellen, über den ich mehr über diesen Ansatz lesen kann?
Rahul Prasad
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.