Funktion, die true / false vs. void zurückgibt, wenn sie erfolgreich ist, und eine Ausnahme auslöst, wenn sie fehlschlägt

22

Ich erstelle eine API, eine Funktion, die eine Datei hochlädt. Diese Funktion gibt nichts / nichtig zurück, wenn die Datei korrekt hochgeladen wurde und löst eine Ausnahme aus, wenn ein Problem aufgetreten ist.

Warum eine Ausnahme und nicht nur falsch? Weil ich in einer Ausnahme den Grund für den Fehler angeben kann (keine Verbindung, fehlender Dateiname, falsches Passwort, fehlende Dateibeschreibung usw.). Ich wollte eine benutzerdefinierte Ausnahme erstellen (mit einigen Aufzählungen, um dem API-Benutzer zu helfen, alle Fehler zu behandeln).

Handelt es sich um eine bewährte Methode oder ist es besser, ein Objekt zurückzugeben (mit einem Booleschen Wert im Inneren, einer optionalen Fehlermeldung und der Aufzählung der Fehler)?

Accollativo
quelle
59
Richtig und falsch passen gut zusammen. Leere und Ausnahme passen gut zusammen. True und Exception gehören zusammen wie Katzen und Steuern. Eines Tages könnte ich Ihren Code lesen. Bitte tu mir das nicht an.
candied_orange
4
Ich mag Muster, bei denen ein Objekt zurückgegeben wird, das Erfolg oder Misserfolg verkapselt. Beispielsweise hat Rust, Result<T, E>wo Tdas Ergebnis ist, wenn es erfolgreich ist, und Eist der Fehler, wenn es nicht erfolgreich ist. Das Auslösen einer Ausnahme (kann in Java nicht so sicher sein) kann teuer sein, da der Aufrufstapel aufgelöst werden muss, das Erstellen eines Ausnahmeobjekts jedoch kostengünstig ist. Halten Sie sich natürlich an die festgelegten Muster der von Ihnen verwendeten Sprache, aber kombinieren Sie keine Booleschen Werte und Ausnahmen wie diese.
Dan Pantry
4
Sei hier vorsichtig. Sie können ein Kaninchenbau geleitet werden. Wenn Ihr Programm in der Lage ist, ein Problem zu beheben, ist es in der Regel einfacher, es zu erkennen und zu beheben, indem Sie die Voraussetzungen überprüfen. Sie würden selten eine Ausnahme abfangen, ihre Daten im Code untersuchen und es nach Behebung des Problems erneut versuchen. Daher ist es selten sinnvoll, sehr viele Ausnahmetypen zu haben. Wenn Sie versuchen, Probleme zu protokollieren , ist dies viel sinnvoller, aber Sie sollten nicht erwarten, eine Reihe von Catch-Blöcken mit einer erheblichen Menge an Code darin zu schreiben.
jpmc26
4
@DanPantry In Java wird der Aufrufstapel beim Erstellen der Ausnahme überprüft , nicht beim Auslösen . fillInStackTrace();heißt im Superkonstruktor, in der KlasseThrowable
Simon Forsberg

Antworten:

35

Das Auslösen einer Ausnahme ist lediglich eine zusätzliche Möglichkeit, eine Methode einen Wert zurückgeben zu lassen. Der Aufrufer kann genauso einfach nach einem Rückgabewert suchen wie eine Ausnahme abfangen und dies überprüfen. Die Entscheidung zwischen throwund returnerfordert daher andere Kriterien.

Das Auslösen von Ausnahmen sollte häufig vermieden werden, wenn dadurch die Effizienz Ihres Programms gefährdet wird (das Erstellen eines Ausnahmeobjekts und das Auflösen des Aufrufstapels ist für den Computer viel mehr Arbeit, als nur einen Wert darauf zu schreiben). Wenn der Zweck Ihrer Methode jedoch darin besteht, eine Datei hochzuladen, liegt der Engpass immer bei der Netzwerk- und Dateisystem-E / A. Daher ist es sinnlos, die Rückgabemethode zu optimieren.

Es ist auch eine schlechte Idee, Ausnahmen für einen einfachen Kontrollfluss auszulösen (z. B. wenn eine Suche erfolgreich ist, indem ihr Wert ermittelt wird), da dies die Erwartungen der API-Benutzer verletzt. Aber eine Methode, die ihren Zweck nicht erfüllt, ist ein Ausnahmefall (oder sollte es zumindest sein), daher sehe ich keinen Grund, keine Ausnahme auszulösen. Und wenn Sie das tun, können Sie es genauso gut zu einer benutzerdefinierten, informativeren Ausnahme machen (aber es ist eine gute Idee, es zu einer Unterklasse einer standardmäßigen, allgemeineren Ausnahme wie zu machen IOException).

