質問

すべての失敗を同じ方法で処理したい場合は、未チェック例外でも問題ありません。たとえば、失敗を記録して次のリクエストにスキップする、ユーザーにメッセージを表示して次のイベントを処理するなどです。これが私のユースケースである場合、私がしなければならないことは、システムの高レベルで一般的な例外タイプをキャッチし、すべてを同じ方法で処理することだけです。

しかし、特定の問題から回復したいのですが、チェックされていない例外を使用してそれに対処する最善の方法がわかりません。具体的な例を示します。

Struts2 と Hibernate を使用して構築された Web アプリケーションがあるとします。私の「アクション」に対して例外が発生した場合は、それを記録し、ユーザーにかなりの謝罪を表示します。しかし、私の Web アプリケーションの機能の 1 つは、一意のユーザー名を必要とする新しいユーザー アカウントを作成することです。ユーザーがすでに存在する名前を選択すると、Hibernate は org.hibernate.exception.ConstraintViolationException (チェックされていない例外) 私のシステムの内部にあります。「問題を記録しましたが、今のところは問題ありません」という同じメッセージをユーザーに与えるのではなく、ユーザーに別のユーザー名を選択するよう求めることで、この特定の問題から回復したいと考えています。

考慮すべき点がいくつかあります。

  1. 同時にアカウントを作成する人もたくさんいます。名前が存在するかどうかを確認するための「SELECT」と存在しない場合の「INSERT」の間でユーザーテーブル全体をロックしたくありません。リレーショナル データベースの場合、これを回避するためのトリックがいくつかあるかもしれませんが、私が本当に興味があるのは、基本的な競合状態が原因で例外の事前チェックが機能しない一般的なケースです。ファイル システム上のファイルの検索などにも同じことが当てはまります。
  2. 私の CTO が「Inc.」のテクノロジーコラムを読んでドライブバイ管理をする傾向があることを考えると、最も低いもの以外は何も変更せずに Hibernate を捨てて Kodo などを使用できるように、永続化メカニズムの周りに間接層が必要です。永続化コードの層。実際のところ、私のシステムにはそのような抽象化レイヤーがいくつかあります。チェックされていない例外にもかかわらず、例外がリークしないようにするにはどうすればよいですか?
  3. チェック例外の宣言された弱点の 1 つは、呼び出しメソッドがチェック例外をスローすることを宣言するか、チェック例外をキャッチして処理することによって、スタック上のすべての呼び出しでチェック例外を「処理」する必要があることです。多くの場合、それらを処理するということは、抽象化のレベルに適した型の別のチェック例外でそれらをラップすることを意味します。したがって、たとえば、チェック例外の領域では、UserRegistry のファイル システムベースの実装がキャッチする可能性があります。 IOException, 、データベース実装はキャッチしますが、 SQLException, 、しかし両方とも UserNotFoundException これにより、基礎となる実装が隠蔽されます。実装の詳細を漏らさずに、各層でのラッピングの負担を軽減して、チェックされていない例外を利用するにはどうすればよいでしょうか?
役に立ちましたか?

解決

IMO では、例外のラッピング (チェックありかどうかにかかわらず) には、コストに見合ういくつかの利点があります。

1) 作成したコードの障害モードについて考えるようになります。基本的に、呼び出すコードがスローする可能性のある例外を考慮する必要があり、次に、自分のコードを呼び出すコードに対してスローする例外も考慮する必要があります。

2) 追加のデバッグ情報を例外チェーンに追加する機会が得られます。たとえば、重複したユーザー名に対して例外をスローするメソッドがある場合、その例外を、失敗の状況に関する追加情報 (重複したユーザー名を提供したリクエストの IP など) を含む例外でラップすることができます。下位レベルのコードでは利用できませんでした。Cookie の例外の痕跡は、複雑な問題のデバッグに役立つ場合があります (私にとっては確かに役に立ちました)。

