例外-「何が起こった」対「何をすべきか」


19

例外を使用して、コードのコンシューマーが予期しない動作を便利な方法で処理できるようにします。通常、例外は「発生した」シナリオを中心に構築されます- FileNotFound(指定したファイルが見つかりませんでした)またはZeroDivisionError1/0操作を実行できませんでした)。

消費者の予想される動作を指定する可能性がある場合はどうなりますか?

たとえば、fetchHTTPリクエストを実行し、取得したデータを返すリソースがあるとします。そして、代わりのようなエラーServiceTemporaryUnavailableまたはRateLimitExceeded私達はちょうど引き上げるRetryableErrorそれだけで、要求を再試行する必要があり、特定の失敗を気にしないことを示唆している消費者を。そのため、基本的には呼び出し側にアクションを提案しています-「何をすべきか」。

消費者のすべてのユースケースを知っているわけではないため、これを頻繁に行うことはありません。しかし、それが発信者にとって最善のアクションコースを知っている特定のコンポーネントであると想像してください。


3
HTTPはすでにこれを行っていませんか?503は、要求者が再試行する必要がありますので、404はそれを再試行しても意味がありませんので、あなたが再試行する必要がありますので、基本的な不在は、301件の手段は、「恒久的に移動」、ですが、別のアドレス、などと、返信する一時的な障害である
キリアン15

7
多くの場合、「何をすべきか」が本当にわかっていれば、コンピューターに自動的にそれを行わせることができ、ユーザーは何かが間違っていることを知る必要さえありません。私のブラウザは301を受け取るたびに、私に尋ねることなく単に新しいアドレスに行くと思います。
Ixrec

@Ixrec-同じ考えを持っていました。ただし、消費者は別のリクエストを待ってアイテムを無視したり、完全に失敗したりすることはできません。
ローマンボドナルドク

1
@RomanBodnarchuk:私は同意しません。中国語を話すために中国語を知る必要はない、と言っているようなものです。HTTPはプロトコルであり、クライアントとサーバーの両方がそれを認識して従うことが期待されています。それがプロトコルの仕組みです。片方だけがそれを知っていてそれに従うなら、あなたはコミュニケーションできません。
クリスプラット

1
正直なところ、例外をcatchブロックに置き換えようとしているようです。これが例外に移行した理由です-これ以上if( ! function() ) handle_something();ではありませんが、実際に呼び出しコンテキストを知っている場所でエラーを処理できます。呼び出し元が別のマイクロサービスである場合。catchブロックにキャッチを処理させます。
Sebb

回答:


47

しかし、それが特定のコンポーネントであり、発信者にとって最善のアクションコースを知っていると想像してください。

これはほとんどの場合、少なくとも1人の呼び出し元で失敗しますが、この動作は非常に苛立たしいものです。よく知っていると思い込まないでください。ユーザーがそれに対して何をすべきかを想定するのではなく、何が起こっているのかをユーザーに伝えてください。多くの場合、正気な行動方針がどのようなものであるかはすでに明らかです(そうでない場合は、ユーザーマニュアルで提案してください)。

たとえば、あなたの質問で与えられた例外でさえ、あなたの壊れた仮定を示しています:ServiceTemporaryUnavailable「後でもう一度試す」にRateLimitExceeded相当し、「すごい落ち着いて、タイマーのパラメーターを調整して、数分後に再試行する」に相当します。ただし、ユーザーはServiceTemporaryUnavailable(サーバーの問題を示す)何らかのアラームをオンにしたい場合があります(サーバーの問題を示しRateLimitExceededません)。

彼らに選択肢を与えてください。


4
同意する。サーバーは情報を適切に伝達するだけです。一方、ドキュメントでは、そのような場合の適切な行動方針を明確に概説する必要があります。
ニール

1
これの小さな欠陥の1つは、一部のハッカーにとっては、特定の例外により、コードが何をしているのかについてかなり多くのことを伝え、それを利用してそれを悪用する方法を見つけることができるということです。
ファラップ

3
@Pharapハッカーがエラーメッセージではなく例外自体にアクセスできる場合、あなたはすでに失われています。
corsiKa

2
私はこの答えが好きですが、例外の要件と考えるものが欠けています...回復する方法を知っていれば、それは例外ではありません!例外は、無効な入力、無効な状態、無効なセキュリティなど、何もできないときだけです。プログラムで修正することはできません。
corsiKa

1
同意した。再試行が可能かどうかを示すことに固執する場合、関連する具体的な例外を常に継承させることができますRetryableError
サピ

18

