最初の処理と例外処理を確認しますか?


88

私は「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つのうち、どちらが一般的にベタープラクティスと見なされますか?


12
本のそのセクションは賢くありません。ループ状態にあり、非常にコストのかかる例外を繰り返しスローしている場合。これを行うタイミングの良い点の概要を説明しました。
ジェイソンセブリング

9
「ファイル存在チェック」トラップに陥らないでください。ファイルが存在します!=ファイルにアクセスできます。または、ファイルを開く呼び出しなどに到達
ビリーオニール

11
Pythonの例外は、他の言語とは異なる考え方です。たとえば、コレクションを反復処理する方法は、例外がスローされるまでコレクションに対して.next()を呼び出すことです。
-WuHoUnited

4
@ emeraldcode.comそれはPythonについて完全に真実ではありません。詳細はわかりませんが、言語はそのパラダイムを中心に構築されているため、例外のスローは他の言語ほど高くはありません。
イズカタ

ただし、この例では、ガードステートメントを使用します。 if -1 == eachLine.find(":"): continue、その後、ループの残りの部分もインデントされません。
イズカタ

回答:


68

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

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

引数の中心には、次の引用があります。

その理由は、1960年代から有害であると考えられている「goto's」よりも例外が優れていると考えているためです。実際、それらはgotoのものよりも著しく悪い:

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

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

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

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

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


5
もう1つ重要な点があります。例外ログがアプリケーション全体で有効になっている場合は、例外を例外的な条件にのみ使用し、通常の条件には使用しない方が良いでしょう。そうしないと、ログが乱雑になり、実際のエラーの原因がわかりにくくなります。
-rwong

2
素敵な答え。ただし、例外はほとんどのプラットフォームで高いパフォーマンスを発揮します。ただし、他の回答についての私のコメントで指摘したように、何かを成文化する方法について包括的なルールを決定する場合、パフォーマンスは考慮されません。
マッテンス

1
Scott Hanselmanからの引用は、「乱用」よりも例外に対する.Netの態度をよく説明しています。パフォーマンスは頻繁に言及されますが、実際の引数は、例外を使用する必要がある理由の逆です-通常の条件が例外になった場合、コードの理解と処理が難しくなります。ジョエルに関しては、ポイント1は実際にはポジティブ(見えないということは、コードが何をしないかではなく、コードが何をするかを示すことを意味します)、ポイント2は無関係です(すでに矛盾した状態にあるか、例外がないはずです) 。それでも、「求められていることを実行できない」ための+1。
-jmoreno

5
この答えは.Netには問題ありませんが、あまりPythonicではないため、これがPythonの質問であることを考えると、なぜIvcの答えがこれ以上投票されないのかわかりません。
マークブース

2
@IanGoldby:いいえ。例外処理は、実際には例外回復としてより適切に説明されます。例外から回復できない場合、おそらく例外処理コードはないはずです。メソッドAがメソッドBを呼び出し、メソッドBがCを呼び出し、Cがスローした場合、AまたはBの両方ではなく、どちらかが回復する可能性があります。Yが他の誰かにタスクの完了を要求する場合、「Xを実行できない場合、Yを実行します」という決定は避けてください。タスクを完了できない場合、残っているのはクリーンアップとロギングのみです。.netでのクリーンアップは自動的に行われ、ロギングは集中化される必要があります。
jmoreno

78

特にPythonでは、例外をキャッチすることをお勧めします。これは、呼び出される傾向にある許可よりも許しを求めることが容易、(EAFP)あなたリープ(LBYL)の前に見てと比較します。LBYLが微妙なバグを与える場合があります。

ただし、むき出しの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())

8
.NETプログラマーとして、私はこれにうんざりしています。しかし、再び、あなたの人々は奇妙なことをすべてします。:)
フィル14

これは、どの例外がどの状況でスローされるかについてAPIが一貫していない場合、または同じ例外タイプで複数の異なる種類の障害がスローされる場合、非常にイライラします(意図しない)。
ジャック

そのため、予期しないエラーと期待される種類の戻り値に対して同じメカニズムを使用することになります。これは、0を数字、偽ブール、および128 + SIGSEGVの終了コードでプロセスを終了する無効なポインターを使用するのと同じくらい素晴らしいです。胞子のように!またはつま先のある靴
...-ヨーマン

2
@yeomanが例外をスローするタイミングは別の質問です。これは、「次は例外をスローする可能性が高い」という条件を設定するのではなく、try/ を使用exceptすることに関するものです。ここでは、分割が成功した場合に文字列を一度だけ歩くので、そのアプローチが(おそらく)より効率的であることを傷つけません。splitここで例外をスローすべきかどうかについて、私は間違いなくそうすべきだと言います-一般的なルールの1つは、あなたの名前が言うことを行うことができず、欠落している区切り文字で分割できないときにスローすることです。
lvc

