So schreiben Sie eine gute Ausnahmemeldung

101

Ich führe gerade eine Codeüberprüfung durch, und eines der Dinge, die ich bemerke, ist die Anzahl der Ausnahmen, bei denen die Ausnahmemeldung lediglich zu wiederholen scheint, wo die Ausnahme aufgetreten ist. z.B

throw new Exception("BulletListControl: CreateChildControls failed.");

Alle drei Punkte in dieser Nachricht kann ich aus dem Rest der Ausnahme herausarbeiten. Ich kenne die Klasse und Methode aus dem Stack-Trace und weiß, dass dies fehlgeschlagen ist (da ich eine Ausnahme habe).

Ich habe darüber nachgedacht, welche Meldung ich in Ausnahmemeldungen eingefügt habe. Zuerst erstelle ich eine Ausnahmeklasse, falls noch keine vorhanden ist, aus dem allgemeinen Grund (z. B. PropertyNotFoundExceptiondem Warum ) und dann, wenn ich sie ausspreche, zeigt die Nachricht an, was schief gelaufen ist (z. B. "Die Eigenschaft 'IDontExist' konnte auf Knoten 1234 nicht gefunden werden "- das was ). Das wo ist in der StackTrace. Das Wann wird möglicherweise im Protokoll vermerkt (falls zutreffend). Das Wie soll der Entwickler herausfinden (und beheben)

Haben Sie weitere Tipps zum Auslösen von Ausnahmen? Speziell im Hinblick auf das Anlegen neuer Typen und die Ausnahmemeldung.

Colin Mackay
quelle
4
Sind diese für Logfiles oder für den Benutzer zu präsentieren?
Jon Hopkins
5
Nur zum Debuggen. Sie können in einem Protokoll enden. Sie würden dem Benutzer nicht präsentiert. Ich bin kein Fan davon, dem Benutzer Ausnahmemeldungen zu präsentieren.
Colin Mackay

Antworten:

70

Ich werde meine Antwort mehr auf das richten, was nach einer Ausnahme kommt: Wofür ist es gut und wie sollte sich Software verhalten, was sollten Ihre Benutzer mit der Ausnahme tun? Eine großartige Technik, auf die ich zu Beginn meiner Karriere gestoßen bin, bestand darin, Probleme und Fehler immer in drei Teilen zu melden: Kontext, Problem und Lösung. Die Verwendung dieser Disziplin verändert die Fehlerbehandlung enorm und verbessert die Benutzerfreundlichkeit der Software erheblich.

Hier sind einige Beispiele.

Context: Saving connection pooling configuration changes to disk.
Problem: Write permission denied on file '/xxx/yyy'.
Solution: Grant write permission to the file.

In diesem Fall weiß der Bediener genau, was zu tun ist und welche Datei betroffen sein muss. Sie wissen auch, dass die Änderungen am Verbindungspool nicht übernommen wurden und wiederholt werden sollten.

Context: Sending email to '[email protected]' regarding 'Blah'.
Problem: SMTP connection refused by server 'mail.xyz.com'.
Solution: Contact the mail server administrator to report a service problem.  The email will be sent later. You may want to tell '[email protected]' about this problem.

Ich schreibe serverseitige Systeme und meine Bediener sind in der Regel technisch versierte First-Line-Support. Ich würde die Nachrichten für Desktop-Software, die eine andere Zielgruppe haben, aber die gleichen Informationen enthalten, anders schreiben.

Wenn man diese Technik anwendet, passieren mehrere wunderbare Dinge. Der Softwareentwickler ist oft am besten in der Lage, die Probleme in seinem eigenen Code zu lösen. Daher ist die Codierung von Lösungen auf diese Weise beim Schreiben des Codes für Endbenutzer von großem Vorteil, die nachteilig Lösungen finden, da ihnen häufig Informationen zu fehlen was genau die Software tat. Jeder, der jemals eine Oracle-Fehlermeldung gelesen hat, weiß, was ich meine.