Kilian Foth
quelle
7
Wenn ein Fehler bei einer Datei-Upload-Anforderung zu erwarten ist, würde mich eine Ausnahmebedingung überraschen (insbesondere, wenn die Methodensignatur einen Rückgabewert enthält).
Hoffmale
1
Beachten Sie, dass der Grund für die Erweiterung einer Standardausnahme darin besteht, dass viele (wahrscheinlich sogar die meisten) Anrufer keinen Code schreiben, um zu versuchen, das Problem zu beheben . Infolgedessen möchten die meisten Anrufer eine einfache "Fang diese große Gruppe von Ausnahmen", ohne sie alle explizit auflisten zu müssen.
jpmc26
4
@gbjbaanb Aus diesem Grund sollten Ausnahmen verwendet werden, wenn wirklich eine unbehebbare Situation vorliegt. Wenn Sie versuchen, eine Datei zu öffnen und die Datei nicht gelesen werden kann, ist dies ein guter Fall für eine Ausnahme. Wenn Sie prüfen, ob eine Datei vorhanden ist, muss ein Boolescher Wert verwendet werden, da zu erwarten ist, dass die Datei möglicherweise nicht vorhanden ist.
Malcolm
21
Kilian sagt im rekursiven Slogan "Ausnahmen sind für Ausnahmesituationen", Ausnahmesituationen sind, wenn die Funktion ihren Zweck nicht erfüllen kann. Wenn die Funktion eine Datei lesen soll und die Datei nicht gelesen werden kann , ist dies eine Ausnahme . Wenn die Funktion Ihnen mitteilen soll, ob eine Datei vorhanden ist oder nicht (ungeachtet des potenziellen TOCTOU), ist die nicht vorhandene Datei keine Ausnahme, da die Funktion immer noch das tun kann, was sie tun soll. Wenn die Funktion behaupten soll, dass eine Datei vorhanden ist, ist dies eine Ausnahme, da "assert" dies bedeutet.
Steve Jessop
10
Beachten Sie, dass der einzige Unterschied zwischen einer Funktion, die angibt, ob eine Datei vorhanden ist, und einer Funktion, die angibt, dass eine Datei vorhanden ist, darin besteht, dass der Fall der nicht vorhandenen Datei außergewöhnlich ist. Die Leute sprechen manchmal so, als sei es eine Eigenschaft der Fehlerbedingung, ob sie "außergewöhnlich" ist oder nicht. Es ist keine Eigenschaft der Fehlerbedingung, sondern eine kombinierte Eigenschaft der Fehlerbedingung und des Zwecks der Funktion, in der sie auftritt. Andernfalls würden wir niemals Ausnahmen abfangen.
Steve Jessop
31

Es gibt absolut keinen Grund, truebei Erfolg zurückzukehren, wenn Sie nicht falsebei Misserfolg zurückkehren. Wie soll der Client-Code aussehen?

if (result = tryMyAPICall()) {
    // business logic
}
else {
    // this will *never* happen anyways
}

In diesem Fall braucht der Aufrufer sowieso einen Try-Catch-Block, aber dann kann er besser schreiben:

try {
    result = tryMyAPICall();
    // business logic
    // will only reach this line when no exception
    // no reason to use an if-condition
} catch (SomeException se) { }

Der trueRückgabewert ist also für den Aufrufer völlig irrelevant. Behalten Sie also einfach die Methode bei void.


Im Allgemeinen gibt es drei Möglichkeiten zum Entwerfen von Fehlermodi.

  1. Rückgabe wahr / falsch
  2. Verwenden Sie voiddie (aktivierte) Ausnahme, werfen Sie sie
  3. Rück ein Zwischenergebnis - Objekt.

Rückgabe true/false

Dies wird in einigen älteren, meist C-artigen APIs verwendet. Die Nachteile liegen auf der Hand, Sie haben keine Ahnung, was schief gelaufen ist. PHP macht das ziemlich oft, was zu folgendem Code führt:

if (xyz_parse($data) === FALSE)
   $error = xyz_last_error();

In Multithreading-Kontexten ist dies sogar noch schlimmer.

Wirf (aktivierte) Ausnahmen

Dies ist ein guter Weg, um es zu tun. An einigen Stellen können Sie mit einem Ausfall rechnen. Java macht das mit Sockets. Die Grundannahme ist, dass ein Aufruf erfolgreich sein sollte, aber jeder weiß, dass bestimmte Vorgänge möglicherweise fehlschlagen. Steckdosenanschlüsse gehören dazu. Der Anrufer wird also gezwungen, den Fehler zu behandeln. Es ist ein ansprechendes Design, da es sicherstellt, dass der Anrufer den Fehler tatsächlich behandelt, und dem Anrufer eine elegante Möglichkeit bietet, mit dem Fehler umzugehen.

Ergebnisobjekt zurückgeben

Dies ist eine weitere gute Möglichkeit, damit umzugehen. Es wird oft zum Parsen oder einfach für Dinge verwendet, die validiert werden müssen.

ValidationResult result = parser.validate(data);
if (result.isValid())
    // business logic
else
    error = result.getvalidationError();

Nette, saubere Logik auch für den Anrufer.

