web-dev-qa-db-ja.com

最初と例外処理をチェックしますか?

私は本 "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つのうちどちらが一般に優れていると考えられていますか?

92
jmq

.NETでは、例外の多用を避けるのが一般的です。 1つの引数はパフォーマンスです。NETでは、例外をスローすると計算コストが高くなります。

それらの過度の使用を回避するもう1つの理由は、それらに過度に依存しているコードを読み取ることが非常に困難になる可能性があることです。 Joel Spolskyの ブログエントリ は、問題をうまく説明しています。

議論の中心は次の引用です:

推論は、例外はコードのあるポイントから別のポイントに突然ジャンプするという点で、1960年代以降有害と考えられている「gotoの例外」と同じだと私は考えています。実際、それらはgotoのものよりも著しく悪いです:

1。それらはソースコードでは見えません。例外をスローする場合とスローしない場合がある関数を含むコードのブロックを見ると、どの例外がどこからスローされるかを確認する方法はありません。これは、注意深いコード検査でさえ潜在的なバグを明らかにしないことを意味します。

2。それらは、関数に対して非常に多くの可能な出口点を作成します。正しいコードを書くためには、関数を通るすべての可能なコードパスについて本当に考える必要があります。例外を発生させてその場でキャッチしない関数を呼び出すたびに、関数が突然終了し、データが一貫性のない状態になるか、その他のコードパスがなかったために、突然のバグが発生する可能性があります。について考える。

個人的には、自分のコードが契約どおりに実行できない場合に例外をスローします。 SOAPの呼び出し、データベースの呼び出し、ファイルのIO、システムの呼び出しなど)プロセスの境界の外で何かを処理しようとしているときに、try/catchを使用する傾向があります。それ以外の場合、私は防御的にコード化しようとします。これは難しくて速い規則ではありませんが、それは一般的な習慣です。

Scott Hanselmanは、.NETの例外についても書いています here 。この記事では、例外に関するいくつかの経験則について説明します。私のお気に入り?

常に発生することに対して例外をスローするべきではありません。それから彼らは「普通」になるでしょう。

69
Kyle Hodgson

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())
78
lvc

実用的なアプローチ

あなたは防御的であるべきですが、ある程度まで。あなたは例外処理を書くべきですが、ある程度まで。ここに私が住んでいるので、ここでは例としてWebプログラミングを使用します。

  1. すべてのユーザー入力が不良であり、防御的にデータタイプの検証、パターンチェック、および悪意のある挿入のポイントにのみ書き込むと想定します。防御的プログラミングは、制御できない非常に頻繁に発生する可能性のあるものでなければなりません。
  2. 時々失敗する可能性があるネットワークサービスの例外処理を記述し、ユーザーフィードバックのために適切に処理します。例外プログラミングは、時々失敗する可能性があるが、通常は安定しており、プログラムを機能させ続ける必要があるネットワーク化されたものに使用する必要があります。
  3. 入力データが検証された後、わざわざアプリケーション内で防御的に書き込む必要はありません。それは時間の無駄であり、あなたのアプリを膨らませます。それが爆発するのは、何か非常にまれ処理する価値がないか、ステップ1と2をより注意深く検討する必要があるということです。
  4. ネットワークデバイスに依存しないコアコード内で例外処理を記述しないでください。そうすることは悪いプログラミングであり、パフォーマンスにコストがかかります。たとえば、ループの範囲外の配列の場合にtry-catchを記述することは、最初からループを正しくプログラムしていなかったことを意味します。
  5. 上記の手順を実行した後、1つの場所で例外をキャッチする中央のエラーログによってすべてを処理します。 Edgeのすべてのケースは無限である可能性があるため、キャッチすることはできません。必要な操作を処理するコードを記述するだけで済みます。そのため、最後の手段として中央のエラー処理を使用します。
  6. TDDはニースです。ある意味、膨らむことなく試してみることができます。つまり、正常な動作が保証されます。
  7. ボーナスポイントはコードカバレッジツールを使用することです。たとえば、イスタンブールは、テストしていない場所を示しているため、ノードに適しています。
  8. 警告これのすべては開発者に優しい例外です。たとえば、構文を誤って使用し、その理由を説明すると、言語によってスローされます。したがって、コードの大部分が依存するtilityライブラリが必要です。

これは、大規模なチームシナリオでの経験に基づいています。

アナロジー

ISSの中にいつも宇宙服を着ていたとしましょう。バスルームに行ったり、食事をしたりするのは難しいでしょう。宇宙モジュールの中で非常にかさばると動き回ります。 。それはひどいです。コード内にたくさんのトライキャッチを書くのは、そのようなものです。あなたが言うにはいくつかのポイントが必要です。私はISSを確保しました。わかりましたので、起こり得るすべてのシナリオで宇宙服を着ることは実際的ではありません。

27
Jason Sebring

この本の主な論点は、独自のエラーチェックを記述しようとした場合に見逃していた可能性があるものをすべてキャッチするため、コードの例外バージョンの方が優れているということです。

この説明は、出力が正しいかどうかを気にしない非常に特殊な状況でのみ当てはまると思います。

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倍にする可能性があるため、非常に低速です。

15
max

原則として、ステートメントが無効な結果を生成する可能性があることがわかっている場合は、それをテストして対処してください。予期しないものには例外を使用してください。 「例外的」なもの。契約上の意味でコードがより明確になります(例として「nullにするべきではない」)。

6
Ian

これまでうまくいったものを使用してください。

  • コードの可読性と効率の点で選択したプログラミング言語
  • あなたのチームと合意された一連のコード規約

例外処理と防御的プログラミングは、同じ意図を表現するための異なる方法です。

2
Sri

TBH、_try/except_メカニックまたはifステートメントチェックを使用するかどうかは関係ありません。ほとんどのPythonベースラインでEAFPとLBYLの両方がよく見られますが、EAFPの方がやや一般的です。EAFPが時々ある多くより読みやすく/慣用的ですが、この特定のケースではどちらの方法でも問題ないと思います。

しかしながら...

現在の参照を使用する場合は注意が必要です。コードに関するいくつかの明白な問題:

  1. ファイル記述子がリークされます。 CPythonの最新バージョン(aspecificPythonインタプリタ)これは、ループ中にスコープ内にのみ存在する匿名オブジェクトであるため、実際には閉じられます(gcはループ後にそれを核にします)ただし、他のインタープリターは行いませんこの保証があります。ディスクリプターが完全にリークする可能性があります。Pythonでファイルを読み取る場合は、ほとんど常にwithイディオムを使用する必要があります。例外はほとんどありませんが、これはその1つではありません。
  2. ポケモンの例外処理は避けられますエラーをマスクするため(つまり、特定の例外をキャッチしない裸のexceptステートメント)
  3. Nit:タプルの解凍に括弧は必要ありません。ただできる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
_
0