Rückgabe eines Booleschen Werts, wenn Erfolg oder Misserfolg das einzige Problem sind

15

Ich finde es oft so, dass ich einen Booleschen Wert von einer Methode zurückgebe, die an mehreren Stellen verwendet wird, um die gesamte Logik um diese Methode an einer einzigen Stelle zu speichern. Die (interne) Aufrufmethode muss nur wissen, ob die Operation erfolgreich war oder nicht.

Ich benutze Python, aber die Frage ist nicht unbedingt spezifisch für diese Sprache. Ich kann mir nur zwei Möglichkeiten vorstellen

  1. Lösen Sie eine Ausnahme aus, obwohl die Umstände nicht außergewöhnlich sind, und denken Sie daran, diese Ausnahme an jeder Stelle abzufangen, an der die Funktion aufgerufen wird
  2. Gib einen Booleschen zurück, wie ich es tue.

Dies ist ein wirklich einfaches Beispiel, das zeigt, wovon ich spreche.

import os

class DoSomething(object):

    def remove_file(self, filename):

        try:
            os.remove(filename)
        except OSError:
            return False

        return True

    def process_file(self, filename):

        do_something()

        if remove_file(filename):
            do_something_else()

Obwohl es funktioniert, mag ich diese Art, etwas zu tun, nicht, es "riecht" und kann manchmal zu einer Menge verschachtelter Ifs führen. Aber ich kann mir keinen einfacheren Weg vorstellen.

Ich könnte mich os.path.exists(filename)vor dem Versuch, eine Löschung vorzunehmen, einer LBYL-Philosophie zuwenden, aber es gibt keine Garantie dafür, dass die Datei in der Zwischenzeit nicht gesperrt wurde (dies ist unwahrscheinlich, aber möglich), und ich muss immer noch feststellen, ob die Löschung erfolgreich war oder nicht.

Ist dies ein "akzeptables" Design und wenn nicht, was wäre ein besserer Weg, dies zu gestalten?

Ben
quelle

Antworten:

11

Sie sollten zurückkehren, booleanwenn die Methode / Funktion beim Treffen logischer Entscheidungen hilfreich ist.

Sie sollten einen werfen, exceptionwenn die Methode / Funktion wahrscheinlich nicht in logischen Entscheidungen verwendet wird.

Sie müssen entscheiden, wie wichtig der Fehler ist und ob er behoben werden soll oder nicht. Wenn Sie den Fehler als Warnung einstufen können, kehren Sie zurück boolean. Wenn das Objekt einen schlechten Zustand annimmt, der zukünftige Aufrufe instabil macht, dann werfen Sie ein exception.

Eine andere Praxis besteht darin, objectsanstelle eines Ergebnisses zurückzukehren. Wenn Sie anrufen open, sollte ein FileObjekt zurückgegeben werden oder es nullkann nicht geöffnet werden. Dadurch wird sichergestellt, dass der Programmierer über eine Objektinstanz verfügt, die sich in einem gültigen Status befindet und verwendet werden kann.

BEARBEITEN:

Beachten Sie, dass die meisten Sprachen das Ergebnis einer Funktion verwerfen, wenn der Typ Boolean oder Integer ist. Es ist also möglich, die Funktion aufzurufen, wenn für das Ergebnis keine Zuweisung für die linke Hand vorliegt. Wenn Sie mit booleschen Ergebnissen arbeiten, gehen Sie immer davon aus, dass der Programmierer den zurückgegebenen Wert ignoriert, und entscheiden Sie anhand dessen, ob es sich eher um eine Ausnahme handelt.

