Oft stoße ich 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, also wollen wir uns hier nicht darauf konzentrieren. Siehe z. B. magischen Wert zurückgeben, Ausnahme auslösen oder falsch zurückgeben, wenn ein Fehler auftritt.
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 zweigeteilt 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 aber auch mit der Hauptfrage, ob ein Parameter zum Ändern der Signatur / des Verhaltens eine gute Idee ist.
Anmerkungen
Für den Fall, dass sich jemand wundert: Die Beispiele sind in PHP. Aber ich denke, die Frage gilt für alle Sprachen, solange sie PHP oder Java etwas ähneln.
quelle
loadItemOrNull(id)
, ob diesloadItemOr(id, defaultItem)
für Sie sinnvoll ist. Wenn item ein String oder eine Zahl ist, ist dies häufig der Fall.Antworten:
Sie haben Recht: Zwei Methoden sind aus mehreren Gründen viel besser:
In Java enthält die Signatur der Methode, die möglicherweise eine Ausnahme auslöst, diese Ausnahme. die andere Methode wird nicht. Es wird besonders deutlich, was von dem einen und anderen zu erwarten ist.
In Sprachen wie C #, in denen die Signatur der Methode nichts über die Ausnahmen aussagt, sollten die öffentlichen Methoden weiterhin dokumentiert sein, und diese Dokumentation enthält die Ausnahmen. Eine einzige Methode zu dokumentieren, wäre nicht einfach.
Ihr Beispiel ist perfekt: Die Kommentare im zweiten Teil des Codes sehen viel klarer aus, und ich würde sogar kurz "Das Element, wenn es gefunden wird (Ausnahme sonst)." Bis zu "Das Element" - das Vorhandensein einer möglichen Ausnahme und der Ihre Beschreibung ist selbsterklärend.
In einem Fall einer einzelnen Methode gibt es wenige Fälle, in denen Sie den Wert des booleschen Parameters zur Laufzeit umschalten möchten, und in diesem Fall müsste der Aufrufer beide Fälle behandeln (eine
null
Antwort und die Ausnahme) ), was den Code viel schwieriger macht, als er sein muss. Da die Auswahl nicht zur Laufzeit getroffen wird, sondern beim Schreiben des Codes, sind zwei Methoden sinnvoll.Einige Frameworks, 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 von Ewan in seiner Antwort erklärtes Muster verwenden , also
int.Parse(string): int
eine Ausnahme auslösen, währendint.TryParse(string, out int): bool
dies nicht der Fall ist. Diese Namenskonvention ist in der .NET-Community sehr stark verbreitet und sollte befolgt werden, wenn der Code der von Ihnen beschriebenen Situation entspricht.quelle
int.Parse()
wird als eine wirklich schlechte Designentscheidung angesehen, weshalb das .NET-Team die Implementierung vorangetrieben hatTryParse
.->itemExists($id)
vor dem Anruf an->loadItem($id)
. Oder vielleicht ist es nur eine Entscheidung, die andere in der Vergangenheit getroffen haben und die wir für selbstverständlich halten müssen.data Maybe a = Just a | Nothing
).Eine interessante Variante ist die Swift-Sprache. In Swift deklarieren Sie eine Funktion als "werfen", das heißt, es ist erlaubt, Fehler zu werfen (ähnlich, aber nicht ganz so wie bei Java-Ausnahmen). Der Anrufer 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 dem
try!
die Meldung "Ich bin sicher, dieser Anruf wird nicht ausgelöst" angezeigt wird. Wenn die aufgerufene Funktion tut werfen, wird die Anwendung zum Absturz zu bringen garantiert.d. Markieren des Funktionsaufrufs, mit dem
try?
die Meldung "Ich weiß, dass die Funktion ausgelöst werden kann, aber es ist mir egal, welcher Fehler genau ausgelöst wird" angezeigt wird. Dies ändert den Rückgabewert der Funktion von Typ "T
" in Typ "optional<T>
" und eine geworfene 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 und Ausnahmen auslösen kann? In diesem Fall wäre der Typ des Rückgabewerts "
optional<R>
" für einen Typ R, und (d) würde dies in "optional<optional<R>>
" ändern, was etwas kryptisch und schwer zu verstehen ist, aber völlig in Ordnung ist. Und eine Swift-Funktion kann zurückkehrenoptional<Void>
, so dass (d) zum Werfen von Funktionen verwendet werden kann, die Void zurückgeben.quelle
Ich mag den
bool TryMethodName(out returnValue)
Ansatz.Es gibt Ihnen einen schlüssigen Hinweis darauf, ob die Methode erfolgreich war oder nicht, und die Benennung stimmt überein, wenn die Ausnahmewurfmethode in einen try catch-Block eingeschlossen wird.
Wenn Sie nur null zurückgeben, wissen Sie nicht, ob dies fehlgeschlagen ist oder der Rückgabewert zu Recht null war.
z.B:
Beispielverwendung (out bedeutet, dass die Referenz nicht initialisiert wird)
quelle
bool TryMethodName(out returnValue)
Ansatz" im ursprünglichen Beispiel aussehen?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 Wert zu übergeben.
quelle
b
: Anstatt aufzurufenfoo(…, b);
, müssen Sieif (b) then foo_x(…) else foo_y(…);
die anderen Argumente schreiben und wiederholen, oder((b) ? foo_x : foo_y)(…)
wenn die Sprache einen ternären Operator und erstklassige Funktionen / Methoden hat. Und dies wird vielleicht sogar an mehreren Stellen im Programm wiederholt.if (myGrandmaMadeCookies) eatThem() else makeFood()
das ich vielleicht in eine andere Funktion wie diese einwickeln würdecheckIfShouldCook(myGrandmaMadeCookies)
. Ich frage mich, ob die von Ihnen erwähnte Nuance damit zusammenhängt, ob wir einen Booleschen Wert für das Verhalten der Funktion übergeben, wie in der ursprünglichen Frage, oder ob wir Informationen zu unserem Modell übergeben, wie in der Oma Beispiel, das ich gerade gegeben habe.Clean Code rät dazu, Argumente mit booleschen Flags zu vermeiden, da ihre Bedeutung an der Aufrufstelle undurchsichtig ist:
Während separate Methoden aussagekräftige Namen verwenden können:
quelle
Wenn ich entweder A oder B verwenden müsste, würde ich aus den in der Antwort von Arseni Mourzenkos angegebenen Gründen B verwenden, zwei getrennte Methoden .
Es gibt aber noch eine andere Methode 1 : 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.So definieren Sie Ihre Methode:
In Ihrer Methode kehren Sie entweder zurück,
Optional.of(item)
wenn Sie den Artikel gefunden haben, oder Sie kehrenOptional.empty()
zurück, falls Sie dies nicht tun. Du wirfst niemals 2 und kommst niemals zurücknull
.Für den Klienten Ihrer Methode ist es etwas Ähnliches
null
, aber mit dem großen Unterschied, dass er gezwungen ist, über den Fall fehlender Elemente nachzudenken. Der Benutzer kann die Tatsache, dass möglicherweise kein Ergebnis vorliegt, nicht einfach ignorieren. Um den Gegenstand aus dem zu entfernen, istOptional
eine explizite Aktion erforderlich:loadItem(1).get()
wirft einenNoSuchElementException
oder gibt den gefundenen Gegenstand zurück.loadItem(1).orElseThrow(() -> new MyException("No item 1"))
eine benutzerdefinierte Ausnahme zu verwenden, wenn die zurückgegebeneOptional
leer ist.loadItem(1).orElse(defaultItem)
Gibt entweder das gefundene Element oder das übergebene zurückdefaultItem
(was auch sein kannnull
), ohne eine Ausnahme auszulösen.loadItem(1).map(Item::getName)
würde wieder einenOptional
mit dem Itemnamen zurückgeben, falls vorhanden.Optional
.Der Vorteil ist, dass es nun am Client-Code liegt, was passieren soll, wenn es keinen Artikel gibt und Sie dennoch nur eine einzige Methode bereitstellen müssen .
1 Ich denke, es ist so etwas wie die Antwort von
try?
in gnasher729s, aber es ist mir nicht ganz klar, da ich Swift nicht kenne und es ein Sprachmerkmal zu sein scheint, so dass es spezifisch für Swift ist.2 Sie können Ausnahmen aus anderen Gründen auslösen, z. B. wenn Sie nicht wissen, ob ein Element vorhanden ist oder nicht, weil Sie Probleme mit der Kommunikation mit der Datenbank hatten.
quelle
Ein weiterer Punkt, der mit der Verwendung von zwei Methoden übereinstimmt, ist, dass dies sehr einfach zu implementieren ist. Ich schreibe mein Beispiel in C #, da ich die PHP-Syntax nicht kenne.
quelle
Ich bevorzuge zwei Methoden, auf diese Weise werden Sie mir nicht nur sagen, dass Sie einen Wert zurückgeben, sondern welchen Wert genau.
Das TryDoSomething () ist auch kein schlechtes Muster.
Ich bin eher daran gewöhnt, Ersteres in Datenbankinteraktionen zu sehen, während Letzteres eher in Syntaxanalyse verwendet wird.
Wie andere 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, diese Flagge zu verwenden, und Sie sehen sich am Ende den Methodenkörper an.
quelle
Während andere Leute etwas anderes vorschlagen, würde ich vorschlagen, dass eine Methode mit einem Parameter, der angibt, wie Fehler behandelt werden sollen, besser ist als die Verwendung separater Methoden für die beiden Verwendungsszenarien. Während es wünschenswert sein kann, auch unterschiedliche Einstiegspunkte für die Verwendungen "Fehlerauslösungen" im Vergleich zu "Fehler gibt Fehleranzeige zurück" zu haben, wird durch die Verwendung eines Einstiegspunkts, der beide Verwendungsmuster bedienen kann, eine Codeduplizierung in Wrapper-Methoden vermieden. Als einfaches Beispiel, wenn man Funktionen hat:
und man möchte Funktionen, die sich ebenfalls verhalten, aber mit x in eckige Klammern eingeschlossen, müssten zwei Sätze von Wrapper-Funktionen geschrieben werden:
Wenn es ein Argument für den Umgang mit Fehlern gibt, wird nur ein Wrapper benötigt:
Wenn der Wrapper einfach genug ist, ist die Codeduplizierung möglicherweise nicht so schlecht, aber wenn sie jemals geändert werden muss, müssen zum Duplizieren des Codes Änderungen vorgenommen werden. Selbst wenn man sich dafür entscheidet, Einstiegspunkte hinzuzufügen, die das zusätzliche Argument nicht verwenden:
Man kann die gesamte "Geschäftslogik" in einer Methode aufbewahren - der, die ein Argument akzeptiert, das angibt, was im Fehlerfall zu tun ist.
quelle
Ich denke, alle Antworten, die erklären, dass Sie kein Flag haben sollten, das steuert, ob eine Ausnahme ausgelöst wird oder nicht, sind gut. Ihr Rat wäre mein Rat an alle, die das Bedürfnis haben, diese Frage zu stellen.
Ich wollte jedoch darauf hinweisen, dass es eine sinnvolle Ausnahme von dieser Regel gibt. Sobald Sie fortgeschritten genug sind, um mit der Entwicklung von APIs zu beginnen, die von Hunderten anderer Personen verwendet werden, kann es vorkommen, dass Sie ein solches Flag bereitstellen möchten. Wenn Sie eine API schreiben, schreiben Sie nicht nur an die Puristen. Sie schreiben an echte Kunden, die echte Wünsche haben. Teil Ihrer Aufgabe ist es, sie mit Ihrer API zufrieden zu stellen. Das wird eher zu einem sozialen Problem als zu einem Programmierproblem, und manchmal schreiben soziale Probleme Lösungen vor, die als eigene Programmierlösungen nicht ideal wären.
Möglicherweise haben Sie zwei unterschiedliche Benutzer Ihrer API, von denen einer Ausnahmen wünscht und einer dies nicht tut. Ein Beispiel aus der Praxis, in dem dies auftreten könnte, ist eine Bibliothek, die sowohl in der Entwicklung als auch auf einem eingebetteten System verwendet wird. In Entwicklungsumgebungen möchten Benutzer wahrscheinlich überall Ausnahmen haben. Ausnahmen sind sehr beliebt für den Umgang mit unerwarteten Situationen. In vielen eingebetteten Situationen sind sie jedoch verboten, da sie zu schwierig sind, um Einschränkungen in Echtzeit zu analysieren. Wenn Sie sich nicht nur um die durchschnittliche Zeit für die Ausführung Ihrer Funktion kümmern , sondern auch um die Zeit, die für einen einzelnen Spaß benötigt wird, ist die Idee, den Stapel an einem beliebigen Ort abzuwickeln, sehr unerwünscht.
Ein echtes Beispiel für mich: eine Mathematikbibliothek. Wenn Ihre Mathematikbibliothek eine
Vector
Klasse hat, die es unterstützt, einen Einheitsvektor in dieselbe Richtung zu erhalten, müssen Sie in der Lage sein, durch die Größe zu dividieren. Wenn die Größe 0 ist, haben Sie eine Division durch Null-Situation. In der Entwicklung möchten Sie diese fangen . Sie wollen wirklich keine überraschende Division durch Nullen. Das Auslösen einer Ausnahme ist hierfür eine sehr beliebte Lösung. Es passiert fast nie (es ist wirklich außergewöhnliches Verhalten), aber wenn es passiert, möchten Sie es wissen.Auf der eingebetteten Plattform möchten Sie diese Ausnahmen nicht. Es wäre sinnvoller, eine Überprüfung durchzuführen
if (this->mag() == 0) return Vector(0, 0, 0);
. Tatsächlich sehe ich das in echtem Code.Denken Sie jetzt aus der Perspektive eines Unternehmens. Sie können versuchen, zwei verschiedene Methoden zur Verwendung einer API beizubringen:
Dies entspricht den Meinungen der meisten Antworten, ist jedoch aus Unternehmenssicht unerwünscht. Embedded - Entwickler haben ein lernen Stil der Codierung und Entwicklung Entwickler müssen eine andere lernen. Dies kann ziemlich lästig sein und zwingt die Menschen zu einer Denkweise. Möglicherweise noch schlimmer: Wie können Sie beweisen, dass die eingebettete Version keinen Code zum Auslösen von Ausnahmen enthält? Die Throwing-Version kann sich leicht einfügen und irgendwo in Ihrem Code verstecken, bis ein teures Failure Review Board sie findet.
Wenn Sie andererseits ein Flag haben, das die Ausnahmebehandlung ein- oder ausschaltet, können beide Gruppen den exakt gleichen Codierungsstil erlernen. In vielen Fällen können Sie sogar den Code einer Gruppe in den Projekten der anderen Gruppe verwenden.
unit()
Wenn Sie bei Verwendung dieses Codes tatsächlich daran interessiert sind, was in einem Fall der Division durch 0 geschieht, sollten Sie den Test selbst geschrieben haben. Wenn Sie es also auf ein eingebettetes System verschieben, auf dem Sie nur schlechte Ergebnisse erzielen, ist dieses Verhalten "normal". Aus geschäftlicher Sicht trainiere ich meine Codierer jetzt einmal und sie verwenden in allen Bereichen meines Geschäfts dieselben APIs und Stile.In den allermeisten Fällen möchten Sie den Ratschlägen anderer folgen: Verwenden Sie
tryGetUnit()
für Funktionen, die keine Ausnahmen auslösen, ähnliche oder ähnliche Redewendungen und geben Sie stattdessen einen Sentinel-Wert wie null zurück. Wenn Sie der Meinung sind, dass Benutzer sowohl Ausnahmebehandlungs- als auch Nicht-Ausnahmebehandlungscode in einem einzigen Programm nebeneinander verwenden möchten, bleiben Sie bei dertryGetUnit()
Notation. Es gibt jedoch einen Eckfall, in dem die Realität des Geschäfts die Verwendung einer Flagge verbessern kann. Wenn Sie sich in diesem Eckfall befinden, haben Sie keine Angst, die "Regeln" auf der Strecke zu lassen. Dafür gibt es Regeln!quelle