Ausnahmen - "was ist passiert" vs. "was ist zu tun?"

19

Wir verwenden Ausnahmen, damit der Benutzer des Codes auf nützliche Weise mit unerwartetem Verhalten umgehen kann. Normalerweise basieren Ausnahmen auf dem Szenario "Was ist passiert?" FileNotFound(Die angegebene Datei konnte nicht gefunden werden) oder ZeroDivisionError(die 1/0Operation konnte nicht ausgeführt werden).

Was ist, wenn die Möglichkeit besteht, das erwartete Verhalten des Verbrauchers festzulegen?

Stellen Sie sich zum Beispiel vor, wir haben eine fetchRessource, die HTTP-Anforderungen ausführt und abgerufene Daten zurückgibt. Und anstelle von Fehlern wie ServiceTemporaryUnavailableoder RateLimitExceededmöchten wir RetryableErrordem Verbraucher lediglich vorschlagen, die Anforderung erneut zu versuchen und sich nicht um bestimmte Fehler zu kümmern. Im Grunde schlagen wir dem Anrufer eine Aktion vor - das "was zu tun ist".

Wir tun dies nicht oft, weil wir nicht alle Verwendungszwecke von Verbrauchern kennen. Stellen Sie sich jedoch vor, dass dies eine bestimmte Komponente ist, mit der wir die besten Vorgehensweisen für einen Anrufer kennen. Sollten wir also den Ansatz "Was ist zu tun?" Anwenden?

Roman Bodnarchuk
quelle
3
Tut HTTP das nicht schon? 503 ist ein vorübergehendes Versäumnis zu antworten, so dass der Anforderer es erneut versuchen sollte, 404 ist eine grundlegende Abwesenheit, so dass es keinen Sinn macht, es erneut zu versuchen, 301 bedeutet "dauerhaft verschoben", so dass Sie es erneut versuchen sollten, aber mit einer anderen Adresse usw.
Kilian Foth
7
In vielen Fällen, wenn wir wirklich wissen, was zu tun ist, können wir den Computer einfach dazu bringen, dies automatisch zu tun, und der Benutzer muss nicht einmal wissen, dass etwas schief gelaufen ist. Ich nehme an, wenn mein Browser eine 301 empfängt, wird er einfach an die neue Adresse weitergeleitet, ohne mich zu fragen.
Ixrec
@Ixrec - hatte auch die gleiche Idee. Der Verbraucher möchte jedoch möglicherweise nicht auf eine andere Anforderung warten und den Artikel ignorieren oder vollständig fehlschlagen.
Roman Bodnarchuk
1
@ RomanBodnarchuk: Ich bin anderer Meinung. Es ist, als würde man sagen, dass man kein Chinesisch sprechen muss, um Chinesisch zu sprechen. HTTP ist ein Protokoll, und sowohl der Client als auch der Server müssen es kennen und befolgen. So funktioniert ein Protokoll. Wenn nur eine Seite davon weiß und sich daran hält, kann man nicht kommunizieren.
Chris Pratt
1
Das klingt ehrlich, als würden Sie versuchen, Ihre Ausnahmen durch einen catch-Block zu ersetzen. Das ist der Grund, warum wir zu Ausnahmen if( ! function() ) handle_something();übergegangen sind - nicht mehr , aber in der Lage, den Fehler an einer Stelle zu behandeln, an der Sie den aufrufenden Kontext tatsächlich kennen In diesem Fall ist der Anrufer ein weiterer Mikrodienst. Lassen Sie die Fangklötze den Fang erledigen.
Sebb

Antworten:

47

Stellen Sie sich jedoch vor, dies ist eine bestimmte Komponente, mit der wir die besten Vorgehensweisen für einen Anrufer kennen.

Dies scheitert fast immer bei mindestens einem Ihrer Anrufer, für den dieses Verhalten unglaublich irritierend ist. Gehen Sie nicht davon aus, dass Sie es am besten wissen. Sagen Sie Ihren Benutzern, was passiert, und nicht, was sie tun sollen. In vielen Fällen ist bereits klar, wie eine vernünftige Vorgehensweise aussehen sollte (und falls dies nicht der Fall ist, schlagen Sie dies in Ihrem Benutzerhandbuch vor).

Selbst die in Ihrer Frage angegebenen Ausnahmen belegen Ihre fehlerhafte Annahme: a bedeutet ServiceTemporaryUnavailable"Versuchen Sie es später noch einmal" und RateLimitExceeded"Woah there chill out, passen Sie möglicherweise Ihre Timer-Parameter an und versuchen Sie es in ein paar Minuten noch einmal". Der Benutzer möchte jedoch möglicherweise auch eine Art Alarm auslösen ServiceTemporaryUnavailable(was auf ein Serverproblem hinweist) und nicht für RateLimitExceeded(was nicht).