Die zweite wunderbare Sache, die mir einfällt, ist, wenn Sie versuchen, eine Lösung in Ihrer Ausnahme zu beschreiben, und Sie schreiben "Check X und wenn A, dann B, dann C". Dies ist ein sehr klares und offensichtliches Zeichen dafür, dass Ihre Ausnahme an der falschen Stelle überprüft wird. Sie, der Programmierer, haben die Fähigkeit, Dinge im Code zu vergleichen. Wenn Anweisungen im Code ausgeführt werden sollen, warum sollte der Benutzer dann an etwas beteiligt sein, das automatisiert werden kann? Die Wahrscheinlichkeit ist groß, dass es sich um einen tieferen Code handelt, und jemand hat das Faule getan und IOException von einer beliebigen Anzahl von Methoden geworfen und potenzielle Fehler von allen in einem Block von aufrufendem Code aufgefangen, der nicht ausreichend beschreiben kann, was falsch gelaufen ist, was der spezifische Code istKontext ist und wie man es behebt. Dies ermutigt Sie, feinere Körnungsfehler zu schreiben, diese zu erfassen und an der richtigen Stelle in Ihrem Code zu behandeln, damit Sie die Schritte, die der Bediener ausführen sollte, richtig artikulieren können.

In einem Unternehmen hatten wir erstklassige Betreiber, die die Software sehr gut kennengelernt und ein eigenes "Run Book" geführt haben, das unsere Fehlerberichte und Lösungsvorschläge vervollständigte. Um dies zu erkennen, hat die Software in Ausnahmefällen Wiki-Links zum Runbook hinzugefügt, sodass eine grundlegende Erklärung zur Verfügung stand sowie Links zu weiterführenden Diskussionen und Beobachtungen der Bediener im Laufe der Zeit.

Wenn Sie die Disziplin hatten, diese Technik auszuprobieren, wird es viel offensichtlicher, wie Sie Ihre Ausnahmen im Code benennen sollten, wenn Sie Ihre eigenen erstellen. NonRecoverableConfigurationReadFailedException wird zu einer Abkürzung für das, was Sie dem Bediener genauer beschreiben werden. Ich mag es, wortreich zu sein, und ich denke, das wird dem nächsten Entwickler, der meinen Code berührt, leichter fallen, ihn zu interpretieren.

Sir Wobin
quelle
1
+1 Dies ist ein gutes System. Was ist wichtiger: Sicher sein, dass die Informationen vermittelt werden, oder kurze Worte verwenden?
Michael K
3
+1 für Ich mag die Lösung der Aufnahme, Kontext, Problem, Lösung
WebDev
1
Diese Technik ist so hilfreich. Ich werde es auf jeden Fall nutzen.
Kid Diamond
Kontext sind unnötige Daten. Es ist bereits im Stack-Trace vorhanden. Eine Lösung zu haben ist vorzuziehen, aber nicht immer möglich / nützlich. Die meisten Probleme ist eine Art anmutig Anwendung stoppen oder ausstehenden Aktionen ignorieren und zurück zur Anwendung Hauptausführungsschleife der Hoffnung , dass Sie das nächste Mal erfolgreich sein ... Name der Ausnahmeklasse sollte so sein , dass Lösung offensichtlich werden wird, für FileNotFoundoder ConnectExceptionSie wissen , was zu tun ) )
Gavenkoa
2
@ThomasFlinkow In Ihrem Beispiel haben Traces init (), execute () und cleanup () im Stack-Trace. Mit einem guten Namensschema in der Bibliothek und einer sauberen / verständlichen API benötigen Sie keine Zeichenfolgenerklärungen. Und scheitern Sie schnell, tragen Sie keinen kaputten Zustand im ganzen System. Ablaufverfolgungen und Protokollierungsdatensätze mit eindeutiger ID können den Anwendungsfluss / -status erläutern.
Gavenkoa
23

In dieser neueren Frage habe ich darauf hingewiesen, dass Ausnahmen überhaupt keine Nachricht enthalten sollten. Meiner Meinung nach ist die Tatsache, dass sie dies tun, ein großes Missverständnis. Was ich vorschlage, ist das

Die "Nachricht" der Ausnahme ist der (vollständig qualifizierte) Klassenname der Ausnahme.

Eine Ausnahme sollte innerhalb ihrer eigenen Mitgliedsvariablen so viele Details wie möglich darüber enthalten, was genau passiert ist. Ein IndexOutOfRangeExceptionsollte beispielsweise den Indexwert enthalten, der als ungültig befunden wurde, sowie die oberen und unteren Werte, die zum Zeitpunkt des Auslösens der Ausnahme gültig waren. Auf diese Weise können Sie mithilfe von Reflection automatisch eine Meldung IndexOutOfRangeException: index = -1; min=0; max=5erstellen lassen, die wie folgt lautet: Dies sollte zusammen mit dem Stack-Trace alle objektiven Informationen enthalten, die Sie zur Behebung des Problems benötigen. Das Formatieren in eine hübsche Nachricht wie "Index -1 war nicht zwischen 0 und 5" fügt keinen Wert hinzu.

