私は本 "Head First Python" (今年学ぶのが私の言語です)を読んでいて、2つのコード手法について彼らが議論するセクションに行きました:
最初のチェックと例外処理の比較。
Pythonコードのサンプル:
# Checking First
for eachLine in open("../../data/sketch.txt"):
if eachLine.find(":") != -1:
(role, lineSpoken) = eachLine.split(":",1)
print("role=%(role)s lineSpoken=%(lineSpoken)s" % locals())
# Exception handling
for eachLine in open("../../data/sketch.txt"):
try:
(role, lineSpoken) = eachLine.split(":",1)
print("role=%(role)s lineSpoken=%(lineSpoken)s" % locals())
except:
pass
最初の例は、.split
関数の問題を直接扱います。 2つ目は、例外ハンドラーで処理するだけです(問題は無視されます)。
彼らは本の中で、最初にチェックする代わりに例外処理を使用するように主張しています。議論は、例外コードがすべてのエラーをキャッチするということです。最初にチェックすると、あなたが考えていることだけをキャッチします(そして、コーナーケースを逃します)。私は最初にチェックするように教えられたので、私の最初の本能はそれを行うことでしたが、彼らの考えは興味深いです。例外処理を使用してケースを処理することは考えていませんでした。
2つのうちどちらが一般に優れていると考えられていますか?
.NETでは、例外の多用を避けるのが一般的です。 1つの引数はパフォーマンスです。NETでは、例外をスローすると計算コストが高くなります。
それらの過度の使用を回避するもう1つの理由は、それらに過度に依存しているコードを読み取ることが非常に困難になる可能性があることです。 Joel Spolskyの ブログエントリ は、問題をうまく説明しています。
議論の中心は次の引用です:
推論は、例外はコードのあるポイントから別のポイントに突然ジャンプするという点で、1960年代以降有害と考えられている「gotoの例外」と同じだと私は考えています。実際、それらはgotoのものよりも著しく悪いです:
1。それらはソースコードでは見えません。例外をスローする場合とスローしない場合がある関数を含むコードのブロックを見ると、どの例外がどこからスローされるかを確認する方法はありません。これは、注意深いコード検査でさえ潜在的なバグを明らかにしないことを意味します。
2。それらは、関数に対して非常に多くの可能な出口点を作成します。正しいコードを書くためには、関数を通るすべての可能なコードパスについて本当に考える必要があります。例外を発生させてその場でキャッチしない関数を呼び出すたびに、関数が突然終了し、データが一貫性のない状態になるか、その他のコードパスがなかったために、突然のバグが発生する可能性があります。について考える。
個人的には、自分のコードが契約どおりに実行できない場合に例外をスローします。 SOAPの呼び出し、データベースの呼び出し、ファイルのIO、システムの呼び出しなど)プロセスの境界の外で何かを処理しようとしているときに、try/catchを使用する傾向があります。それ以外の場合、私は防御的にコード化しようとします。これは難しくて速い規則ではありませんが、それは一般的な習慣です。
Scott Hanselmanは、.NETの例外についても書いています here 。この記事では、例外に関するいくつかの経験則について説明します。私のお気に入り?
常に発生することに対して例外をスローするべきではありません。それから彼らは「普通」になるでしょう。
Python特に、通常、例外をキャッチする方がよいと考えられています。呼び出される傾向があります 許可よりも許しを求めるのは簡単です (EAFP) Look Before You Leap(LBYL)。LBYLが 微妙なバグ を与える場合があります。
ただし、注意してください bare except:
statement だけでなく、overbroad exceptステートメント。どちらもバグをマスクすることができるため、次のようなものが良いでしょう。
for eachLine in open("../../data/sketch.txt"):
try:
role, lineSpoken = eachLine.split(":",1)
except ValueError:
pass
else:
print("role=%(role)s lineSpoken=%(lineSpoken)s" % locals())
あなたは防御的であるべきですが、ある程度まで。あなたは例外処理を書くべきですが、ある程度まで。ここに私が住んでいるので、ここでは例としてWebプログラミングを使用します。
これは、大規模なチームシナリオでの経験に基づいています。
ISSの中にいつも宇宙服を着ていたとしましょう。バスルームに行ったり、食事をしたりするのは難しいでしょう。宇宙モジュールの中で非常にかさばると動き回ります。 。それはひどいです。コード内にたくさんのトライキャッチを書くのは、そのようなものです。あなたが言うにはいくつかのポイントが必要です。私はISSを確保しました。わかりましたので、起こり得るすべてのシナリオで宇宙服を着ることは実際的ではありません。
この本の主な論点は、独自のエラーチェックを記述しようとした場合に見逃していた可能性があるものをすべてキャッチするため、コードの例外バージョンの方が優れているということです。
この説明は、出力が正しいかどうかを気にしない非常に特殊な状況でのみ当てはまると思います。
raising例外が健全で安全な方法であることは間違いありません。プログラムの現在の状態で、(開発者として)処理できない、または処理したくない何かがあると感じた場合は、必ずそうする必要があります。
ただし、例はcatching例外についてです。例外をキャッチした場合、あなたはnotを見落としたかもしれないシナリオから身を守っています。あなたは正確に反対のことをしています:このタイプの例外を引き起こした可能性のあるシナリオを見逃していないと想定しているので、それをキャッチしても問題がないと確信しています(したがって、プログラムが終了するのを防ぎます)キャッチされない例外がそうであるように)。
例外アプローチを使用して、ValueError
例外が発生した場合は、行をスキップします。従来の例外以外のアプローチを使用して、split
からの戻り値の数をカウントし、2未満の場合は行をスキップします。従来のエラーチェックで他のいくつかの「エラー」状況を忘れた可能性があり、except ValueError
がそれらをキャッチするので、例外アプローチでより安全に感じる必要がありますか?
これはプログラムの性質によって異なります。
たとえば、Webブラウザーやビデオプレーヤーを作成している場合、入力に関する問題が原因で、キャッチされない例外によってクラッシュすることはありません。終了するよりも、(厳密に言えば、正しくない場合でも)リモートで意味のある何かを出力する方がはるかに優れています。
正確さが重要なアプリケーション(ビジネスソフトウェアやエンジニアリングソフトウェアなど)を作成している場合、これはひどいアプローチになります。 ValueError
を発生させるいくつかのシナリオを忘れた場合、あなたができる最悪のことは、この未知のシナリオを黙って無視し、単に行をスキップすることです。これが、非常に微妙でコストのかかるバグがソフトウェアで発生する方法です。
このコードでValueError
を確認できる唯一の方法は、split
が(2つではなく)1つの値しか返さない場合だと思うかもしれません。しかし、ある条件の下でprint
ステートメントが後でValueError
を発生させる式の使用を開始するとどうなりますか?これにより、:
が欠落しているためではなく、print
が失敗したために、一部の行がスキップされます。これは私が以前に言及した微妙なバグの例です-何も気付かず、いくつかの行を失うだけです。
私が推奨するのは、不正な出力を生成する方が終了するよりも悪い場合に、コードで例外をキャッチしない(ただし、発生させない!)このようなコードで例外をキャッチするのは、ほんのささいな式があるときだけなので、考えられる例外の種類のそれぞれの原因を簡単に判断できます。
例外の使用によるパフォーマンスへの影響については、例外が頻繁に発生しない限り、(Pythonでは)取るに足らないことです。
例外を使用して日常的に発生する条件を処理する場合、場合によっては莫大なパフォーマンスコストを支払うことがあります。たとえば、いくつかのコマンドをリモートで実行するとします。コマンドテキストが少なくとも最小限の検証(構文など)に合格していることを確認できます。または、例外が発生するのを待つこともできます(これは、リモートサーバーがコマンドを解析して問題を見つけた後でのみ発生します)。明らかに、前者は桁違いに速いです。別の簡単な例:除算を実行してからZeroDivisionError例外をキャッチするよりも、数値がゼロであるかどうかを〜10倍速く確認できます。
これらの考慮事項は、不正なコマンド文字列をリモートサーバーに頻繁に送信するか、除算に使用するゼロ値の引数を受信する場合にのみ重要です。
注:単なるexcept
の代わりにexcept ValueError
を使用すると思います。他の人が指摘したように、そして本自体が数ページで述べているように、あなたは裸のexcept
を決して使用すべきではありません。
別のメモ:例外のない適切なアプローチは、:
を検索するのではなく、split
によって返される値の数をカウントすることです。後者はsplit
によって行われた作業を繰り返し、実行時間をほぼ2倍にする可能性があるため、非常に低速です。
原則として、ステートメントが無効な結果を生成する可能性があることがわかっている場合は、それをテストして対処してください。予期しないものには例外を使用してください。 「例外的」なもの。契約上の意味でコードがより明確になります(例として「nullにするべきではない」)。
これまでうまくいったものを使用してください。
例外処理と防御的プログラミングは、同じ意図を表現するための異なる方法です。
TBH、_try/except
_メカニックまたはif
ステートメントチェックを使用するかどうかは関係ありません。ほとんどのPythonベースラインでEAFPとLBYLの両方がよく見られますが、EAFPの方がやや一般的です。EAFPが時々ある多くより読みやすく/慣用的ですが、この特定のケースではどちらの方法でも問題ないと思います。
しかしながら...
現在の参照を使用する場合は注意が必要です。コードに関するいくつかの明白な問題:
with
イディオムを使用する必要があります。例外はほとんどありませんが、これはその1つではありません。except
ステートメント)role, lineSpoken = eachLine.split(":",1)
IvcはこれとEAFPについて良い答えを持っていますが、記述子もリークしています。
LBYLバージョンは必ずしもEAFPバージョンほどパフォーマンスが良いとは限らないため、例外のスローは「パフォーマンスの点で高価」であると断定すると、間違いです。それは本当にあなたが処理している文字列のタイプに依存します:
_In [33]: def lbyl(lines):
...: for line in lines:
...: if line.find(":") != -1:
...: # Nuke the parens, do Tuple unpacking like an idiomatic Python dev.
...: role, lineSpoken = line.split(":",1)
...: # no print, since output is obnoxiously long with %timeit
...:
In [34]: def eafp(lines):
...: for line in lines:
...: try:
...: # Nuke the parens, do Tuple unpacking like an idiomatic Python dev.
...: role, lineSpoken = eachLine.split(":",1)
...: # no print, since output is obnoxiously long with %timeit
...: except:
...: pass
...:
In [35]: lines = ["abc:def", "onetwothree", "xyz:hij"]
In [36]: %timeit lbyl(lines)
100000 loops, best of 3: 1.96 µs per loop
In [37]: %timeit eafp(lines)
100000 loops, best of 3: 4.02 µs per loop
In [38]: lines = ["a"*100000 + ":" + "b", "onetwothree", "abconetwothree"*100]
In [39]: %timeit lbyl(lines)
10000 loops, best of 3: 119 µs per loop
In [40]: %timeit eafp(lines)
100000 loops, best of 3: 4.2 µs per loop
_