Gib ihnen die Wahl .

Leichtigkeit Rennen mit Monica
quelle
4
Genau. Der Server sollte die Informationen nur ordnungsgemäß übermitteln. Die Dokumentation sollte in solchen Fällen die richtige Vorgehensweise klar umreißen.
Neil
1
Ein kleines Manko dabei ist, dass einigen Hackern bestimmte Ausnahmen ziemlich viel darüber verraten, was Ihr Code tut, und sie können damit herausfinden, wie sie ihn ausnutzen können.
Pharap
3
@Pharap Wenn Ihre Hacker anstelle einer Fehlermeldung Zugriff auf die Ausnahme selbst haben, sind Sie bereits verloren.
corsiKa
2
Ich mag diese Antwort, aber es fehlt das, was ich für Ausnahmebedingungen halte ... Wenn Sie wüssten, wie man sich erholt, wäre es keine Ausnahme! Eine Ausnahme sollte nur dann sein, wenn Sie nichts dagegen tun können: ungültige Eingabe, ungültiger Status, ungültige Sicherheit - Sie können diese nicht programmgesteuert beheben.
corsiKa
1
Einverstanden. Wenn Sie darauf bestehen, anzugeben, ob ein erneuter Versuch möglich ist, können Sie immer nur die relevanten konkreten Ausnahmen festlegen, von denen erbt RetryableError.
Sapi
18

Warnung! C ++ - Programmierer kommen hier mit möglicherweise unterschiedlichen Vorstellungen, wie die Ausnahmebehandlung durchgeführt werden sollte, um eine Frage zu beantworten, bei der es sich sicherlich um eine andere Sprache handelt!

Angesichts dieser Idee:

Stellen Sie sich zum Beispiel vor, wir haben eine Abrufressource, die eine HTTP-Anforderung ausführt und abgerufene Daten zurückgibt. Und anstatt von Fehlern wie ServiceTemporaryUnavailable oder RateLimitExceeded würden wir nur einen RetryableError auslösen, der dem Verbraucher vorschlägt, dass er die Anfrage nur wiederholen und sich nicht um bestimmte Fehler kümmern soll.

Ich würde vorschlagen, dass Sie die Bedenken, einen Fehler zu melden, mit Vorgehensweisen verwechseln, um darauf zu reagieren, die die Allgemeingültigkeit Ihres Codes beeinträchtigen oder viele "Übersetzungspunkte" für Ausnahmen erfordern .

Wenn ich beispielsweise eine Transaktion modelliere, bei der eine Datei geladen wird, kann dies aus mehreren Gründen fehlschlagen. Möglicherweise wird beim Laden der Datei ein Plugin geladen, das auf dem Computer des Benutzers nicht vorhanden ist. Möglicherweise ist die Datei einfach beschädigt, und beim Parsen ist ein Fehler aufgetreten.

Egal was passiert, sagen wir, die Vorgehensweise besteht darin, dem Benutzer zu melden, was passiert ist, und ihn aufzufordern, was er dagegen tun möchte ("erneut versuchen, eine andere Datei laden, abbrechen").

Werfer gegen Fänger

Diese Vorgehensweise gilt unabhängig davon, auf welche Art von Fehler wir in diesem Fall gestoßen sind. Es ist nicht in die allgemeine Idee eines Parsing-Fehlers eingebettet. Es ist nicht in die allgemeine Idee eingebettet, dass ein Plugin nicht geladen werden kann. Es ist eingebettet in die Idee, solche Fehler im genauen Kontext des Ladens einer Datei (der Kombination aus Laden einer Datei und Fehlschlagen) zu finden. Typischerweise sehe ich es grob gesagt als die catcher'sVerantwortung, die Vorgehensweise als Reaktion auf eine ausgelöste Ausnahme (z. B. Aufforderung des Benutzers mit Optionen) zu bestimmen, nicht die thrower's.

Anders ausgedrückt, den Sites mit throwAusnahmen fehlen normalerweise solche Kontextinformationen, insbesondere wenn die auszulösenden Funktionen allgemein anwendbar sind. Selbst in einem völlig entarteten Kontext, in dem diese Informationen vorliegen, können Sie das Wiederherstellungsverhalten verbessern, indem Sie sie in die throwSite einbetten . Die Sites, catchdie im Allgemeinen über die meisten verfügbaren Informationen verfügen, um eine Vorgehensweise zu bestimmen, und bieten Ihnen einen zentralen Ort zum Ändern, falls sich diese Vorgehensweise jemals für eine bestimmte Transaktion ändern sollte.