Reactgular
quelle
Es ist eine Bestätigung dessen, was ich tue, daher gefällt mir die Antwort :-). Obwohl ich verstehe, woher Sie kommen, verstehe ich nicht, wie dies in den meisten Fällen, in denen ich es verwenden würde, hilft. Ich möchte DRY sein, also werde ich das Objekt auf eine einzige Methode zurückführen, da ich nur eine Sache damit machen möchte. Ich habe dann den gleichen Code, den ich jetzt habe, und speichere ihn mit einer zusätzlichen Methode. (Auch für das angegebene Beispiel lösche ich die Datei, damit ein Null-Datei-Objekt nicht viel aussagt :-)
Ben
Löschen ist schwierig, da dies nicht garantiert ist. Ich habe noch nie gesehen, dass eine Methode zum Löschen von Dateien eine Ausnahme ausgelöst hat, aber was kann der Programmierer tun, wenn dies fehlschlägt? Endlos wiederholen? Nein, es ist ein Problem mit dem Betriebssystem. Der Code sollte das Ergebnis protokollieren und weitermachen.
Reactgular
4

Ihre Intuition dazu ist richtig, es gibt einen besseren Weg, dies zu tun: Monaden .

Was sind Monaden?

Monaden sind (um Wikipedia zu umschreiben) eine Möglichkeit, Operationen miteinander zu verketten, während der Verkettungsmechanismus verborgen bleibt. In Ihrem Fall ist der Verkettungsmechanismus das verschachtelte ifs. Verstecken Sie das und Ihr Code wird viel besser riechen .

Es gibt ein paar Monaden, die genau das tun ("Maybe" und "Either") und zum Glück sind sie Teil einer wirklich schönen Python-Monaden-Bibliothek!

Was sie für Ihren Code tun können

Hier ist ein Beispiel mit der Monade "Either" ("Failable" in der verknüpften Bibliothek), in der eine Funktion je nach Ereignis einen Erfolg oder einen Fehler zurückgeben kann:

import os

class DoSomething(object):

    def remove_file(self, filename):
        try:
            os.remove(filename)
            return Success(None)
        except OSError:
            return Failure("There was an OS Error.")

    @do(Failable)
    def process_file(self, filename):
        do_something()
        yield remove_file(filename)
        do_something_else()
        mreturn(Success("All ok."))

Das sieht vielleicht nicht viel anders aus als das, was Sie jetzt haben, aber überlegen Sie, wie es wäre, wenn Sie mehr Operationen hätten, die zu einem Fehler führen könnten:

    def action_that_might_fail_and_returns_something(self):
        # get some random value between 0 and 1 here
        if value < 0.5:
            return Success(value)
        else:
            return Failure("Bad value! Bad! Go to your room!")

    @do(Failable)
    def process_file(self, filename):
        do_something()
        yield remove_file(filename)
        yield action_that_might_fail(somearg)
        yield another_action_that_might_fail(someotherarg)
        some_val = yield action_that_might_fail_and_returns_something()
        yield something_that_used_the_return_value(some_val)
        do_something_else()
        mreturn(Success("All ok."))

An jeden der yields in der process_fileFunktion, wenn der Funktionsaufruf einen Ausfall kehrt dann die process_filewürde Funktion verlassen aus, an diesem Punkt , den Fehlerwert aus der gescheiterten Funktion zurückkehrte, anstatt weiterhin auf durch den Rest und die Rückkehr derSuccess("All ok.")

Stellen Sie sich vor, Sie machen das oben beschriebene mit verschachtelten ifs! (Wie würden Sie mit dem Rückgabewert umgehen !?)

Fazit

Monaden sind nett :)


Anmerkungen:

Ich bin kein Python-Programmierer - ich habe die oben verlinkte Monadenbibliothek in einem Skript verwendet, das ich für eine Projektautomatisierung ninja gemacht habe. Ich stelle jedoch fest, dass der bevorzugte, idiomatische Ansatz im Allgemeinen darin besteht, Ausnahmen zu verwenden.

IIRC Es gibt einen Tippfehler im lib-Skript auf der Seite, auf die verlinkt ist, obwohl ich vergesse, wo es sich um ATM handelt. Ich werde aktualisieren, wenn ich mich erinnere. Ich habe meine Version gegen die der Seite abgeglichen und festgestellt: def failable_monad_examle():-> def failable_monad_example():- das pin hat examplegefehlt.