警告!確かに別の言語に関する質問に答えようとして、例外処理がどのように行われるべきかについて、おそらく異なる考えでここに来るC ++プログラマー!

この考えを考えると:

たとえば、HTTPリクエストを実行し、取得したデータを返すフェッチリソースがあるとします。また、ServiceTemporaryUnavailableやRateLimitExceededのようなエラーの代わりに、RetryableErrorを発生させ、要求を再試行するだけで特定の失敗を気にしないことをコンシューマに提案します。

...私がお勧めすることの1つは、エラーを報告することと、コードの一般性を低下させたり、例外のために多くの「翻訳ポイント」を必要とする方法でそれに対応するアクションコースを混在させることです。

たとえば、ファイルの読み込みを伴うトランザクションをモデル化すると、いくつかの理由で失敗する可能性があります。おそらく、ファイルの読み込みには、ユーザーのマシンに存在しないプラグインの読み込みが含まれます。おそらくファイルは単に破損しており、解析中にエラーが発生しました。

何が起こっても、ユーザーに何が起こったのかを報告し、ユーザーがそれに対して何をしたいのかを促します(「再試行、別のファイルをロード、キャンセル」)。

投げる人対キャッチャー

この場合に発生したエラーの種類に関係なく、その一連のアクションが適用されます。解析エラーの一般的な考え方には埋め込まれていません。プラグインの読み込みに失敗するという一般的な考え方には埋め込まれていません。ファイルをロードする正確なコンテキスト(ファイルのロードと失敗の組み合わせ)でこのようなエラーが発生するという考えに組み込まれています。だから通常、私は大雑把に言っcatcher'sて、スローされた例外(例:ユーザーにオプションを要求する)に応じてアクションのコースを決定する責任ではなく、を参照しますthrower's

別の言い方をthrowすれば、特にスローする機能が一般的に適用可能な場合、例外が通常この種のコンテキスト情報を欠いているサイトです。彼らがこの情報を持っている完全に一般化された状況でさえ、あなたはthrowサイトにそれを埋め込むことによって回復行動に関して自分自身を追い詰めることになります。catch通常、アクションのコースを決定するために利用可能な情報量が最も多いサイトであり、特定のトランザクションに対してアクションのコースが変更される場合に変更する中心的な場所を提供します。

例外をスローしようとすると、何が問題なのかを報告せず、何をすべきかを判断しようとすると、コードの汎用性と柔軟性が低下する可能性があります。解析エラーが常にこの種のプロンプトにつながるとは限りません。例外がスローされるコンテキスト(スローされたトランザクション)によって異なります。

盲目の投げ手

ちょうど一般的に、例外処理の設計の多くは、多くの場合、盲目の投げ手のアイデアを中心に展開します。例外がどのようにキャッチされるのか、どこで行われるのかはわかりません。同じことが、手動のエラー伝播を使用した古い形式のエラー回復にも当てはまります。エラーが発生したサイトにはユーザーのアクションコースは含まれず、発生したエラーの種類を報告するための最小限の情報のみが埋め込まれます。

反転した責任とキャッチャーの一般化

これについてもっと慎重に考えると、これが誘惑になるようなコードベースを想像しようとしていました。私の想像(おそらく間違っている)は、あなたのチームがまだここで「消費者」の役割を果たしており、呼び出しコードの大部分も実装しているということです。おそらくtry、同じ一連のエラーに遭遇する可能性のある多くの異なるトランザクション(多くのブロック)があり、すべてが設計の観点から、回復アクションの均一なコースにつながるはずです。

Lightness Races in Orbit's細かい答え(賢明なライブラリ指向の考え方から来ていると思います)からの賢明なアドバイスを考慮すると、トランザクション回復サイトの近くでのみ、「何をすべきか」例外をスローするように誘惑されるかもしれません。

ここから、実際に「何をすべきか」の懸念を集中化するが、それでもキャッチのコンテキスト内にある、中間の共通トランザクション処理サイトを見つけることが可能かもしれません。

ここに画像の説明を入力してください

これは、これらのすべての外部トランザクションが使用する何らかの一般的な関数を設計できる場合にのみ適用されます(例:呼び出す別の関数を入力する関数、または洗練されたキャッチを行うこの中間トランザクションサイトをモデル化するオーバーライド可能な動作を持つ抽象トランザクションベースクラス)。

ただし、さまざまなエラーに対応して、ユーザーのアクションコースを一元化する責任があります。これは、スローではなくキャッチのコンテキスト内です。単純な例(Python風の擬似コード。私は経験豊富なPython開発者ではありませんので、これについてもっと慣用的な方法があるかもしれません):

