web-dev-qa-db-ja.com

成功または失敗のみが懸念事項である場合にブール値を返す

メソッドの周囲のすべてのロジックを1か所に含めるために、複数の場所で使用されるメソッドからブール値を返すことがよくあります。 (内部)呼び出しメソッドが知る必要があるのは、操作が成功したかどうかだけです。

Python=を使用していますが、質問は必ずしもその言語に固有のものではありません。考えられる選択肢は2つだけです。

  1. 例外ではないが例外を発生させ、関数が呼び出されるすべての場所でその例外をキャッチすることを忘れないでください。
  2. 私がしているようにブール値を返します。

これはreallyの簡単な例で、私が話していることを示しています。

_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()
_

それは機能的ですが、私はこの方法で何かをするのは本当に嫌いです。「においがする」ので、場合によってはネストされたifがたくさん発生することがあります。しかし、もっと簡単な方法は思いつきません。

削除を試みる前に、よりLBYLの考え方に戻り、os.path.exists(filename)を使用することもできますが、その間にファイルがロックされていないという保証はありません(可能性は低いですが可能です)。削除が成功したかどうか。

これは「許容できる」設計ですか、そうでない場合、これを設計するためのより良い方法は何でしょうか?

15
Ben

メソッド/関数が論理的な決定に役立つ場合は、booleanを返す必要があります。

メソッド/関数が論理的な決定に使用される可能性が低い場合は、exceptionをスローする必要があります。

障害の重要性と、対処する必要があるかどうかを決定する必要があります。失敗を警告として分類できる場合は、booleanを返します。オブジェクトが不正な状態になり、その後の呼び出しが不安定になる場合は、exceptionをスローします。

別の方法は、結果の代わりにobjectsを返すことです。 openを呼び出すと、Fileオブジェクトを返すか、開くことができない場合はnullを返します。これにより、プログラマは、使用できる有効な状態のオブジェクトインスタンスを確実に持つことができます。

編集:

ほとんどの言語では、型がブール型または整数型の場合、関数の結果が破棄されることに注意してください。したがって、結果に対する左辺の割り当てがないときに関数を呼び出すことができます。ブール値の結果を処理するときは常に、プログラマーが戻り値を無視していると想定し、それを使用して、それが例外であるかどうかを判断してください。

11
Reactgular

これに関するあなたの直感は正しいです、これを行うより良い方法があります:monads

モナドとは何ですか?

モナドは(言い換えればWikipediaですが)連鎖メカニズムを隠しながら操作を連鎖させる方法です。あなたの場合、連鎖メカニズムはネストされたifsです。それを非表示にすると、コードの臭いがよくなります

それだけを行うモナドがいくつかあり(「たぶん」と「どちらか」)、ラッキーなことに、それらは一部です 本当にいいpythonモナドライブラリ!

あなたのコードに対して彼らができること

「Either」モナド(リンクされているライブラリでは「Failable」)を使用した例を次に示します。関数は、発生した内容に応じて、成功または失敗を返すことができます。

_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."))
_

さて、これはあなたが今持っているものと大差ないように見えるかもしれませんが、失敗につながる可能性のある操作がもっとあったらどうなるかを考えてください:

_    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."))
_

_process_file_関数の各yieldsで、関数呼び出しが失敗を返した場合、_process_file_関数はその時点でを終了します、残りの部分を続行してSuccess("All ok.")を返す代わりに、失敗した関数からFailure値を返します

さて、ネストされたifsを使用して上記を行うことを想像してください! (戻り値をどのように処理しますか?)

結論

モナドはいいです:)


注:

私はPythonプログラマではありません-プロジェクトの自動化のために忍者で使用したスクリプトで、上記にリンクされたモナドライブラリを使用しました。ただし、一般的には、推奨される慣用的なアプローチは例外を使用することです。

IIRCリンク先のページのlibスクリプトにタイプミスがありますが、ATMの場所を忘れてしまいました。覚えていれば更新します。 私のバージョンをページのdiffで比較したところ、def failable_monad_examle():-> def failable_monad_example():-pexampleがありませんでした。

Failableデコレート関数(_process_file_など)の結果を取得するには、variableに結果をキャプチャし、_variable.value_を実行して取得する必要があります。

4
paul

関数はコントラクトであり、その名前はどのコントラクトが履行するかを示唆しているはずです。私見、名前を付ければremove_fileそのため、ファイルを削除する必要があり、そうしないと例外が発生します。一方、名前を付けるとtry_remove_file、削除してブール値を返し、ファイルが削除されたかどうかを通知する必要があります。

これは別の質問につながります-remove_fileまたはtry_remove_file?それはあなたの呼び出しサイトに依存します。実際には、両方の方法を使用して、異なるシナリオでそれらを使用できますが、ファイル自体を削除すると成功する可能性が高いと思うので、remove_file失敗すると例外をスローします。

2
tia

この特定のケースでは、ファイルを削除できない場合がある理由について考えることが役立つ場合があります。問題は、ファイルが存在する場合と存在しない場合があるということです。次に、trueまたはfalseを返す関数doesFileExist()と、ファイルを削除するだけの関数removeFile()が必要です。

コードでは、最初にファイルが存在するかどうかを確認します。存在する場合は、removeFileを呼び出します。そうでない場合は、他のことを行います。

この場合でも、パーミッションなどの他の理由でファイルを削除できない場合は、removeFileで例外をスローする必要があります。

要約すると、例外的なものに対して例外をスローする必要があります。したがって、削除しようとしているファイルが存在しないことが完全に正常である場合、それは例外ではありません。それをチェックするブール述語を記述します。一方、ファイルへの書き込み権限がない場合、または突然アクセスできなくなったリモートファイルシステム上にある場合は、例外的な状況である可能性があります。

0
Dima