「例外を使用しないでください!」という一般的なコンセンサスは、主に他の言語に由来し、時には時代遅れのものもあります。
C ++では、「スタックの巻き戻し」のために例外をスローするのは非常にコストがかかります。すべてのローカル変数宣言はwith
Pythonのステートメントのようなものであり、その変数のオブジェクトはデストラクタを実行できます。これらのデストラクタは、例外がスローされるときに実行されますが、関数から戻るときにも実行されます。この「RAIIイディオム」は不可欠な言語機能であり、堅牢で正しいコードを記述するために非常に重要です。したがって、RAIIと安価な例外は、C ++がRAIIに対して決定したトレードオフでした。
初期のC ++では、多くのコードは例外に対して安全な方法で記述されていませんでした。実際にRAIIを使用しない限り、メモリやその他のリソースを簡単に漏らすことができます。そのため、例外をスローすると、そのコードが不正確になります。C ++標準ライブラリでさえ例外を使用しているため、これはもはや妥当ではありません。例外が存在しないふりをすることはできません。ただし、CコードとC ++を組み合わせる場合、例外は依然として問題です。
Javaでは、すべての例外にスタックトレースが関連付けられています。スタックトレースは、エラーをデバッグする際に非常に貴重ですが、例外が出力されない場合、たとえば制御フローにのみ使用されるため、無駄な労力です。
したがって、これらの言語では、例外は制御フローとして使用するには「非常に高価」です。Pythonでは、これは問題ではなく、例外ははるかに安価です。さらに、Python言語には、他の制御フロー構造と比較して例外のコストが気付かれないオーバーヘッドがすでにあります。たとえば、明示的なメンバーシップテストでdictエントリが存在するかどうかのチェックif key in the_dict: ...
は、エントリにアクセスしthe_dict[key]; ...
てKeyErrorを取得します。一部の統合言語機能(ジェネレーターなど)は、例外の観点から設計されています。
したがって、Pythonで例外を明確に回避する技術的な理由はありませんが、戻り値の代わりに例外を使用する必要があるかどうかは依然として疑問です。例外に関する設計レベルの問題は次のとおりです。
それらはまったく明らかではありません。関数を簡単に調べて、どの例外がスローされるかを確認することはできないため、何をキャッチすべきかを常に把握しているとは限りません。戻り値はより明確になる傾向があります。
例外は、コードを複雑にする非ローカル制御フローです。例外をスローすると、制御フローが再開される場所がわかりません。すぐに処理できないエラーの場合、これはおそらく良い考えです。呼び出し元に条件を通知する場合、これはまったく不要です。
Python文化は一般的に例外を支持して傾斜していますが、簡単に行き過ぎてしまいます。list_contains(the_list, item)
リストにそのアイテムと等しいアイテムが含まれているかどうかをチェックする関数を想像してください。結果が次のように呼び出さなければならないため、絶対に迷惑な例外を介して伝えられる場合:
try:
list_contains(invited_guests, person_at_door)
except Found:
print("Oh, hello {}!".format(person_at_door))
except NotFound:
print("Who are you?")
boolを返すと、より明確になります。
if list_contains(invited_guests, person_at_door):
print("Oh, hello {}!".format(person_at_door))
else:
print("Who are you?")
関数がすでに値を返すことになっている場合、人々はこの値をチェックするのを忘れるので、特別な条件に対して特別な値を返すことはかなりエラーを起こしやすいです(おそらくCの問題の1/3の原因です)。通常、例外はより正確です。
良い例は、 `haystack文字列でpos = find_string(haystack, needle)
最初に出現するneedle
文字列を検索し、開始位置を返す関数です。しかし、haystack-stringにneedle-stringが含まれていない場合はどうでしょうか?
Cによる解決策とPythonによる模倣は、特別な値を返すことです。CではこれはNULLポインターであり、Pythonではこれは-1
です。これは、特に-1
Pythonで有効なインデックスのように、チェックなしで位置が文字列インデックスとして使用される場合、驚くべき結果につながります。Cでは、NULLポインターは少なくともセグメンテーション違反を引き起こします。
PHPでは、異なるタイプの特別な値FALSE
、つまり整数ではなくブール値が返されます。結局のところ、これは言語の暗黙の変換規則のために実際にはこれ以上良くありません(ただし、Pythonではブール値もintとして使用できることに注意してください!)。一貫性のある型を返さない関数は、一般的に非常に紛らわしいと考えられています。
より堅牢なバリアントは、文字列が見つからないときに例外をスローすることでした。これにより、通常の制御フロー中に、通常の値の代わりに誤って特別な値を使用できないようになります。
try:
pos = find_string(haystack, needle)
do_something_with(pos)
except NotFound:
...
あるいは、直接使用できないが最初にラップを解除する必要がある型を常に返すことができます。たとえば、ブール値が例外が発生したか、結果が使用可能かを示す結果ブールタプルです。次に:
pos, ok = find_string(haystack, needle)
if not ok:
...
do_something_with(pos)
これにより、問題をすぐに処理する必要がありますが、非常にすばやく迷惑になります。また、機能を簡単にチェーンすることもできません。すべての関数呼び出しには、3行のコードが必要になりました。Golangは、この迷惑は安全に値すると考えている言語です。
要約すると、例外は完全に問題がないわけではなく、特に「通常の」戻り値を置き換える場合に、間違いなく使い古される可能性があります。ただし、特別な条件(必ずしもエラーだけでなく)を通知するために使用する場合、例外はクリーンで直感的で使いやすく、誤用しにくいAPIの開発に役立ちます。