Ich ringe immer wieder damit. Ich versuche, die richtige Balance zwischen Versuch / Fang und Code zu finden, und werde nicht zu einem obszönen Durcheinander von Tabs, Klammern und Ausnahmen, die wie eine heiße Kartoffel auf den Call-Stack zurückgeworfen werden. Ich habe zum Beispiel eine App, die ich gerade entwickle und die SQLite verwendet. Ich habe eine Datenbankschnittstelle, die die SQLite-Aufrufe abstrahiert, und ein Modell, das Dinge akzeptiert, die in die / aus der Datenbank gelangen ... Wenn also eine SQLite-Ausnahme auftritt, muss sie an das Modell übergeben werden (von wem sie aufgerufen wurde) ), der es an denjenigen weitergeben muss, der den AddRecord / DeleteRecord / whatever aufgerufen hat ...
Ich bin ein Fan von Ausnahmen im Gegensatz zur Rückgabe von Fehlercodes, da Fehlercodes ignoriert, vergessen usw. werden können, während eine Ausnahme im Wesentlichen behandelt werden muss (zugegeben, ich könnte sie abfangen und sofort weitermachen ...) Sicher, es muss einen besseren Weg geben als das, was ich gerade vorhabe.
Edit: Ich hätte das etwas anders formulieren sollen. Ich verstehe es als verschiedene Typen und solche wiederzuwerfen, ich habe das schlecht formuliert und das ist meine eigene Schuld. Meine Frage ist ... wie hält man den Code dabei am besten sauber? Nach einer Weile fühlt es sich für mich sehr unübersichtlich an.
quelle
Antworten:
Stellen Sie sich das als starke Typisierung vor, auch wenn Sie keine stark typisierte Sprache verwenden. Wenn Ihre Methode den erwarteten Typ nicht zurückgeben kann, sollte sie eine Ausnahme auslösen.
Anstatt die SQLException bis zum Modell (oder schlimmer noch bis zur Benutzeroberfläche) auszulösen, sollte jede Ebene bekannte Ausnahmen abfangen und diese durch für diese Ebene geeignete Ausnahmen umbrechen / mutieren / ersetzen:
Dies sollte dazu beitragen, die Anzahl der Ausnahmen, nach denen Sie in jeder Ebene suchen, zu begrenzen und ein organisiertes Ausnahmesystem aufrechtzuerhalten.
quelle
throws SQLException
eine Methode zu haben , die nicht impliziert, dass SQL überhaupt involviert ist. Und was passiert, wenn Sie entscheiden, dass einige Vorgänge in einen Dateispeicher verschoben werden sollen? Jetzt müssen Sie deklarierenthrows SQLException, IOException
, etc. Es wird außer Kontrolle geraten.Ausnahmen ermöglichen es, saubereren Code zu schreiben, da sich der Großteil des Codes um den Normalfall kümmert und die Ausnahmefälle später auch in einem anderen Kontext behandelt werden können.
Die Regel für das Behandeln (Abfangen) von Ausnahmen lautet, dass dies durch einen Kontext erfolgen muss, der tatsächlich etwas dagegen tun kann. Das hat aber eine Ausnahme:
Ausnahmen müssen an Modulgrenzen (insbesondere Ebenengrenzen) abgefangen werden, und selbst wenn sie nur eingeschlossen werden sollen, muss eine Ausnahme höherer Ebene ausgelöst werden, die für den Aufrufer von Bedeutung ist. Jedes Modul und jeder Layer muss seine Implementierungsdetails auch in Bezug auf Ausnahmen verbergen (ein Heap-Modul kann HeapFull auslösen, aber niemals ArrayIndexOutOfBounds).
In Ihrem Beispiel ist es unwahrscheinlich, dass die oberen Schichten etwas gegen eine SQLite-Ausnahme unternehmen können (wenn dies der Fall ist, ist alles so an SQLite gekoppelt, dass Sie die Datenschicht nicht auf etwas anderes umstellen können). Es gibt eine Handvoll vorhersehbarer Gründe, warum Dinge wie Hinzufügen / Löschen / Aktualisieren fehlschlagen, und einige davon (inkompatible Änderungen bei gleichzeitigen Transaktionen) lassen sich selbst in der Daten- / Persistenzschicht nicht wiederherstellen (Verstoß gegen Integritätsregeln, z. B.). Die Persistenzebene sollte die Ausnahmen in aussagekräftige Ausdrücke der Modellebene übersetzen, damit die oberen Ebenen entscheiden können, ob sie es erneut versuchen oder ordnungsgemäß fehlschlagen.
quelle
In der Regel sollten Sie nur bestimmte Ausnahmen (z. B. IOException) abfangen und nur dann, wenn Sie nach dem Abfangen der Ausnahme etwas Bestimmtes zu tun haben.
Andernfalls ist es oft am besten, Ausnahmen an die Oberfläche sprudeln zu lassen, damit sie freigelegt und behandelt werden können. Einige Leute nennen das Fail-Fast.
Sie sollten eine Art Handler im Stammverzeichnis Ihrer Anwendung haben, um unbehandelte Ausnahmen zu erfassen, die von unten in die Luft gesprudelt sind. Auf diese Weise haben Sie die Möglichkeit, die Ausnahme in geeigneter Weise darzustellen, zu melden oder zu verwalten.
Das Umbrechen von Ausnahmen ist nützlich, wenn Sie eine Ausnahme auf einem verteilten System auslösen müssen und der Client nicht über die Definition des serverseitigen Fehlers verfügt.
quelle
Stellen Sie sich vor, Sie schreiben eine Stapelklasse. Sie fügen keinen Code für die Ausnahmebehandlung in die Klasse ein. Dies kann zu den folgenden Ausnahmen führen.
Bei einem vereinfachten Ansatz zum Umschließen von Ausnahmen werden möglicherweise beide Ausnahmen in eine StackError-Ausnahmeklasse eingeschlossen. Hier geht es jedoch nicht darum, Ausnahmen zu verpacken. Wenn ein Objekt eine Low-Level-Ausnahme auslöst, bedeutet dies, dass das Objekt beschädigt ist. Es gibt jedoch einen Fall, in dem dies akzeptabel ist: Wenn das Objekt tatsächlich beschädigt ist.
Der Punkt beim Umschließen von Ausnahmen ist, dass das Objekt ordnungsgemäße Ausnahmen für normale Fehler gibt. Der Stack sollte StackEmpty und nicht ArrayIndexError auslösen, wenn Sie von einem leeren Stack abspringen. Es ist nicht beabsichtigt , andere Ausnahmen auszulösen, wenn das Objekt oder der Code beschädigt ist.
Was wir wirklich vermeiden wollen, ist das Abfangen von Ausnahmen auf niedriger Ebene, die durch Objekte auf hoher Ebene geleitet wurden. Eine Stapelklasse, die einen ArrayIndexError auslöst, wenn sie von einem leeren Stapel abruft, ist ein geringfügiges Problem. Wenn Sie diesen ArrayIndexError tatsächlich abfangen, haben wir ein ernstes Problem. Die Ausbreitung von Fehlern auf niedriger Ebene ist eine weitaus weniger schwerwiegende Sünde, als sie zu fangen.
Um dies auf Ihr Beispiel einer SQLException zurückzuführen: Warum erhalten Sie SQLExceptions? Ein Grund ist, dass Sie ungültige Abfragen übergeben. Wenn Ihre Datenzugriffsebene jedoch fehlerhafte Abfragen generiert, ist sie beschädigt. Es sollte nicht versucht werden, seine Brokenness in eine DataAccessFailure-Ausnahme umzubrechen.
Eine SQLException kann jedoch auch aufgrund eines Verbindungsverlusts zur Datenbank auftreten. Meine Strategie in diesem Punkt besteht darin, die Ausnahme an der letzten Verteidigungslinie abzufangen, dem Benutzer zu melden, dass die Datenbankkonnektivität unterbrochen und heruntergefahren wurde. Da die Anwendung keinen Zugriff auf die Datenbank hat, kann nicht viel mehr getan werden.
Ich weiß nicht, wie Ihr Code aussieht. Aber es hört sich so an, als würden Sie alle Ausnahmen blind in übergeordnete Ausnahmen übersetzen. Sie sollten dies nur in relativ wenigen Fällen tun. Die meisten Ausnahmen auf niedrigeren Ebenen weisen auf Fehler in Ihrem Code hin. Das Fangen und Umwickeln ist kontraproduktiv.
quelle