In Ihrem speziellen Beispiel NodePropertyNotFoundExceptionwürde die Klasse den Namen der Eigenschaft enthalten, die nicht gefunden wurde, und einen Verweis auf den Knoten, der die Eigenschaft nicht enthielt. Dies ist wichtig: Es sollte nicht den Namen des Knotens enthalten. es sollte einen Verweis auf den tatsächlichen Knoten enthalten. In Ihrem speziellen Fall ist dies möglicherweise nicht erforderlich, es handelt sich jedoch um ein Prinzip und eine bevorzugte Denkweise: Das Hauptanliegen beim Erstellen einer Ausnahme ist, dass sie von Code verwendet werden kann, der sie möglicherweise abfängt. Benutzerfreundlichkeit für den Menschen ist ein wichtiges, aber nur zweitrangiges Anliegen.

Dies behebt die sehr frustrierende Situation, die Sie möglicherweise zu einem bestimmten Zeitpunkt in Ihrer Karriere erlebt haben, in der Sie möglicherweise eine Ausnahme mit wichtigen Informationen über das Geschehen im Nachrichtentext, aber nicht in den Mitgliedervariablen abgefangen haben Ich musste den Text in Strings analysieren, um herauszufinden, was passiert ist, in der Hoffnung, dass der Nachrichtentext in zukünftigen Versionen der darunter liegenden Ebene gleich bleibt, und beten, dass der Nachrichtentext nicht in einer Fremdsprache vorliegt, wenn Ihr Programm ist in anderen Ländern laufen.

Da der Klassenname der Ausnahme die Nachricht der Ausnahme ist (und die Mitgliedsvariablen der Ausnahme die spezifischen Details sind), bedeutet dies natürlich, dass Sie sehr viele verschiedene Ausnahmen benötigen, um alle verschiedenen Nachrichten zu übermitteln, und das ist gut.

Jetzt stoßen wir manchmal beim Schreiben von Code auf eine fehlerhafte Situation, für die wir nur schnell eine throwAnweisung codieren und den Code schreiben möchten, anstatt zu unterbrechen, was wir tun, um eine neue Ausnahmeklasse zu erstellen, damit wir dies tun können wirf es genau dort hin. Für diese Fälle habe ich eine GenericExceptionKlasse, die tatsächlich eine Zeichenfolgenmeldung als Konstruktionszeitparameter akzeptiert, aber der Konstruktor dieser Ausnahmeklasse ist mit einem großen, großen, leuchtend violetten FIXME XXX TODOKommentar geschmückt, der besagt, dass jede einzelne Instanz dieser Klasse sein muss wird durch eine Instanziierung einer spezielleren Ausnahmeklasse ersetzt, bevor das Softwaresystem freigegeben wird, vorzugsweise bevor der Code festgeschrieben wird.

Mike Nakis
quelle
8
Wenn Sie in einer Sprache ohne GC wie C ++ arbeiten, sollten Sie sehr vorsichtig sein, wenn Sie in Ausnahmen, die Sie an den Stack senden, Verweise auf beliebige Daten einfügen. Möglicherweise ist alles, worauf Sie verweisen, zerstört, wenn die Ausnahme abgefangen wird.
Sebastian Redl
4
@SebastianRedl True. Dasselbe könnte für C # und Java gelten, wenn das nodeObjekt durch eine using-disposable-Klausel (in C #) oder eine try-with-resources-Klausel (in Java) geschützt wird: Das mit der Ausnahme gespeicherte Objekt wird freigegeben / geschlossen, wodurch es ungültig wird Zugriff darauf, um nützliche Informationen an der Stelle abzurufen, an der die Ausnahme behandelt wird. Ich nehme an, dass in diesen Fällen eine Art Zusammenfassung des Objekts in der Ausnahme statt des Objekts selbst gespeichert werden sollte. Ich kann mir keinen idiotensicheren Weg vorstellen, dies für alle Fälle generisch zu handhaben.
Mike Nakis
13

