it-swarm.com.de

Parameter zur Steuerung, ob eine Ausnahme ausgelöst oder null zurückgegeben werden soll - bewährte Methode?

Ich stoße oft auf Methoden/Funktionen, die einen zusätzlichen booleschen Parameter haben, der steuert, ob bei einem Fehler eine Ausnahme ausgelöst oder null zurückgegeben wird.

Es gibt bereits Diskussionen darüber, welche davon in welchem ​​Fall die bessere Wahl ist. Konzentrieren wir uns hier also nicht darauf. Siehe z. Magischen Wert zurückgeben, Ausnahme auslösen oder bei Fehler false zurückgeben?

Nehmen wir stattdessen an, dass es einen guten Grund gibt, warum wir beide Wege unterstützen wollen.

Persönlich denke ich, dass eine solche Methode eher in zwei Teile geteilt werden sollte: Eine, die bei einem Fehler eine Ausnahme auslöst, die andere, die bei einem Fehler null zurückgibt.

Also, was ist besser?

A: Eine Methode mit $exception_on_failure Parameter.

/**
 * @param int $id
 * @param bool $exception_on_failure
 *
 * @return Item|null
 *   The item, or null if not found and $exception_on_failure is false.
 * @throws NoSuchItemException
 *   Thrown if item not found, and $exception_on_failure is true.
 */
function loadItem(int $id, bool $exception_on_failure): ?Item;

B: Zwei verschiedene Methoden.

/**
 * @param int $id
 *
 * @return Item|null
 *   The item, or null if not found.
 */
function loadItemOrNull(int $id): ?Item;

/**
 * @param int $id
 *
 * @return Item
 *   The item, if found (exception otherwise).
 *
 * @throws NoSuchItemException
 *   Thrown if item not found.
 */
function loadItem(int $id): Item;

EDIT: C: Noch etwas?

Viele Leute haben andere Optionen vorgeschlagen oder behaupten, dass sowohl A als auch B fehlerhaft sind. Solche Vorschläge oder Meinungen sind willkommen, relevant und nützlich. Eine vollständige Antwort kann solche zusätzlichen Informationen enthalten, befasst sich jedoch auch mit der Hauptfrage, ob ein Parameter zum Ändern der Signatur/des Verhaltens eine gute Idee ist.

Anmerkungen

Falls sich jemand wundert: Die Beispiele sind in PHP. Aber ich denke, die Frage gilt für alle Sprachen, solange sie PHP oder Java) etwas ähnlich sind.

24
donquixote

Sie haben Recht: Zwei Methoden sind aus mehreren Gründen viel besser dafür:

  1. In Java enthält die Signatur der Methode , die möglicherweise eine Ausnahme auslöst, diese Ausnahme. die andere Methode wird nicht. Es macht besonders deutlich, was von dem einen und dem anderen zu erwarten ist.

  2. In Sprachen wie C #, in denen die Signatur der Methode nichts über die Ausnahmen aussagt, sollten die öffentlichen Methoden weiterhin dokumentiert werden, und diese Dokumentation enthält die Ausnahmen. Eine einzelne Methode zu dokumentieren wäre nicht einfach.

    Ihr Beispiel ist perfekt: Die Kommentare im zweiten Code sehen viel klarer aus, und ich würde sogar kurz sagen: "Das Element, wenn es gefunden wird (Ausnahme sonst)." bis hin zu „Der Artikel“ - das Vorhandensein einer möglichen Ausnahme und die Beschreibung, die Sie ihm gegeben haben, sind selbsterklärend.

  3. In einem Fall einer einzelnen Methode gibt es einige Fälle, in denen Sie den Wert des booleschen Parameters zur Laufzeit umschalten möchten. Dies würde bedeuten, dass der Aufrufer beide Fälle behandeln muss (eine null Antwort und die Ausnahme), was den Code viel schwieriger macht, als er sein muss. Da die Auswahl nicht zur Laufzeit, sondern beim Schreiben des Codes getroffen wird, sind zwei Methoden durchaus sinnvoll.

  4. Einige Frameworks, wie z. B. .NET Framework, haben Konventionen für diese Situation festgelegt und lösen diese mit zwei Methoden, wie Sie vorgeschlagen haben. Der einzige Unterschied besteht darin, dass sie ein Muster verwenden, das von Ewan in seine Antwort erklärt wurde, sodass int.Parse(string): int eine Ausnahme auslöst, während int.TryParse(string, out int): bool dies nicht tut. Diese Namenskonvention ist in der .NET-Community sehr stark und sollte immer dann befolgt werden, wenn der Code der von Ihnen beschriebenen Situation entspricht.