Wenn Sie versuchen, Ausnahmen auszulösen, die nicht mehr melden, was falsch ist, sondern ermitteln, was zu tun ist, kann dies die Allgemeingültigkeit und Flexibilität Ihres Codes beeinträchtigen. Ein Parsing-Fehler sollte nicht immer zu einer solchen Aufforderung führen. Er hängt vom Kontext ab, in dem eine solche Ausnahme ausgelöst wird (der Transaktion, unter der sie ausgelöst wurde).

Der blinde Werfer

Nur im Allgemeinen dreht sich ein Großteil des Designs für die Ausnahmebehandlung oft um die Idee eines blinden Werfers. Es weiß nicht, wie und wo die Ausnahme abgefangen wird. Gleiches gilt für noch ältere Formen der Fehlerbehebung mittels manueller Fehlerweitergabe. Websites, bei denen Fehler auftreten, enthalten keine Benutzeraktion. Sie enthalten nur die minimalen Informationen, um zu melden, welche Art von Fehler aufgetreten ist.

Umgekehrte Verantwortlichkeiten und Verallgemeinerung des Fängers

Beim genaueren Überlegen versuchte ich mir die Art von Codebasis vorzustellen, in der dies eine Versuchung werden könnte. Meiner Vorstellung nach (möglicherweise falsch) spielt Ihr Team hier immer noch die Rolle des "Verbrauchers" und implementiert auch den größten Teil des aufrufenden Codes. Möglicherweise gibt es viele unterschiedliche Transaktionen (viele tryBlöcke), bei denen alle dieselben Fehler auftreten können, und alle sollten aus Entwurfssicht zu einer einheitlichen Vorgehensweise für die Wiederherstellung führen.

Unter Berücksichtigung der Lightness Races in Orbit'sguten Antworten (die meiner Meinung nach von einer fortgeschrittenen bibliotheksorientierten Denkweise herrühren) könnten Sie immer noch versucht sein, Ausnahmen zu machen, die nur näher an der Transaktionswiederherstellungssite liegen.

Hier könnte es möglich sein, eine zwischengeschaltete, gemeinsame Transaktionsabwicklungs-Site zu finden, die die "was zu tun ist" -Anliegen tatsächlich zentralisiert, aber immer noch im Kontext des Fangens.

Bildbeschreibung hier eingeben

Dies gilt nur, wenn Sie eine Art allgemeine Funktion entwerfen können, die alle diese äußeren Transaktionen verwenden (z. B. eine Funktion, die eine andere aufzurufende Funktion eingibt, oder eine abstrakte Transaktionsbasisklasse mit überschreibbarem Verhalten, die diese zwischengeschaltete Transaktionssite modelliert, die das anspruchsvolle Abfangen ausführt ).

Dieser könnte jedoch dafür verantwortlich sein, die Vorgehensweise des Benutzers als Reaktion auf eine Vielzahl möglicher Fehler zu zentralisieren, und dies immer noch im Kontext des Fangens statt des Werfens. Einfaches Beispiel (Python-isch-Pseudocode, und ich bin nicht im geringsten ein erfahrener Python-Entwickler, daher könnte es eine idiomatischere Vorgehensweise geben):

def general_catcher(task):
    try:
       task()
    except SomeError1:
       # do some uniformly-designed recovery stuff here
    except SomeError2:
       # do some other uniformly-designed recovery stuff here
    ...

[Hoffentlich mit einem besseren Namen als general_catcher]. In diesem Beispiel können Sie eine Funktion übergeben, die die auszuführende Aufgabe enthält, aber dennoch vom allgemeinen / einheitlichen Fangverhalten für alle Arten von Ausnahmen profitiert, an denen Sie interessiert sind, und den Teil "Was ist zu tun?" Weiterhin erweitern oder ändern Sie möchten von diesem zentralen Ort aus und dennoch in einem catchKontext, in dem dies normalerweise empfohlen wird. Das Beste von allem ist, dass wir die Wurfplätze davon abhalten können, sich mit der Frage zu befassen, was zu tun ist (unter Beibehaltung der Vorstellung des "blinden Werfers").

Wenn Sie keinen dieser Vorschläge hier hilfreich finden und die Versuchung groß ist, Ausnahmen zu machen, sollten Sie sich vor allem darüber im Klaren sein, dass dies zumindest sehr antiidiomatisch ist und möglicherweise eine verallgemeinerte Denkweise entmutigt.