def general_catcher(task):
    try:
       task()
    except SomeError1:
       # do some uniformly-designed recovery stuff here
    except SomeError2:
       # do some other uniformly-designed recovery stuff here
    ...

[願わくはgeneral_catcher] よりも良い名前を付けてください。この例では、実行するタスクを含む関数を渡すことができますが、関心のあるすべての種類の例外に対して一般化/統一されたキャッチ動作の恩恵を受け、引き続き「何をする」部分を拡張または変更しますあなたはこの中心的な場所から好きであり、それでもcatchこれが通常奨励されているコンテキスト内にあります。何よりも、私たちはスローサイトが「何をすべきか」(「ブラインドスロー」の概念を維持する)を自分自身に関係させないようにすることができます。

ここでこれらの提案のどれも役に立たず、とにかく「するべきこと」の例外をスローする強い誘惑がある場合、主にこれは非常に反イディオマティックであり、一般化された考え方を落胆させる可能性があることに注意してください。


2
+1。私は以前に「ブラインド投げ」のアイデアを聞いたことはありませんでしたが、例外処理の考え方に適合しています。エラーが発生したことを示し、処理方法を考えないでください。完全なスタックを担当する場合、責任を明確に分離することは困難ですが(重要です!)、呼び出し先は問題を通知し、呼び出し元は問題を処理する責任があります。呼び出し先は、何をするように要求されたかを知っているだけで、理由はわかりません。エラーの処理は、「理由」のコンテキストで行う必要があります。したがって、呼び出し元で行う必要があります。
シェードジョブポストマス

1
(また:私はあなたの返事がC ++固有のだと思いますが、一般的に例外処理には適用しません)
Sjoerd仕事Postmus

1
ありがとうございます!「Blind Thrower」は、私がここで思いついた間抜けなアナロジーです。複雑な技術的概念を消化するのはあまり賢くも速いので、よく自分の理解を説明し改善しようとする小さな画像やアナロジーを見つけたいです。物事の。いつか私は、たくさんの漫画の絵で満たされた小さなプログラミング本を書くために私の方法を試すことができるかもしれません。:-D

1
へえ、それは素敵な小さな画像です。目隠しをして野球の形の例外を投げる小さな漫画のキャラクターは、誰が捕まえるのか(または捕まえられるかどうかさえ)わからないが、盲目の投げ手としての義務を果たしている。
ブラックライトシャイニング

1
@DrunkCoder:投稿を破壊しないでください。私たちはすでにインターネット上に十分なものがあります。削除する正当な理由がある場合は、モデレーターの注意を引くために投稿にフラグを立てて、主張します。
ロバートハーヴェイ

2

ほとんどの場合、それらの状況を処理する方法を伝える引数を関数に渡す方が良いと思います。

たとえば、関数を考えてみましょう:

Response fetchUrl(URL url, RetryPolicy retryPolicy);

RetryPolicy.noRetries()またはRetryPolicy.retries(3)などを渡すことができます。再試行可能な失敗の場合、ポリシーを参照して再試行するかどうかを決定します。


ただし、呼び出しサイトに例外をスローすることとはあまり関係ありません。あなたは罰金が、問題のない本当に一部..です何かビット異なる、話している
モニカと明度レース

@LightnessRacesinOrbit、反対に。呼び出しサイトに例外をスローするというアイデアに代わるものとして提示しています。OPの例では、fetchUrlはRetryableExceptionをスローしますが、代わりにfetchUrlに再試行のタイミングを指示する必要があります。
ウィンストンイーバート

2
@WinstonEwert:LightnessRacesinOrbitには同意しますが、あなたの主張も見ていますが、同じコントロールを表す別の方法と考えています。しかし、おそらく別のものを処理する必要があるかもしれないnew RetryPolicy().onRateLimitExceeded(STOP).onServiceTemporaryUnavailable(RETRY, 3)ので、おそらく何かを通過したいと思うと考えてください。それを書いた後、私の考えは次のとおりです。より柔軟な制御が可能になるため、例外をスローする方がよい。RateLimitExceededServiceTemporaryUnavailable
シェードジョブポストマス

@SjoerdJobPostmus、私はそれが依存すると思います。あなたのライブラリではレート制限と再試行のロジックが行われている可能性がありますが、その場合は私のアプローチが理にかなっていると思います。それを単にあなたの呼び出し元に任せたほうが理にかなっているなら、ぜひとも投げてください。
ウィンストンイーバート
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.