35

Eine interessante Variante ist die Sprache Swift. In Swift deklarieren Sie eine Funktion als "werfen", dh es ist erlaubt, Fehler zu werfen (ähnlich, aber nicht ganz dasselbe wie say Java Ausnahmen) Der Aufrufer kann diese Funktion auf vier Arten aufrufen:

ein. In einer try/catch-Anweisung wird die Ausnahme abgefangen und behandelt.

b. Wenn der Anrufer selbst als auslösend deklariert ist, rufen Sie ihn einfach auf, und jeder ausgelöste Fehler wird zu einem vom Anrufer ausgelösten Fehler.

c. Markieren Sie den Funktionsaufruf mit try!, Was bedeutet "Ich bin sicher, dass dieser Aufruf nicht ausgelöst wird". Wenn die aufgerufene Funktion tut wirft, stürzt die Anwendung garantiert ab.

d. Markieren Sie den Funktionsaufruf mit try?, Was bedeutet "Ich weiß, dass die Funktion auslösen kann, aber es ist mir egal, welcher Fehler genau ausgelöst wird". Dadurch wird der Rückgabewert der Funktion vom Typ "T" in "optional<T>" Geändert, und eine ausgelöste Ausnahme wird in "return nil" umgewandelt.

(a) und (d) sind die Fälle, nach denen Sie fragen. Was ist, wenn die Funktion nil zurückgeben kann nd Ausnahmen auslösen kann? In diesem Fall wäre der Typ des Rückgabewerts für einige Typen R "optional<R>", Und (d) würde dies in "optional<optional<R>>" Ändern, was etwas kryptisch und schwer zu verstehen ist völlig in Ordnung. Und eine Swift -Funktion kann optional<Void> Zurückgeben, sodass (d) zum Auslösen von Funktionen verwendet werden kann, die Void zurückgeben.

9
gnasher729

Ich mag den Ansatz bool TryMethodName(out returnValue).

Es gibt Ihnen einen schlüssigen Hinweis darauf, ob die Methode erfolgreich war oder nicht, und die Benennung stimmt mit dem Umschließen der Auslösemethode in einen try catch-Block überein.

Wenn Sie nur null zurückgeben, wissen Sie nicht, ob dies fehlgeschlagen ist oder der Rückgabewert zu Recht null war.

z.B:

//throws exceptions when error occurs
Item LoadItem(int id);

//returns false when error occurs
bool TryLoadItem(int id, out Item item)

beispielverwendung (out bedeutet, dass die Referenz nicht initialisiert ist)

Item i;
if(TryLoadItem(3, i))
{
    Print(i.Name);
}
else
{
    Print("unable to load item :(");
}
2
Ewan

Normalerweise gibt ein boolescher Parameter an, dass eine Funktion in zwei Teile geteilt werden kann. In der Regel sollten Sie sie immer aufteilen, anstatt einen booleschen Parameter zu übergeben.

2
Max

Wenn ich entweder A oder B verwenden müsste, würde ich B verwenden, zwei getrennte Methoden, aus den in Antwort von Arseni Mourzenkos angegebenen Gründen.

Es gibt aber noch eine andere Methode1: In Java heißt es Optional, aber Sie können dasselbe Konzept auf andere Sprachen anwenden, in denen Sie Ihre eigenen Typen definieren können, wenn die Sprache noch keine ähnliche Klasse bereitstellt .

Sie definieren Ihre Methode folgendermaßen:

Optional<Item> loadItem(int id);

