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


15

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

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

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

これは、私が話していることを示す本当に簡単な例です。

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が大量に発生することがあります。しかし、もっと簡単な方法は考えられません。

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

これは「受け入れられる」設計ですか。そうでない場合、これを設計するより良い方法は何でしょうか?

回答:


11

booleanメソッド/関数が論理的な決定を下すのに役立つとき、あなたは戻るべきです。

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

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

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

編集:

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


それは私がやっていることの検証なので、答えが好きです:-)。オブジェクトについては、あなたがどこから来たのか理解していますが、私がこれを使用するほとんどの場合にこれがどのように役立つかはわかりません。私はDRYになりたいので、オブジェクトを1つのメソッドに戻すだけです。1つのことをやりたいだけです。その後、現在のコードと同じコードを残し、追加のメソッドで保存します。(また、与えられた例では、ファイルを削除しているので、nullファイルオブジェクトはあまり語らない:
ベン

削除は保証されないため、注意が必要です。ファイル削除メソッドが例外をスローするのを見たことはありませんが、失敗した場合、プログラマは何ができますか?継続的にループを再試行しますか?いいえ、それはOSの問題です。コードは結果を記録し、先に進む必要があります。
Reactgular

4

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

モナドとは何ですか?

モナドは(Wikipediaを換言すると)連鎖メカニズムを隠しつつ操作を連鎖する方法です。あなたの場合、連鎖メカニズムはネストされたifsです。それを隠すと、コードの匂いがずっと良くなります。

それを行うモナドがいくつかあり( "Maybe"および "Either")、幸運なことに、それらは本当に素晴らしいpythonモナドライブラリの一部です

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

以下は、「Either」モナド(リンクされたライブラリの「Failable」)を使用した例です。関数は、発生した内容に応じてSuccessまたはFailureを返すことができます。

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

関数yield内の各sで、process_file関数呼び出しがFailureを返した場合、process_file関数は終了します。はその時点で残りの部分を続行して返すのではなく、失敗した関数からFailure値を返しますSuccess("All ok.")

さて、入れ子で上記を行うことを想像してください if sで!(戻り値をどのように処理しますか?)

結論

モナドはいいです:)


ノート:

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

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

Failable装飾関数(などprocess_file)の結果を取得するには、結果をaにキャプチャし、それを取得するためにa variableを実行するvariable.value必要があります。


2

関数はコントラクトであり、その名前はどのコントラクトが実現するかを示唆する必要があります。私見、名前を付けるとremove_fileファイルが削除され、そうしないと例外が発生します。一方、名前を付けるとtry_remove_file場合、ファイルを削除したかどうかを判断するために、「try」を削除してブール値を返す必要があります。

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


0

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

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

この場合removeFile、アクセス許可などの他の理由でファイルを削除できない場合でも、例外をスローすることができます。

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


それは私が与えた例に非常に特有のものであり、むしろ避けたい。私はまだこれを書いていません。ファイルをアーカイブし、データベースでこれが起こったという事実を記録します。ファイルはいつでもリロードできます(一度ロードされたファイルがリロードされる可能性ははるかに低いですが)が、チェックと削除の試行の間、別のプロセスによってファイルがロックされる可能性があります。失敗について特別なことは何もありません。最初にチェックを行わず、発生した場合に例外をキャッチする(必要な場合)のは標準Pythonです。今回は何もしたくないだけです。
ベン

障害について例外的なものがない場合、ファイルを削除できるかどうかを確認することは、プログラムロジックの正当な部分です。単一の責任原則により、check関数とremoveFile関数が必要です。
ディマ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.