メソッドの周囲のすべてのロジックを1か所に含めるために、複数の場所で使用されるメソッドからブール値を返すことがよくあります。 (内部)呼び出しメソッドが知る必要があるのは、操作が成功したかどうかだけです。
Python=を使用していますが、質問は必ずしもその言語に固有のものではありません。考えられる選択肢は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)
を使用することもできますが、その間にファイルがロックされていないという保証はありません(可能性は低いですが可能です)。削除が成功したかどうか。
これは「許容できる」設計ですか、そうでない場合、これを設計するためのより良い方法は何でしょうか?
メソッド/関数が論理的な決定に役立つ場合は、boolean
を返す必要があります。
メソッド/関数が論理的な決定に使用される可能性が低い場合は、exception
をスローする必要があります。
障害の重要性と、対処する必要があるかどうかを決定する必要があります。失敗を警告として分類できる場合は、boolean
を返します。オブジェクトが不正な状態になり、その後の呼び出しが不安定になる場合は、exception
をスローします。
別の方法は、結果の代わりにobjects
を返すことです。 open
を呼び出すと、File
オブジェクトを返すか、開くことができない場合はnull
を返します。これにより、プログラマは、使用できる有効な状態のオブジェクトインスタンスを確実に持つことができます。
編集:
ほとんどの言語では、型がブール型または整数型の場合、関数の結果が破棄されることに注意してください。したがって、結果に対する左辺の割り当てがないときに関数を呼び出すことができます。ブール値の結果を処理するときは常に、プログラマーが戻り値を無視していると想定し、それを使用して、それが例外であるかどうかを判断してください。
これに関するあなたの直感は正しいです、これを行うより良い方法があります:monads。
モナドとは何ですか?
モナドは(言い換えればWikipediaですが)連鎖メカニズムを隠しながら操作を連鎖させる方法です。あなたの場合、連鎖メカニズムはネストされたif
sです。それを非表示にすると、コードの臭いがよくなります。
それだけを行うモナドがいくつかあり(「たぶん」と「どちらか」)、ラッキーなことに、それらは一部です 本当にいい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
_関数の各yield
sで、関数呼び出しが失敗を返した場合、_process_file
_関数はその時点でを終了します、残りの部分を続行してSuccess("All ok.")
を返す代わりに、失敗した関数からFailure値を返します
さて、ネストされたif
sを使用して上記を行うことを想像してください! (戻り値をどのように処理しますか?)
結論
モナドはいいです:)
注:
私はPythonプログラマではありません-プロジェクトの自動化のために忍者で使用したスクリプトで、上記にリンクされたモナドライブラリを使用しました。ただし、一般的には、推奨される慣用的なアプローチは例外を使用することです。
IIRCリンク先のページのlibスクリプトにタイプミスがありますが、ATMの場所を忘れてしまいました。覚えていれば更新します。 私のバージョンをページのdiffで比較したところ、def failable_monad_examle():
-> def failable_monad_example():
-p
のexample
がありませんでした。
Failableデコレート関数(_process_file
_など)の結果を取得するには、variable
に結果をキャプチャし、_variable.value
_を実行して取得する必要があります。
関数はコントラクトであり、その名前はどのコントラクトが履行するかを示唆しているはずです。私見、名前を付ければremove_file
そのため、ファイルを削除する必要があり、そうしないと例外が発生します。一方、名前を付けるとtry_remove_file
、削除してブール値を返し、ファイルが削除されたかどうかを通知する必要があります。
これは別の質問につながります-remove_file
またはtry_remove_file
?それはあなたの呼び出しサイトに依存します。実際には、両方の方法を使用して、異なるシナリオでそれらを使用できますが、ファイル自体を削除すると成功する可能性が高いと思うので、remove_file
失敗すると例外をスローします。
この特定のケースでは、ファイルを削除できない場合がある理由について考えることが役立つ場合があります。問題は、ファイルが存在する場合と存在しない場合があるということです。次に、trueまたはfalseを返す関数doesFileExist()
と、ファイルを削除するだけの関数removeFile()
が必要です。
コードでは、最初にファイルが存在するかどうかを確認します。存在する場合は、removeFile
を呼び出します。そうでない場合は、他のことを行います。
この場合でも、パーミッションなどの他の理由でファイルを削除できない場合は、removeFile
で例外をスローする必要があります。
要約すると、例外的なものに対して例外をスローする必要があります。したがって、削除しようとしているファイルが存在しないことが完全に正常である場合、それは例外ではありません。それをチェックするブール述語を記述します。一方、ファイルへの書き込み権限がない場合、または突然アクセスできなくなったリモートファイルシステム上にある場合は、例外的な状況である可能性があります。