In der Regel sollte eine Ausnahme den Entwicklern helfen, die Ursache zu lokalisieren, indem nützliche Informationen (erwartete Werte, tatsächlicher Wert, mögliche Ursachen / Lösung usw.) angegeben werden.

Neue Ausnahmetypen sollten erstellt werden, wenn keiner der integrierten Typen sinnvoll ist . Mit einem bestimmten Typ können andere Entwickler eine bestimmte Ausnahme abfangen und damit umgehen. Wenn der Entwickler weiß, wie er mit Ihrer Ausnahme umgehen soll, der Typ jedoch so ist Exception, kann er sie nicht richtig behandeln.

mbillard
quelle
+1 - erwartete und tatsächliche Werte sind äußerst nützlich. In dem in der Frage angegebenen Beispiel sollten Sie nicht einfach sagen, dass eine Methode fehlgeschlagen ist, sondern warum sie fehlgeschlagen ist (im Grunde genommen der genaue Befehl, der fehlgeschlagen ist, und die Umstände, die den Fehler verursacht haben.)
Felix Dombek
4

In .NET nicht immer throw new Exception("...")(wie der Autor der Frage gezeigt hat). Die Ausnahme ist der Stamm-Ausnahmetyp und darf niemals direkt ausgelöst werden. Werfen Sie stattdessen einen der abgeleiteten .NET-Ausnahmetypen oder erstellen Sie eine eigene benutzerdefinierte Ausnahme, die von Exception (oder einem anderen Ausnahmetyp) abgeleitet ist.

Warum nicht Exception werfen? Weil das Auslösen von Exception nichts zur Beschreibung Ihrer Exception beiträgt und Ihren aufrufenden Code dazu zwingt, Code zu schreiben, wie er catch(Exception ex) { ... }im Allgemeinen nicht gut ist! :-).

bytedev
quelle
2

Die Dinge, nach denen Sie suchen möchten, um der Ausnahme "hinzuzufügen", sind diejenigen Datenelemente, die der Ausnahme oder der Stapelablaufverfolgung nicht inhärent sind. Ob diese Teil der "Nachricht" sind oder bei der Anmeldung angehängt werden müssen, ist eine interessante Frage.

Wie Sie bereits bemerkt haben, sagt Ihnen die Ausnahme wahrscheinlich, was, die Stapelspur sagt Ihnen wahrscheinlich, wo, aber das "Warum" kann mehr involviert sein (sollte es sein, würde man hoffen), als nur ein oder zwei Zeilen zu betrachten und zu sagen: " doh! Natürlich ". Dies gilt umso mehr, wenn Fehler im Produktionscode protokolliert werden. Ich bin allzu oft von schlechten Daten gebissen worden, die in ein Live-System eingedrungen sind, das es in unseren Testsystemen nicht gibt. Etwas so Einfaches wie das Erkennen der ID des Datensatzes in der Datenbank, der den Fehler verursacht (oder dazu beiträgt), kann erhebliche Zeitersparnis bedeuten.

Also ... Entweder aufgelistet oder für .NET zur Datenerfassung für protokollierte Ausnahmen hinzugefügt (siehe @Plip!):

  • Parameter (dies kann etwas interessant werden - Sie können der Datenerfassung nichts hinzufügen, wenn sie nicht serialisiert werden kann und manchmal kann ein einzelner Parameter überraschend komplex sein.)
  • Die von ADO.NET oder Linq an SQL oder ähnliches zurückgegebenen zusätzlichen Daten (dies kann auch ein bisschen interessant werden!).
  • Was auch immer sonst nicht offensichtlich sein mag.

Einige Dinge werden Sie natürlich nicht wissen müssen, bis Sie sie nicht in Ihrem anfänglichen Fehlerbericht / -protokoll haben. Einige Dinge, von denen Sie nicht wissen, dass Sie sie bekommen können, bis Sie feststellen, dass Sie sie brauchen.

Murph
quelle
0

Was sind Ausnahmen für ?

(1) Dem Benutzer mitteilen, dass ein Fehler aufgetreten ist?

Dies sollte ein letzter Ausweg sein, da Ihr Code eingreifen und ihnen etwas "Schöneres" als eine Ausnahme zeigen sollte.

Die "Fehler" -Meldung sollte klar und prägnant angeben, was schief gelaufen ist und was der Benutzer gegebenenfalls tun kann, um den Fehlerzustand zu beheben.

zB "Bitte drücken Sie diese Taste nicht noch einmal"