ChrisF
quelle
2
+1. Ich habe noch nie von der Idee "Blind Thrower" als solcher gehört, aber sie passt zu meiner Meinung nach zur Ausnahmebehandlung: Geben Sie an, dass der Fehler aufgetreten ist, und überlegen Sie nicht, wie damit umgegangen werden soll. Wenn Sie für den gesamten Stapel verantwortlich sind, ist es schwierig (aber wichtig!), Die Verantwortlichkeiten sauber zu trennen, und der Angerufene ist für die Benachrichtigung über Probleme verantwortlich und der Anrufer für die Behandlung von Problemen. Der Angerufene weiß nur, worum es gebeten wurde, nicht warum. Die Behandlung von Fehlern sollte im Zusammenhang mit dem Warum erfolgen, also im Aufrufer.
Sjoerd Job Postmus
1
(Außerdem: Ich glaube nicht, dass Ihre Antwort C ++ -spezifisch ist, aber für die Ausnahmebehandlung im Allgemeinen gilt.)
Sjoerd Job Postmus
1
@SjoerdJobPostmus Yay danke! Der "Blind Thrower" ist nur eine blöde Analogie, die ich mir hier ausgedacht habe. Ich bin nicht sehr schlau und kann komplexe technische Konzepte nicht gut verarbeiten. Daher möchte ich oft nur wenig Bilder und Analogien finden, um zu versuchen, mein eigenes Verständnis zu erklären und zu verbessern von Sachen. Vielleicht kann ich eines Tages versuchen, ein kleines Programmierbuch mit vielen Comic-Zeichnungen zu schreiben. :-D
1
Heh, das ist ein schönes kleines Bild. Eine kleine Zeichentrickfigur, die eine Augenbinde trägt und baseballförmige Ausnahmen auswirft, nicht sicher ist, wer sie fängt (oder ob sie überhaupt gefangen werden), aber ihre Pflicht als blinder Werfer erfüllt.
Blacklight - glänzender
1
@ DrunkCoder: Bitte zerstören Sie Ihre Beiträge nicht. Wir haben schon genug Borken im Internet. Wenn Sie einen guten Grund zum Löschen haben, kennzeichnen Sie Ihren Beitrag für die Aufmerksamkeit des Moderators und machen Sie Ihre Sache.
Robert Harvey
2

Ich denke, die meiste Zeit wäre es besser, Argumente an die Funktion zu übergeben, um zu sagen, wie mit diesen Situationen umgegangen werden soll.

Betrachten Sie zum Beispiel eine Funktion:

Response fetchUrl(URL url, RetryPolicy retryPolicy);

Ich kann RetryPolicy.noRetries () oder RetryPolicy.retries (3) oder was auch immer übergeben. Im Falle eines erneuten Versagens wird die Richtlinie konsultiert, um zu entscheiden, ob es erneut versucht werden soll oder nicht.

Winston Ewert
quelle
Das hat aber nicht wirklich viel damit zu tun, Ausnahmen zurück auf die Aufrufseite zu werfen. Du sprichst von etwas anderem, was in Ordnung ist, aber nicht wirklich Teil der Frage.
Lightness Races mit Monica
@LightnessRacesinOrbit, im Gegenteil. Ich präsentiere es als Alternative zu der Idee, Ausnahmen zurück auf die Aufrufseite zu werfen. Im Beispiel des OP würde fetchUrl eine RetryableException auslösen, und ich sage stattdessen, Sie sollten fetchUrl mitteilen, wann es erneut versuchen soll.
Winston Ewert
2
@ WinstonEwert: Obwohl ich mit LightnessRacesinOrbit einverstanden bin, sehe ich Ihren Standpunkt auch, aber betrachten Sie ihn als eine andere Art, dasselbe Steuerelement darzustellen. Aber denken Sie daran, dass Sie wahrscheinlich passen möchten new RetryPolicy().onRateLimitExceeded(STOP).onServiceTemporaryUnavailable(RETRY, 3)oder so, weil das RateLimitExceededmöglicherweise anders gehandhabt werden muss als ServiceTemporaryUnavailable. Nachdem ich das geschrieben habe, ist mein Gedanke: Wirf lieber eine Ausnahme, weil es flexiblere Kontrolle gibt.
Sjoerd Job Postmus
@SjoerdJobPostmus, ich denke es kommt darauf an. Es kann durchaus sein, dass Ratenbegrenzungs- und Wiederholungslogik in Ihrer Bibliothek funktioniert. In diesem Fall ist mein Ansatz meines Erachtens sinnvoll. Wenn es sinnvoller ist, dies einfach Ihrem Anrufer zu überlassen, werfen Sie es auf jeden Fall.
Winston Ewert