3) これにより、下位レベルのコードから実装を独立させることができます。例外をラップしていて、Hibernate を他の ORM に交換する必要がある場合は、Hibernate 処理コードを変更するだけで済みます。他のすべてのコード層は、根底にある状況が変わったとしても、ラップされた例外を引き続き正常に使用し、同じ方法で例外を解釈します。これは、Hibernate が何らかの方法で変更された場合でも適用されることに注意してください (例:新しいバージョンでは例外を切り替えます)。それは単にテクノロジーを大規模に置き換えるためだけではありません。

4) さまざまな状況を表すために、さまざまなクラスの例外を使用することをお勧めします。たとえば、ユーザーがユーザー名を再利用しようとすると DuplicateUsernameException が発生し、DB 接続が壊れているために重複したユーザー名をチェックできない場合は DatabaseFailureException が発生する可能性があります。これにより、柔軟かつ強力な方法で質問 (「どうすれば回復できますか?」) に答えることができます。DuplicateUsernameException が発生した場合は、別のユーザー名をユーザーに提案することもできます。DatabaseFailureException が発生した場合は、ユーザーに「メンテナンスのため停止しています」ページが表示されるまでバブルアップさせ、通知メールを送信することができます。カスタム例外を設定すると、応答もカスタマイズ可能になります。これは良いことです。

他のヒント

私はアプリケーションの「層」間で例外を再パッケージするのが好きです。そのため、たとえば、DB 固有の例外は、アプリケーションのコンテキストで意味のある別の例外の中に再パッケージされます (もちろん、元の例外はメンバーとして残しますので、スタック トレースを破壊しません)。

そうは言っても、一意でないユーザー名は、スローに値するほどの「例外的な」状況ではないと思います。代わりにブール値の戻り引数を使用します。あなたのアーキテクチャについてあまり知らないので、これ以上具体的で当てはまることを言うのは難しいです。

見る エラーの生成、取り扱い、管理のパターン

分割ドメインと技術的エラーのパターンから

技術的なエラーは、ドメインエラーを生成することは決してありません(Twainが満たすべきではありません)。技術的なエラーがビジネス処理を失敗させる必要がある場合、システムエラーとしてラップする必要があります。

ドメインエラーは常にドメインの問題から開始し、ドメインコードで処理する必要があります。

ドメインエラーは、技術的な境界を「シームレスに」渡す必要があります。このようなエラーは、これが起こるためにはシリアル化され、再構成されなければならないかもしれません。プロキシとファサードは、これを行う責任を負うべきです。

技術的なエラーは、境界などのアプリケーションの特定のポイントで処理する必要があります(分布境界でのログを参照)。

エラーで渡されたコンテキスト情報の量は、その後の診断と取り扱いにこれがどれほど役立つかによって異なります(代替戦略の把握)。リモートマシンからのスタックトレースがドメインエラーの処理に完全に役立つかどうかを疑問視する必要があります(ただし、その時点でのエラーのコード位置と変動値が役立つ場合があります)

したがって、「UniqueUsernameException」などの未チェックのドメイン例外で休止状態にする境界で休止状態例外をラップし、それをそのハンドラーまでバブルアップさせます。チェック例外ではない場合でも、スローされた例外を javadoc するようにしてください。

現在休止状態を使用しているため、最も簡単な方法は、その例外を確認し、それをカスタム例外またはフレームワークで設定したカスタム結果オブジェクトのいずれかにラップすることです。後で休止状態を無視したい場合は、この例外を 1 か所だけでラップするようにしてください。つまり、休止状態からの例外を最初にキャッチする場所です。いずれにしても、切り替えを行うときにおそらく変更する必要があるコードです。が 1 か所にある場合、追加のオーバーヘッドはほとんどゼロになります。

ヘルプ?

私もニックの意見に同意します。あなたが説明した例外は実際には「予期しない例外」ではないため、考えられる例外を考慮してそれに応じてコードを設計する必要があります。

また、Microsoft Enterprise Library のドキュメントを参照することをお勧めします。 例外処理ブロック エラー処理パターンの概要がわかりやすく説明されています。

未チェックの例外をラップしなくてもキャッチできます。たとえば、次は有効な Java です。

try {
    throw new IllegalArgumentException();
} catch (Exception e) {
    System.out.println("boom");
}

したがって、アクション/コントローラー内で、Hibernate 呼び出しが行われるロジックの周囲に try-catch ブロックを置くことができます。例外に応じて、特定のエラー メッセージを表示できます。