Es gibt einige Debatten darüber, wann der zweite und wann der dritte Fall zu verwenden ist. Einige Leute glauben , dass Ausnahmen sollten außergewöhnliche und dass Sie nicht mit der Möglichkeit von Ausnahmen im Auge entwerfen sollten, und werden so ziemlich immer die dritten Optionen. Das ist in Ordnung. Wir haben jedoch Ausnahmen in Java überprüft, sodass ich keinen Grund sehe, sie nicht zu verwenden. Ich verwende geprüfte Expetitionen, wenn die Grundannahme ist, dass der Aufruf erfolgreich sein sollte (wie das Verwenden eines Sockets), aber ein Fehler möglich ist, und ich verwende die dritte Option, wenn sehr unklar ist, ob der Aufruf erfolgreich sein sollte (wie das Validieren von Daten). Aber dazu gibt es unterschiedliche Meinungen.

In deinem Fall würde ich mit void+ gehen Exception. Sie erwarten, dass der Dateiupload erfolgreich ist, und wenn dies nicht der Fall ist, ist dies außergewöhnlich. Der Aufrufer ist jedoch gezwungen, diesen Fehlermodus zu verarbeiten, und Sie können eine Ausnahme zurückgeben, die genau beschreibt, welche Art von Fehler aufgetreten ist.

Polygnom
quelle
5

Dies hängt wirklich davon ab, ob ein Fehler außergewöhnlich ist oder erwartet wird .

Wenn ein Fehler normalerweise nicht erwartet wird, ruft der API-Benutzer wahrscheinlich nur Ihre Methode ohne spezielle Fehlerbehandlung auf. Durch das Auslösen einer Ausnahme kann der Stapel an eine Stelle gesprudelt werden, an der er bemerkt wird.

Wenn andererseits Fehler an der Tagesordnung sind, sollten Sie für den Entwickler optimieren, der nach ihnen try-catchsucht , und Klauseln sind etwas umständlicher als ein if/elifoder eine switchReihe.

Entwerfen Sie eine API immer im Denken einer Person, die sie verwendet.

Xiong Chiamiov
quelle
1
In meinem Fall sollte, wie jemand betonte, ein außergewöhnlicher Fehler sein, dass der API-Benutzer die Datei nicht hochladen kann.
Accollativo
4

Geben Sie keinen Booleschen Wert zurück, wenn Sie nie die Absicht haben, zurückzukehren false. Machen Sie einfach die Methode voidund dokumentieren Sie, dass sie bei einem IOExceptionFehler eine Ausnahme auslöst ( geeignet ist).

Der Grund dafür ist, dass der Benutzer Ihrer API, wenn Sie einen Booleschen Wert zurückgeben, möglicherweise zu dem Schluss kommt, dass er dies tun kann:

if (fileUpload(file) == false) {
    // Handle failure.
}

Das wird natürlich nicht funktionieren; Das heißt, es besteht eine Inkongruenz zwischen dem Vertrag Ihrer Methode und ihrem Verhalten. Wenn Sie eine aktivierte Ausnahme auslösen, muss der Benutzer der Methode den Fehler behandeln:

try {
    fileUpload(file);
} catch (IOException e) {
    // Handle failure.
}
JeroenHoek
quelle
3

Wenn Ihre Methode einen Rückgabewert hat, kann das Auslösen einer Ausnahme die Benutzer überraschen. Wenn ich sehe, dass eine Methode einen Booleschen Wert zurückgibt, bin ich ziemlich davon überzeugt, dass sie true zurückgibt, wenn dies erfolgreich ist, und false, wenn dies nicht der Fall ist, und strukturiere meinen Code mithilfe einer if-else-Klausel. Wenn Ihre Ausnahme ohnehin auf einer Aufzählung basiert, können Sie stattdessen auch einen Aufzählungswert zurückgeben, der Windows HResults ähnelt, und einen Aufzählungswert beibehalten, wenn die Methode erfolgreich ist.

Es ist auch ärgerlich, wenn eine Methode eine Ausnahme auslöst, wenn dies nicht der letzte Schritt in einer Prozedur ist. Das Schreiben eines Try-Catch- und Divert-Kontrollflusses in den Catch-Block ist ein gutes Rezept für Spaghetti und sollte vermieden werden.

Wenn Sie mit Ausnahmen fortfahren, versuchen Sie stattdessen, void zurückzugeben. Benutzer werden feststellen, ob dies erfolgreich ist, wenn keine Ausnahmen ausgelöst werden, und keiner wird versuchen, eine if-else-Klausel zu verwenden, um den Kontrollfluss umzuleiten.

ccControl
quelle
1
Die Aufzählung war nur eine Idee, um dem API-Benutzer zu helfen, verschiedene Fehlertypen zu behandeln, ohne die Fehlermeldung zu analysieren. Aus Ihrer Sicht ist es in meinem Fall also besser, die Aufzählung zurückzugeben und eine Aufzählung für "OK" hinzuzufügen.
Accollativo