4

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

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

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

考慮すべきいくつかの点を次に示します。

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

9 に答える 9

15

IMO、例外のラップ (チェック済みまたはそれ以外) には、コストに見合ういくつかの利点があります。

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

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

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

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

于 2008-08-28T22:32:53.390 に答える
3

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

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

于 2008-08-28T22:02:57.940 に答える
2

エラーの生成、処理、および管理のパターンを参照してください。

Split Domain and Technical Errors パターンから

技術的なエラーが原因でドメイン エラーが発生することはありません (2 つのエラーが発生することはありません)。技術的なエラーによってビジネス処理が失敗する必要がある場合は、SystemError としてラップする必要があります。

ドメイン エラーは常にドメインの問題から始まり、ドメイン コードによって処理される必要があります。

ドメイン エラーは、技術的な境界を「シームレスに」通過する必要があります。これが発生するためには、そのようなエラーをシリアル化して再構成する必要があるかもしれません。プロキシとファサードは、これを行う責任を負う必要があります。

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

エラーとともに返されるコンテキスト情報の量は、これがその後の診断と処理 (別の戦略の検討) にどの程度役立つかによって異なります。リモート マシンからのスタック トレースがドメイン エラーの処理に完全に役立つかどうかを疑問視する必要があります (ただし、エラーのコードの場所とその時点での変数値は役立つ場合があります)。

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

于 2008-09-16T18:09:39.387 に答える
1
  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
}
于 2008-09-02T15:16:34.447 に答える
1

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

ヘルプ?

于 2008-08-28T22:05:52.130 に答える
1

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

また、Microsoft Enterprise Library Exception Handling Blockのドキュメントを参照することをお勧めします。これには、エラー処理パターンの概要が記載されています。

于 2008-08-28T22:19:56.480 に答える
0

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

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

于 2008-09-02T20:56:00.823 に答える
0

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

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

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

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

于 2008-08-28T23:36:12.693 に答える
0

@エリクソン

あなたの考えに食べ物を追加するだけです:

チェックされているものとチェックされていないものもここで議論されています

非チェック例外の使用は、関数の呼び出し元によって引き起こされた例外に対して IMO が使用されているという事実に準拠しています(呼び出し元はその関数のいくつかのレイヤーにある可能性があるため、他のフレームが例外を無視する必要があります)。

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

そうは言っても、基礎となるテクノロジーが変更された場合、それは実際にコードに既に存在する catch() に影響を与え、最新のシナリオには答えません。

于 2008-09-16T15:30:12.453 に答える