In Ihrer Methode geben Sie entweder Optional.of(item) zurück, wenn Sie das Element gefunden haben, oder Sie geben Optional.empty() zurück, falls Sie dies nicht tun. Du wirfst nie2 und niemals null zurückgeben.

Für den Client Ihrer Methode ist es so etwas wie null, aber mit dem großen Unterschied, dass es gezwungen ist, über den Fall fehlender Elemente nachzudenken. Der Benutzer kann die Tatsache, dass möglicherweise kein Ergebnis vorliegt, nicht einfach ignorieren. Um das Element aus Optional zu entfernen, ist eine explizite Aktion erforderlich:

  • loadItem(1).get() löst ein NoSuchElementException aus oder gibt das gefundene Element zurück.
  • Es gibt auch loadItem(1).orElseThrow(() -> new MyException("No item 1")), um eine benutzerdefinierte Ausnahme zu verwenden, wenn das zurückgegebene Optional leer ist.
  • loadItem(1).orElse(defaultItem) gibt entweder das gefundene Element oder das übergebene defaultItem (das auch null sein kann) zurück, ohne eine Ausnahme auszulösen.
  • loadItem(1).map(Item::getName) würde erneut ein Optional mit dem Elementnamen zurückgeben, falls vorhanden.
  • Es gibt einige weitere Methoden, siehe Javadoc für Optional .

Der Vorteil ist, dass es jetzt am Client-Code liegt, was passieren soll, wenn kein Element vorhanden ist und Sie dennoch nur eine einzelne Methode bereitstellen müssen .


1 Ich denke, es ist so etwas wie die try? In gnasher729s Antwort , aber es ist mir nicht ganz klar, da ich nicht weiß Swift und Es scheint eine Sprachfunktion zu sein, daher ist es spezifisch für Swift.

2 Sie können Ausnahmen aus anderen Gründen auslösen, z. Wenn Sie nicht wissen, ob ein Element vorhanden ist oder nicht, weil Sie Probleme mit der Kommunikation mit der Datenbank hatten.

1
siegi

Clean Code empfiehlt, boolesche Flag-Argumente zu vermeiden, da ihre Bedeutung an der Aufrufstelle undurchsichtig ist:

loadItem(id, true) // does this throw or not? We need to check the docs ...

während separate Methoden ausdrucksstarke Namen verwenden können:

loadItemOrNull(id); // meaning is obvious; no need to refer to docs
1
meriton

Ich bevorzuge zwei Methoden. Auf diese Weise sagen Sie mir nicht nur, dass Sie einen Wert zurückgeben, sondern auch welchen Wert genau.

public int GetValue(); // If you can't get the value, you'll throw me an exception
public int GetValueOrDefault(); // If you can't get the value, you'll return me 0, since it's the default for ints

Das TryDoSomething () ist auch kein schlechtes Muster.

public bool TryParse(string value, out int parsedValue); // If you return me false, I won't bother checking parsedValue.

Ich bin eher daran gewöhnt, Ersteres in Datenbankinteraktionen zu sehen, während Letzteres eher für das Analysieren verwendet wird.

Wie andere Ihnen bereits gesagt haben, sind Flag-Parameter normalerweise ein Code-Geruch (Code-Gerüche bedeuten nicht, dass Sie etwas falsch machen, nur dass es wahrscheinlich ist, dass Sie es falsch machen). Obwohl Sie es genau benennen, gibt es manchmal Nuancen, die es schwierig machen zu wissen, wie man dieses Flag verwendet, und am Ende schauen Sie in den Methodenkörper.

0
A Bravo Dev

Als weiterer Punkt, der mit der Verwendung von zwei Methoden übereinstimmt, kann dies sehr einfach zu implementieren sein. Ich werde mein Beispiel in C # schreiben, da ich die PHP-Syntax nicht kenne.

public Widget GetWidgetOrNull(int id) {
    // All the lines to get your item, returning null if not found
}

public Widget GetWidget(int id) {
    var widget = GetWidgetOrNull(id);

    if (widget == null) {
        throw new WidgetNotFoundException();
    }

    return widget;
}
0
krillgar