特に特定の例外のみがキャッチされるので、私はそれが悪い、遅い、またはひどいとは思いません。Ans私は実際にPythonが好きです。Cが数字のゼロ、Spork、Randall Munroeのこれまでで一番好きな靴をつま先で使っていると言ったように、それが時々味をまったく見せないのは面白いです:)それを行う方法、私はそれを行きます:)並行性、コルーチン、またはそれらのいずれかがさらに先に追加されているため、事前に条件を確認することはもちろん良い考えではありません
...-ヨーマン

27

実用的なアプローチ

防御的である必要がありますが、ある程度まで。例外処理は、ある程度まで記述する必要があります。これは私が住んでいる場所なので、例としてWebプログラミングを使用します。

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

これは、大規模なチームシナリオでの作業経験によるものです。

アナロジー

ISS内で常に宇宙服を着ていると想像してください。トイレに行くのも、食べるのも難しいでしょう。移動するには、宇宙モジュール内で非常にかさばります。それは吸うだろう。コード内に一連のtry-catchesを記述することは、そのようなものです。ISSを確保し、中の宇宙飛行士は大丈夫だから、起こりうるすべてのシナリオに合わせて宇宙服を着ることは現実的ではありません。


4
ポイント3の問題は、プログラムとそれに取り組んでいるプログラマーが完璧であることを前提としていることです。それらはそうではないので、これらを念頭に置いて防御的に最高のプログラムです。主要な時点で適切な金額を設定すると、「入力がすべて完全にチェックされた場合」という考え方よりもソフトウェアの信頼性がはるかに高くなります。
マッテンツ

それがテストの目的です。
ジェイソンセブリング

3
テストはすべてではありません。100%のコードと「環境」カバレッジを持つテストスイートはまだ見ていません。
マルジャンヴェネ

1
@emeraldcode:私と一緒に仕事をしたいのですが、ソフトウェアを実行するすべてのエッジケースのすべての順列を例外なくテストするチームの誰かがいるのが大好きです。コードが完全にテストされていることを絶対に確実に知っている必要があります。
マッテンツ

1
同意する。防御的なプログラミングと例外処理の両方がうまく機能するシナリオと悪いシナリオがあり、私たちプログラマーはそれらを認識することを学び、最適な手法を選択する必要があります。ポイント3が好きなのは、コードの特定のレベルで、いくつかのコンテキスト条件が満たされるべきであると仮定する必要があると思うからです。これらの条件は、コードの外側の層で防御的にコーディングすることで満たされ、これらの仮定が内側の層で破られる場合、例外処理が適していると思います。
-yaobin

15

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

このステートメントは、出力が正しいかどうか気にしない非常に特定の状況でのみ当てはまると思います。

例外を提起することは、健全で安全な習慣であることは間違いありません。(開発者として)対処できない、または対処したくないプログラムの現在の状態に何かがあると感じるときはいつでもそうすべきです。

ただし、例は例外をキャッチすることです。例外をキャッチした場合、見落としている可能性のあるシナリオから身を守ることはできません。あなたはまったく逆のことをしています:このタイプの例外を引き起こす可能性のあるシナリオを見逃していないと仮定しているため、それをキャッチしても大丈夫だと確信しています(したがって、プログラムが終了するのを防ぎます)キャッチされない例外と同様に)。

例外アプローチを使用して、例外が表示された場合はValueError、行をスキップします。従来の非例外アプローチを使用して、から返される値の数をカウントし、split2未満の場合は1行スキップします。従来のエラーチェックでは他の「エラー」状況を忘れてしまった可能性があるため、例外アプローチを使用してより安全に感じる必要がありexcept ValueErrorますか?

これは、プログラムの性質に依存します。

たとえば、Webブラウザーやビデオプレーヤーを作成している場合、入力に関する問題が原因でキャッチされない例外でクラッシュすることはありません。終了するよりも、リモートで分別のあるものを出力する方が(厳密に言えば、正しくない場合でも)はるかに優れています。

正確性が重要なアプリケーション(ビジネスソフトウェアやエンジニアリングソフトウェアなど)を作成している場合、これはひどいアプローチになります。を発生させるいくつかのシナリオを忘れた場合ValueError、あなたができる最悪のことは、この未知のシナリオを静かに無視し、単に行をスキップすることです。これは、ソフトウェアに存在する非常に微妙で費用のかかるバグです。