(2) Einem Entwickler mitteilen, wann ein Fehler aufgetreten ist?

Dies ist die Art von Dingen, die Sie für die nachfolgende Analyse in eine Datei einloggen.
Der Stack-Trace teilt dem Entwickler mit, wo der Code fehlerhaft war. Die Nachricht sollte wieder anzeigen, was schief gelaufen ist.

(3) Einem Exception-Handler (dh Code) mitteilen, dass ein Fehler aufgetreten ist?

Der Typ der Ausnahme entscheidet, welche Ausnahmebehandlungsroutine darauf zugreift, und die für das Ausnahmeobjekt definierten Eigenschaften ermöglichen es der Behandlungsroutine, damit umzugehen.

Die Nachricht der Ausnahme ist völlig irrelevant .

Phill W.
quelle
-3

Erstellen Sie keine neuen Typen, wenn Sie es verbessern können. Sie können zusätzliche Verwirrung und Komplexität verursachen und dazu führen, dass mehr Code gepflegt wird. Sie könnten dazu führen, dass Ihr Code erweitert werden musste. Das Entwerfen einer Ausnahmehierarchie erfordert jedoch eine Menge Tests. Es ist kein nachträglicher Gedanke. Es ist oft am besten, die eingebaute Sprachausnahmehierarchie zu verwenden.

Der Inhalt der Ausnahmemeldung hängt vom Empfänger der Meldung ab - Sie müssen sich also in die Lage dieser Person versetzen.

Ein Support-Techniker muss in der Lage sein, die Fehlerquelle so schnell wie möglich zu identifizieren. Fügen Sie eine kurze beschreibende Zeichenfolge sowie alle Daten hinzu, die zur Behebung des Problems beitragen können. Fügen Sie immer den Stack-Trace ein, wenn Sie können. Dies ist die einzig wahre Informationsquelle.

Die Darstellung von Fehlern für allgemeine Benutzer Ihres Systems hängt von der Art des Fehlers ab: Wenn der Benutzer das Problem beheben kann, indem er beispielsweise eine andere Eingabe vornimmt, ist eine kurze beschreibende Meldung erforderlich. Wenn der Benutzer das Problem nicht beheben kann, geben Sie am besten an, dass ein Fehler aufgetreten ist, und protokollieren / senden Sie einen Fehler an den Support (gemäß den obigen Richtlinien).

Auch - werfen Sie keinen großen "HALT ERROR!" Symbol. Es ist ein Fehler - es ist nicht das Ende der Welt.

Also zusammenfassend: Denken Sie über die Akteure und Anwendungsfälle für Ihr System nach. Versetzen Sie sich in die Lage dieses Benutzers. Sei hilfreich. Sei nett. Denken Sie in Ihrem Systemdesign darüber nach. Aus Sicht Ihrer Benutzer sind diese Ausnahmefälle und die Art und Weise, wie das System mit ihnen umgeht, genauso wichtig wie die normalen Fälle in Ihrem System.

Conor
quelle
10
Ich stimme dir nicht zu. Es gibt viele gute Gründe, eigene Ausnahmen zu implementieren, wenn die Sprach-API nicht genau Ihren Anforderungen entspricht. Ein Grund dafür ist, dass Sie in einer Methode, bei der mehrere Dinge fehlschlagen können, unterschiedliche catch-Klauseln für verschiedene Arten von Ausnahmen schreiben können, um auf das genaue Problem zu reagieren. Zum anderen können Sie mehrere Ebenen von Ausnahmen trennen, die verschiedene Abstraktionsebenen darstellen, wobei die genaue Ebene, zu der eine Ausnahme gehört, in ihrem Typ codiert werden kann. Nur "Exception" oder "IllegalStateException" und eine Nachrichtenzeichenfolge zu verwenden, hilft da nicht viel.
Felix Dombek
1
Ich stimme auch nicht zu. Ausnahmetypen sollten für den aufrufenden Code, der sie verwendet, sinnvoll sein. Wenn ich beispielsweise ein Framework aufrufe und dies intern eine FileDoesNotExistException verursacht, ist dies für mich als Aufrufer des Frameworks möglicherweise nicht sinnvoll. Stattdessen ist es möglicherweise besser, eine benutzerdefinierte Ausnahme zu erstellen und die ausgelöste Ausnahme als innere Ausnahme zu übergeben.
Bytedev