しかし、今日は Hibernate になり、明日は SleepLo​​ngerDuringWinter フレームワークになる可能性があると思います。この場合、サードパーティのフレームワークをラップする独自の小さな ORM フレームワークを持っているふりをする必要があります。これにより、フレームワーク固有の例外を、より理解する方法がわかっている、より意味のある例外やチェックされた例外にラップできるようになります。

  1. 質問は実際にはチェック済みとチェック済みのものとは関係ありません。未チェックの議論は、両方の例外タイプに同じことが当てはまります。

  2. ConstraintViolationException がスローされる時点と、適切なエラー メッセージを表示して違反を処理したい時点との間には、スタック上に多数のメソッド呼び出しがあり、これらはすぐに中止されるべきであり、問​​題を気にする必要はありません。そのため、例外から戻り値までコードを再設計するのではなく、例外メカニズムが正しい選択となります。

  3. 実際、コールスタック上のすべての中間メソッドを本当に必要としているため、チェック済み例外の代わりに未チェック例外を使用するのが自然です。 無視する 例外と 対処しないでください .

  4. ユーザーにわかりやすいエラー メッセージ (エラー ページ) を表示するだけで「一意の名前違反」を処理したい場合は、実際には特定の DuplicateUsernameException は必要ありません。これにより、例外クラスの数が低く抑えられます。代わりに、多くの同様のシナリオで再利用できる MessageException を作成できます。

    できるだけ早く ConstraintViolationException をキャッチし、それを適切なメッセージを含む MessageException に変換します。違反しているのが他の制約ではなく、実際に「一意のユーザー名制約」であることが確認できたら、すぐに変換することが重要です。

    最上位ハンドラーに近い場所で、MessageException を別の方法で処理するだけです。「問題を記録しましたが、今のところあなたは困っています」の代わりに、スタック トレースなしで、MessageException に含まれるメッセージを表示するだけです。

    MessageException は、問題の詳細な説明、利用可能な次のアクション (キャンセル、別のページに移動)、アイコン (エラー、警告) など、追加のコンストラクター パラメーターを取ることができます。

コードは次のようになります

// insert the user
try {
   hibernateSession.save(user);
} catch (ConstraintViolationException e) {
   throw new MessageException("Username " + user.getName() + " already exists. Please choose a different name.");
}

まったく別の場所に最上位の例外ハンドラーがあります

try {
   ... render the page
} catch (MessageException e) {
   ... render a nice page with the message
} catch (Exception e) {
   ... render "we logged your problem but for now you're hosed" message
}

@1月 ここでの中心的な問題は、チェック済みか未チェックかです。間にあるフレームでは例外を無視すべきであるというあなたの仮定 (#3) に疑問を持ちます。それを行うと、高レベルのコードに実装固有の依存関係が発生することになります。Hibernate を置き換える場合は、アプリケーション全体の catch ブロックを変更する必要があります。しかし同時に、下位レベルで例外をキャッチした場合、チェックされていない例外を使用してもあまりメリットが得られません。

また、ここでのシナリオは、特定の論理エラーを捕捉し、ユーザーに別の ID を再度求めることでアプリケーションのフローを変更したいというものです。表示されるメッセージを変更するだけでは十分ではなく、例外タイプに基づいてさまざまなメッセージにマップする機能がすでにサーブレットに組み込まれています。

@エリクソン

あなたの考えに食べ物を加えるために:

チェック済みとチェックなしも同様です ここで議論されました

チェックされていない例外の使用は、IMO の例外に使用されるという事実に準拠しています。 関数の呼び出し元が原因で発生 (そして、呼び出し元はその関数の上のいくつかの層にある可能性があるため、他のフレームが例外を無視する必要があります)

特定の問題に関しては、コールスタックを表示せずに(@Jan Soltisで述べたように)独自の例外で@Kanookが述べたように、未チェックの例外を高レベルでキャッチしてカプセル化する必要があります。

そうは言っても、基盤となるテクノロジーが変更されると、コード内にすでに存在する catch() に実際に影響が及ぶことになり、最新のシナリオには対応しません。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top