Um das Ergebnis einer Failable Decorated-Funktion (wie z. B. process_file) zu erhalten, müssen Sie das Ergebnis in a erfassen variableund a ausführen variable.value, um es zu erhalten.

paul
quelle
2

Eine Funktion ist ein Vertrag, und ihr Name sollte angeben, welchen Vertrag sie erfüllen wird. IMHO, wenn Sie es remove_fileso nennen, sollte es die Datei entfernen , und wenn Sie dies nicht tun, sollte dies eine Ausnahme verursachen. Wenn Sie es jedoch benennen try_remove_file, sollte es versuchen, es zu entfernen, und einen booleschen Wert zurückgeben, um festzustellen, ob die Datei entfernt wurde oder nicht.

Dies würde zu einer anderen Frage führen - sollte es sein remove_fileoder try_remove_file? Das hängt von Ihrer Anrufstelle ab. Eigentlich können Sie beide Methoden haben und sie in verschiedenen Szenarien verwenden, aber ich denke, dass das Entfernen von Dateien per se eine hohe Erfolgschance hat, also bevorzuge ich, nur remove_filediese Ausnahmebedingung zu haben, wenn dies fehlschlägt.

tia
quelle
0

In diesem speziellen Fall kann es hilfreich sein, darüber nachzudenken, warum Sie die Datei möglicherweise nicht entfernen können. Angenommen, das Problem besteht darin, dass die Datei möglicherweise vorhanden ist oder nicht. Dann sollten Sie eine Funktion haben doesFileExist(), die true oder false zurückgibt, und eine Funktion removeFile(), die nur die Datei löscht.

In Ihrem Code würden Sie zuerst überprüfen, ob die Datei vorhanden ist. Wenn ja, rufen Sie an removeFile. Wenn nicht, dann mache andere Sachen.

In diesem Fall möchten Sie möglicherweise immer noch removeFileeine Ausnahme auslösen, wenn die Datei aus einem anderen Grund, z. B. aufgrund von Berechtigungen, nicht entfernt werden kann.

Zusammenfassend gesagt, sollten Ausnahmen für Dinge geworfen werden, die außergewöhnlich sind. Wenn es also ganz normal ist, dass die zu löschende Datei nicht existiert, ist dies keine Ausnahme. Schreiben Sie ein boolesches Prädikat, um dies zu überprüfen. Wenn Sie jedoch nicht über die Schreibberechtigungen für die Datei verfügen oder wenn Sie sich in einem fernen Dateisystem befinden, auf das plötzlich nicht mehr zugegriffen werden kann, sind dies möglicherweise Ausnahmebedingungen.

Dima
quelle
Das ist sehr spezifisch für das Beispiel, das ich gegeben habe und das ich lieber meide. Ich habe dies noch nicht geschrieben, es wird Dateien archivieren und die Tatsache, dass dies geschehen ist, in der Datenbank protokollieren. Dateien können jederzeit neu geladen werden (auch wenn es sehr viel unwahrscheinlicher ist, dass geladene Dateien erneut geladen werden), ist es möglich, dass eine Datei zwischen der Überprüfung und dem Löschversuch durch einen anderen Prozess gesperrt wird. Ein Misserfolg ist nichts Besonderes. Es ist Standard-Python, sich nicht die Mühe zu machen, zuerst zu prüfen und die Ausnahme zu erkennen, wenn sie ausgelöst wird (falls erforderlich). Ich möchte diesmal einfach nichts damit anfangen.
Ben
Wenn der Fehler nicht außergewöhnlich ist, ist die Überprüfung, ob Sie eine Datei entfernen können, ein legitimer Bestandteil Ihrer Programmlogik. Das Prinzip der einmaligen Verantwortung schreibt vor, dass Sie eine Prüffunktion und eine removeFile-Funktion haben müssen.
Dima