ValueErrorこのコードで確認できる唯一の方法は、split返される値が(2つではなく)1つだけであると考えるかもしれません。しかし、ある条件の下printで発生する式を後で使用してステートメントが開始したらどうなるValueErrorでしょうか?これにより:、が見つからないためではなく、print失敗するために一部の行がスキップされます。これは私が以前言及した微妙なバグの例です-あなたは何も気付かないでしょう、ただいくつかの行を失います。

私の推奨事項は、不正な出力の生成が終了するよりも悪いコードで例外をキャッチしないようにすることです(ただし、発生させないでください!)。そのようなコードで例外をキャッチするのは、本当に些細な式があるときだけです。そのため、考えられる例外タイプのそれぞれの原因を簡単に推論できます。

例外を使用した場合のパフォーマンスへの影響については、例外が頻繁に発生しない限り、それは簡単です(Pythonで)。

定期的に発生する条件を処理するために例外を使用する場合、場合によっては膨大なパフォーマンスコストを支払う可能性があります。たとえば、何らかのコマンドをリモートで実行するとします。コマンドテキストが少なくとも最小限の検証(構文など)に合格することを確認できます。または、例外が発生するのを待つこともできます(これは、リモートサーバーがコマンドを解析して問題を見つけた後にのみ発生します)。明らかに、前者は桁違いに高速です。別の簡単な例:除算を実行してZeroDivisionError例外をキャッチするよりも、数値が0から10倍速いかどうかを確認できます。

これらの考慮事項は、不正な形式のコマンド文字列をリモートサーバーに頻繁に送信するか、除算に使用するゼロ値の引数を受け取る場合にのみ重要です。

注:except ValueErrorちょうどの代わりに使用すると仮定しますexcept。他の人が指摘したように、そして本自体が数ページで述べているように、決してbareを使用すべきではありませんexcept

別の注意:適切な非例外アプローチはsplit、を検索するのではなく、によって返される値の数をカウントすることです:。後者はsplit、実行される作業を繰り返し、実行時間をほぼ2倍にする可能性があるため、非常に遅いです。


6

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


2

うまく機能するものを使用してください。

  • コードの読みやすさと効率の観点から選択したプログラミング言語
  • チームと合意されたコード規約のセット

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


0

TBH、try/exceptメカニックまたはifステートメントチェックを使用するかどうかは関係ありません。通常、ほとんどのPythonベースラインにはEAFPとLBYLの両方が表示されますが、EAFPが少し一般的です。EAFPの方はるかに読みやすい/ イディオマティックな場合もありますが、この特定のケースではどちらの方法でも問題ないと思います。

しかしながら...

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

  1. ファイル記述子がリークしています。CPythonの最新バージョン(特定の Pythonインタープリター)は、ループ中にのみスコープ内にある匿名オブジェクトであるため、実際に閉じます(gcはループ後に破棄します)。ただし、他の通訳者にこの保証はありません。記述子が完全にリークする可能性があります。ほとんどのwith場合、Pythonでファイルを読み取るときにイディオムを使用する必要があります。例外はほとんどありません。これはそれらの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

-4

基本的に、例外処理はOOP言語により適しているはずです。

2番目のポイントはパフォーマンスですeachLine.find。すべての行で実行する必要がないためです。


7
-1:パフォーマンスは、ブランケットルールの非常に悪い理由です。
マッテンス

3
いいえ、例外はOOPとはまったく関係ありません。
パブ

-6

防御的なプログラミングはパフォーマンスを低下させると思います。また、処理しようとしている例外のみをキャッチし、処理方法がわからない例外をランタイムが処理できるようにする必要があります。


7
しかし、readablity、maintainablity bla bla blaよりもパフォーマンスを心配することについてはanotehr -1です。パフォーマンスは理由ではありません。
マッテンス

説明せずに-1を配布している理由を知っていますか?防御的プログラミングとは、より多くのコード行を意味し、パフォーマンスの低下を意味します。スコアを撃ち落とす前に説明したい人はいますか?
マノジ

3
@Manoj:プロファイラーで測定して、コードのブロックが許容できないほど遅いことがわかった場合を除き、パフォーマンスよりもずっと前に可読性と保守性を高めるコード。
デニース

さらに、@ Manojが言ったことは、より少ないコードが普遍的にデバッグと保守の際の作業が少ないことを意味します。完璧なコードに満たない開発者の時間への打撃は非常に高くなります。私は(私のように)あなたは完璧なコードを書かないと思います。間違っていても許してください。
マッテンツ

2
リンクをお寄せいただきありがとうございます-私はそれに同意しなければならないという興味深い読みをしました... .... "証人の立場ではあまりうまく行かないでしょう。私は、それがあらゆる状況が異なる適切な応答を持っているものの一つだと思います。
マッテンツ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.