例外は例外的な場合にのみ使用されるべきだと言われました。私のケースが例外的であるかどうかを知るにはどうすればよいですか?


99

ここでの私の具体的なケースは、ユーザーがアプリケーションに文字列を渡すことができ、アプリケーションがそれを解析し、構造化オブジェクトに割り当てることです。ユーザーが無効なものを入力する場合があります。例えば、彼らの入力は人を説明するかもしれませんが、彼らは彼らの年齢が「リンゴ」であると言うかもしれません。その場合の正しい動作は、トランザクションをロールバックし、エラーが発生したことをユーザーに伝えることであり、ユーザーは再試行する必要があります。最初のエラーだけでなく、入力で見つかったすべてのエラーについてレポートする必要があるかもしれません。

この場合、例外をスローする必要があると主張しました。彼は、「例外は例外であるべきだ。ユーザーが無効なデータを入力する可能性があるため、これは例外的なケースではない」と言って同意しませんでした。正しいようです。

しかし、そもそも例外が発明されたのはこのためだと私は理解しています。それはあなたがために使用いたエラーが発生したかどうかを確認するために、結果を検査します。チェックに失敗すると、気付かないうちに悪いことが起こる可能性があります。

例外なく、スタックのすべてのレベルが呼び出すメソッドの結果を確認する必要があり、プログラマーがこれらのレベルのいずれかをチェックインするのを忘れると、コードが誤って続行して無効なデータを保存する可能性があります(たとえば)。そのようになりやすいエラーのようです。

とにかく、ここで言ったことは何でも修正してください。私の主な質問は、例外が例外的であるべきだと誰かが言った場合、私の事例が例外的であるかどうかをどのように知ることができますか?


3
可能性のある複製?例外をスローするタイミング。そこは閉鎖されていましたが、ここに収まると思います。それはまだ少しの哲学であり、一部の人々とコミュニティは例外を一種のフロー制御と見なす傾向があります。
トーステンミュラー

8
ユーザーが口のきけないときは、無効な入力を提供します。賢いユーザーは、無効な入力を提供することでプレイします。したがって、無効なユーザー入力は例外ではありません。
ムーヴィシエル

7
また、Javaと.NETの非常に特殊なメカニズムである例外を、はるかに一般的な用語であるエラーと混同しないでください。エラー処理には、例外をスローするだけではありません。この議論は例外エラーのニュアンスに触れます。
エリックキング

4
!= "例外" "めったに起こらない"
ConditionRacer

3
Eric LippertのVexingの例外は適切なアドバイスだと思います。
ブライアン

回答:


87

例外は、コードの混乱を少なくしてエラー処理を容易にするために考案されました。コードの乱雑さを減らしてエラー処理を容易にする場合に使用してください。この「例外的な状況のみの例外」ビジネスは、例外処理が許容できないパフォーマンスヒットと見なされていた時代に由来します。大半のコードではもはやそうではありませんが、人々はその背後にある理由を覚えていないままルールを吐き出します。

特にJavaは、これまで考えられた中で最も例外を好む言語かもしれませんが、コードを単純化するときに例外を使用することを気にしないでください。 実際、Javaの独自のIntegerクラスには、を潜在的にスローすることなく、文字列が有効な整数かどうかをチェックする手段がありませんNumberFormatException

また、UIの検証だけに依存することはできませんが、短い数値を入力するためにスピナーを使用するなど、UIが適切に設計されている場合は留意してください。例外的な状態。


10
いいスワイプ、そこ。実際、私が設計した実際のアプリでは、パフォーマンスヒット違いを生み、特定の解析操作で例外をスローしないように変更する必要ありました。
ロバートハーベイ

17
パフォーマンスヒットが正当な理由であるケースはまだないということではありませんが、それらのケースはルールではなく例外(しゃれを意図したもの)です。
カールビーレフェルト

11
@RobertHarvey Javaのコツは、ではなく、事前に作成された例外オブジェクトをスローすることthrow new ...です。または、fillInStackTrace()が上書きされるカスタム例外をスローします。その場合、ヒットについては言うまでもなく、パフォーマンスの低下に気付かないはずです。
インゴ

3
+1:まさに答えようとしていたこと。コードを簡素化するときに使用します。例外を使用すると、コールスタックのすべてのレベルで戻り値を確認する必要がない、より明確なコードを提供できます。(ただし、他のすべてと同様に、間違った方法で使用すると、コードが恐ろしい混乱になる可能性があります。)
レオ

4
@Brendanいくつかの例外が発生し、エラー処理コードが呼び出しスタックの4レベル下にあるとします。エラーコードを使用する場合、ハンドラーの上の4つの関数はすべて、戻り値としてエラーコードの型を持つ必要があり、if (foo() == ERROR) { return ERROR; } else { // continue }すべてのレベルでチェーンを実行する必要があります。チェックされていない例外をスローすると、うるさくて冗長な「エラーがエラーを返す場合」はありません。また、関数を引数として渡す場合、エラーが発生しなくても、エラーコードを使用すると、関数のシグネチャが互換性のない型に変更される場合があります。
ドーバル14年

72

例外はいつスローされるべきですか?コードに関しては、次の説明が非常に役立つと思います。

例外は、メンバーが名前で示されるとおりに実行することになっているタスクを完了できない場合です。(Jeffry Richter、C#経由のCLR)

なぜ役立つのですか?何かを例外として処理すべきかどうかは、コンテキストに依存することを示唆しています。メソッド呼び出しのレベルでは、コンテキストは、(a)メソッドの名前、(b)メソッドの署名、および(b)メソッドを使用する、または使用する予定のクライアントコードによって与えられます。

質問に答えるには、ユーザー入力が処理されるコードを確認する必要があります。次のようになります。

public void Save(PersonData personData) {  }

メソッド名は、何らかの検証が行われていることを示唆していますか?いいえ。この場合、無効なPersonDataは例外をスローする必要があります。

クラスに次のような別のメソッドがあると仮定します。

public ValidationResult Validate(PersonData personData) {  }

メソッド名は、何らかの検証が行われていることを示唆していますか?はい。この場合、無効なPersonDataが例外をスローすることはありません。

物事をまとめるために、両方のメソッドはクライアントコードが次のように見えることを提案します:

ValidationResult validationResult = personRegister.Validate(personData);
if (validationResult.IsValid())
{
    personRegister.Save(personData)
}
else
{
    // Throw an exception? To answer this look at the context!
    // That is: (a) Method name, (b) signature and
    // (c) where this method is (expected) to be used.
}

メソッドが例外をスローする必要があるかどうかが不明な場合は、メソッド名またはシグネチャが適切に選択されていないことが原因である可能性があります。クラスのデザインが明確でないかもしれません。例外をスローするかどうかの質問に対する明確な回答を得るために、コード設計を変更する必要がある場合があります。


ちょうど昨日struct「ValidationResult」と呼ばれるものを作成し、記述したとおりにコードを構造化しました。
ポール

4
あなたの質問に答えることは助けになりませんが、私はあなたが暗黙的または意図的にコマンド-クエリ分離の原則(en.wikipedia.org/wiki/Command-query_separation)に従ったことを指摘したいと思います。;-)
テオレンドルフ

良いアイデア!1つの欠点:あなたの例では、検証は実際に2回実行されます:一度Validate(無効な場合はFalseを返す)と一度Save(無効な場合は明確に文書化された特定の例外をスロー)もちろん、検証結果はオブジェクト内にキャッシュできますが、検証結果は変更時に無効にする必要があるため、さらに複雑になります。
ハインジ

@Heinzi同意します。メソッドValidate()内で呼び出されるようにリファクタリングできます。またSave()、からの特定の詳細をValidationResult使用して、例外に対する適切なメッセージを作成できます。
フィル

3
これは、私が考える受け入れられた答えよりも優れています。呼び出しが本来行うべきことを実行できない場合にスローします。
アンディ

31

例外は例外的である必要があります:ユーザーが無効なデータを入力する可能性があるため、これは例外的なケースではありません

その議論について:

  • ファイルが存在しない可能性があるため、例外的なケースではありません。
  • サーバーへの接続が失われる可能性があるため、例外的なケースではありません
  • 構成ファイルが文字化けしている可能性があるため、例外的なケースではありません
  • リクエストが時々失敗することが予想されるため、例外的なケースではありません

あなたがキャッチした例外は、あなたがそれをキャッチすることに決めたので、あなたは期待しなければなりません。したがって、このロジックにより、実際にキャッチする予定の例外をスローすることはできません。

したがって、「例外は例外であるべきだ」というのは恐ろしい経験則だと思います。

あなたがすべきことは言語によって異なります。例外をスローするタイミングについては、言語ごとに異なる規則があります。たとえば、Pythonはすべてに対して例外をスローします。Pythonの場合は、私もそれに続きます。一方、C ++は例外を比較的少数しかスローしません。C ++またはJavaをPythonのように扱い、すべてに対して例外をスローできますが、言語自体がどのように使用されることを期待するかと対立します。

私はPythonのアプローチを好みますが、他の言語をくじくのは悪い考えだと思います。


1
@gnat、知っています。私のポイントは、たとえそれがあなたのお気に入りではないとしても、あなたは言語(この場合はJava)の規約に従うべきだということでした。
ウィンストンイーバート

6
+1 "exceptions should be exceptional" is a terrible rule of thumb.そうですね!それは、人々が考えずに繰り返すものの1つです。
アンドレス・F.

2
「期待される」は、主観的な議論や慣習ではなく、API /関数の契約によって定義されます(これは明示的かもしれませんが、しばしば暗示されています)。異なる機能/ API /サブシステムは異なる期待を持つことができます。例えば、いくつかのより高いレベルの機能については、存在しないファイルはそれが処理するための期待されるケースです(GUIを通じてこれをユーザーに報告するかもしれません)、他のより低いレベルの機能についてはおそらくそうではありません(したがって、例外をスローする必要があります)。この答えは、その重要なポイントを見逃しているようです
。...-mikera

1
@mikera、はい、関数はそのコントラクトで定義された例外を(のみ)スローする必要があります。しかし、それは問題ではありません。問題は、その契約の内容をどのように決定するかです。私は、「例外は例外であるべきだ」という経験則は、その決定を下すのに役に立たないと主張します。
ウィンストンイーバート

1
@supercat、私はそれが実際に重要になるとは思いませんが、どちらがより一般的になります。重大な問題は、正気のデフォルトを持つことだと思います。エラー状態を明示的に処理しない場合、コードは何も起こらないふりをしたり、有用なエラーメッセージを受け取ったりしますか?
ウィンストンイーバート

30

例外を考えるときは、データベースサーバーやWeb APIにアクセスするようなことを常に考えています。サーバー/ Web APIが機能することを期待していますが、例外的なケースでは機能しない可能性があります(サーバーがダウンしています)。通常、Web要求は高速ですが、例外的な状況(高負荷)ではタイムアウトになる場合があります。これはあなたがコントロールできないものです。

ユーザーの入力データは管理下にあります。これは、ユーザーが送信する内容を確認し、好きなように処理できるためです。あなたの場合、保存する前にユーザー入力を検証します。そして、無効なデータを提供するユーザーが期待されるべきであり、入力を検証し、ユーザーフレンドリーなエラーメッセージを提供することでアプリがそれを説明することに同意する傾向があります。

そうは言っても、ほとんどのドメインモデルセッターでは例外を使用しますが、そこでは無効なデータが入る可能性はまったくありません。 、そのため、そのドメインモデルの例外をトリガーする可能性はほとんどありません。したがって、セッターが1つのことを期待し、別のことを取得する場合、それは例外的な状況であり、通常の状況では発生しませんでした。

編集(他に考慮すべきこと):

ユーザーが提供したデータをdbに送信するとき、テーブルに入力すべきものとすべきでないものを事前に知っています。これは、予期される形式に対してデータを検証できることを意味します。これはあなたがコントロールできるものです。制御できないのは、クエリの途中でサーバーが失敗することです。そのため、クエリが正常であり、データがフィルタリング/検証されていることがわかっているので、クエリを試行しても失敗します。これは例外的な状況です。

同様に、Webリクエストの場合、リクエストを送信する前に、リクエストがタイムアウトするか、接続に失敗するかを知ることはできません。そのため、リクエストを送信するときに数ミリ秒後に動作するかどうかをサーバーに尋ねることができないため、これはtry / catchアプローチも保証します。


8
しかし、なぜ?なぜ例外は、より予想される問題の処理においてあまり役に立たないのですか?
ウィンストンイーバート

6
@Pinetree、ファイルを開く前にファイルの存在を確認するのは悪い考えです。ファイルは、チェックとオープンの間に存在しなくなる可能性があります。ファイルには、それを開く許可がありません。また、存在をチェックしてからファイルを開くには、2つの高価なシステムコールが必要です。ファイルを開こうとして、失敗した場合に対処する方が良いでしょう。
ウィンストンイーバート

10
私が見る限り、ほとんどすべての可能性のある障害は、事前に成功を確認しようとするよりも、障害から回復する方が適切に処理されます。例外を使用するかどうか、または他の何かが失敗を示すかどうかは別の問題です。例外を誤って無視できないため、例外を好みます。
ウィンストンユワート

11
無効なユーザーデータが予期されるため、例外と見なすことはできないという前提に同意しません。パーサーを作成し、誰かが解析できないデータをフィードした場合、それは例外です。解析を続行できません。例外の処理方法は完全に別の質問です。
ConditionRacer

4
File.ReadAllBytesはFileNotFoundException、間違った入力(たとえば、存在しないファイル名)が指定されたときにスローします。これがこのエラーを脅かす唯一の有効な方法ですが、エラーコードを返さずに他にできることは何ですか?
oɔɯǝɹ

16

参照

実用プログラマーから

プログラムの通常のフローの一部として例外を使用することはめったにないはずです。例外は、予期しないイベントのために予約する必要があります。キャッチされない例外がプログラムを終了し、「すべての例外ハンドラーを削除してもこのコードは実行されますか?」と自問します。答えが「いいえ」の場合、例外が非例外的な状況で使用されている可能性があります。

さらに、読み取り用にファイルを開く例を調べますが、ファイルは存在しません。例外を発生させる必要がありますか?

ファイルが場合必要がありますがあった場合、例外が正当化されます。[...]一方、ファイルが存在するかどうかわからない場合は、見つからない場合でも例外とは思われず、エラーが返されます。

後で、彼らはこのアプローチを選んだ理由について議論します:

[A] n例外は、即時の非ローカル制御の転送を表しgotoます。これは一種のカスケードです。通常の処理の一部として例外を使用するプログラムは、従来のスパゲッティコードの読みやすさと保守性の問題をすべて抱えています。これらのプログラムはカプセル化を破ります:ルーチンとその呼び出し元は、例外処理を介してより緊密に結合されます。

あなたの状況について

あなたの質問は、「検証エラーは例外を発生させるべきですか?」答えは、検証が行われている場所に依存するということです。

問題のメソッドがコードのセクション内にあり、入力データが既に検証されていると想定される場合、無効な入力データは例外を発生させるはずです。このメソッドがユーザーによって入力された正確な入力を受け取るようにコードが設計されている場合、無効なデータが予期されるため、例外は発生しません。


11

ここには哲学的な主張がたくさんありますが、一般的に言って、例外的な条件は、ユーザーの介入なしでは処理できない(クリーンアップ、エラー報告などを除く)単純な条件です。言い換えれば、それらは回復不可能な状態です。

何らかの方法でそのファイルを処理する目的でプログラムにファイルパスを渡し、そのパスで指定されたファイルが存在しない場合、それは例外的な状態です。ユーザーに報告し、別のファイルパスを指定できるようにすること以外は、コード内でそれについて何もできません。


1
+1、私が言っていたことに非常に近い。それはスコープに関するものであり、ユーザーとは何の関係もありません。これの良い例は、2つの.Net関数int.Parseとint.TryParseの違いです。前者は、悪い入力で例外をスローする以外に選択肢がありません。後者は例外をスローするべきではありません
-jmoreno

1
@jmoreno:エルゴ、コードが解析不可能な条件について何かできる場合はTryParseを使用し、できない場合はParseを使用します。
ロバートハーヴェイ

7

考慮すべき2つの懸念事項があります。

  1. 単一の懸念事項について議論します- Assignerこの懸念事項は構造化オブジェクトに入力を割り当てることであるため、それを呼び出しましょう-そして、その入力が有効であるという制約を表現します

  2. よく実装されたユーザインタフェースは、追加の懸念があります。エラー時にユーザー入力&建設的なフィードバックの検証(のはこの部分を呼びましょうValidator

Assignerコンポーネントの観点からは、違反した制約を表明したため、例外をスローすることはまったく合理的です。

ユーザーエクスペリエンスの観点から、ユーザーはそもそもこれと直接話し合うべきではありませんAssigner。彼らはそれに話をする必要があります経由Validator

さて、ではValidator、無効なユーザー入力は例外的なケースではなく、実際にあなたがより興味を持っているケースです。したがって、ここでは例外は適切ではなく、これはすべてのエラーを識別するのではなく最初に救済します。

これらの懸念がどのように実装されているかに言及しなかったことにお気づきでしょう。あなたはについて話しているようでAssigner、あなたの同僚は複合について話していValidator+Assignerます。あなたが実現するとしている二つの別々の(または分離可能)懸念が、少なくともあなたは賢明にそれを議論することができます。


ルナンさんのコメントに対処するために、私はちょうどだと仮定すると、あなたの二つの別々の懸念を特定したら、それはそれぞれの文脈における例外的な考慮すべき例は明白だと。

実際、何かが例外的であると考えられるべきかどうか明らかでない場合、おそらくソリューションの独立した懸念を特定し終わっていないと思います。

私はそれが直接の答えになると思います

...私のケースが例外的であるかどうかをどのように知ることができますか?

それが明らかになるまで簡素化し続けてください。よく理解している単純な概念が山ほどある場合、それらをコード、クラス、ライブラリなどに再構成することを明確に考えることができます。


-1はい、2つの懸念がありますが、これは「私のケースが例外的であるかどうかをどのように知ることができますか?」という質問には答えません。
RMalke

重要な点は、同じケースはあるコンテキストでは例外的であり、別のコンテキストでは例外的ではないということです。ここで質問に答えるのは、実際に話しているコンテキストを特定することです(両方を融合するのではなく)。
役に立たない

...実際には、そうではないかもしれません-代わりに私の答えであなたのポイントに対処しました。
役に立たない

4

他の人はよく答えましたが、それでも私の短い答えがここにあります。例外は、環境内の何かが間違っている状況であり、これを制御することはできず、コードをまったく進めることができません。この場合、何が間違っていたのか、それ以上先に進めない理由、および解決策をユーザーに通知する必要があります。


3

私は、例外的な場合にのみ例外を投げるべきだというアドバイスの大ファンではありませんでした、それは部分的には何も言わないからです(食用の食べ物だけを食べるべきだと言っているようなものですが)は非常に主観的であり、例外的なケースを構成するものとそうでないものが明確でないことがよくあります。

ただし、このアドバイスには正当な理由があります:例外のスローとキャッチが遅い、および例外がスローされるたびに通知するように設定されたVisual Studioのデバッガーでコードを実行している場合、数十人にスパムされる可能性があります問題が発生するずっと前に、何百ものメッセージがない場合。

したがって、一般的なルールとして、次の場合:

  • コードにバグがなく、かつ
  • 依存するサービスがすべて利用可能であり、かつ
  • ユーザーは、使用することを意図した方法でプログラムを使用しています(提供する入力の一部が無効であっても)

その場合、コードは例外をスローすることはありません。例外は後でキャッチされます。無効なデータをトラップするためInt32.TryParse()に、プレゼンテーション層などのUIレベルまたはコードでバリデーターを使用できます。

それ以外の場合は、例外はメソッドがその名前のとおりにできないことを意味するという原則に固執する必要があります。一般に、失敗を示すために戻りコードを使用することはお勧めできません(メソッド名が明示的にそれを示している場合を除きますTryParse())。最初に、エラーコードに対するデフォルトの応答は、エラー状態を無視して、関係なく続行することです。第二に、戻りコードを使用するいくつかのメソッドと、例外を使用する他のメソッドを簡単に作成できます。ここでは、同じインターフェイスの2つの異なる交換可能な実装が異なるアプローチをとるコードベースを見てきました。


2

例外は、呼び出し元のメソッドが処理する場合でも、すぐに呼び出すコードが処理する準備ができていない可能性が高い条件を表す必要があります。たとえば、ファイルから一部のデータを読み取り、有効なファイルが有効なレコードで終了し、部分的なレコードから情報を抽出する必要がないと合法的に仮定するコードを考えてみましょう。

データ読み取りルーチンが例外を使用せず、読み取りが成功したかどうかを単に報告した場合、呼び出しコードは次のようになります。

temp = dataSource.readInteger();
if (temp == null) return null;
field1 = (int)temp;
temp = dataSource.readInteger();
if (temp == null) return null;
field2 = (int)temp;
temp = dataSource.readString();
if (temp == null) return null;
field3 = temp;

など。有用な作業ごとに3行のコードを費やす。対照的に、もしreadIntegerファイルの終わりに遭遇すると例外がスローされ、呼び出し元が単に例外を渡すことができる場合、コードは次のようになります。

field1 = dataSource.readInteger();
field2 = dataSource.readInteger();
field3 = dataSource.readString();

物事が正常に機能する場合にはるかに重点を置いて、よりシンプルでクリーンな外観。すぐに呼び出し元条件を処理することを期待している場合、エラーコードを返すメソッドは、例外をスローするメソッドよりも役立つことが多いことに注意してください。たとえば、ファイル内のすべての整数を合計するには:

do
{
  temp = dataSource.tryReadInteger();
  if (temp == null) break;
  total += (int)temp;
} while(true);

try
{
  do
  {
    total += (int)dataSource.readInteger();
  }
  while(true);
}
catch endOfDataSourceException ex
{ // Don't do anything, since this is an expected condition (eventually)
}

整数を要求するコードは、これらの呼び出しの1つが失敗することを予期しています。コードに無限ループを使用させ、それが発生するまで実行することは、戻り値を介して失敗を示すメソッドを使用するよりもはるかにエレガントではありません。

多くの場合、クラスはクライアントがどの条件を予期するか、または予期しないかを知らないため、一部の呼び出し元が予期し、他の呼び出し元が予期しない方法で失敗する可能性のある2つのバージョンのメソッドを提供すると便利です。そうすることで、そのようなメソッドを両方のタイプの呼び出し元できれいに使用できるようになります。また、呼び出し側が予期しない状況が発生した場合、「try」メソッドでさえ例外をスローすることに注意してください。たとえばtryReadInteger、クリーンなファイルの終わり状態に遭遇した場合、例外をスローすべきではありません(呼び出し側がそれを予期していなかった場合、呼び出し側はreadInteger)。一方、たとえば、データを含むメモリスティックが取り外されたためにデータを読み取れなかった場合は、おそらく例外をスローする必要があります。このようなイベントは常に可能性として認識される必要がありますが、即時の呼び出しコードが応答に役立つ何かを実行する準備ができることはほとんどありません。ファイルの終わりの状態と同じ方法で報告されるべきではありません。


2

ソフトウェアを書く上で最も重要なことは、それを読みやすくすることです。他のすべての考慮事項は二次的であり、効率化や正確化を含みます。読み取り可能な場合は、メンテナンスで残りの部分を処理できます。読み取りできない場合は、破棄することをお勧めします。したがって、読みやすくする場合は例外をスローする必要があります。

アルゴリズムを作成するときは、将来それを読む人について考えてください。潜在的な問題が発生する可能性がある場所に来たら、読者がその問題をどのように処理するかを見たいのか、それともアルゴリズムをそのまま使いたいのかを自問してください。

チョコレートケーキのレシピを考えるのが好きです。卵を追加するように指示された場合、選択肢があります:卵があると仮定してレシピを始めるか、卵がない場合に卵を得る方法の説明を開始することができます。野生の鶏を狩るためのテクニックで本全体を埋めることができ、すべてがケーキを焼くのに役立ちます。それは良いことですが、ほとんどの人はそのレシピを読みたくないでしょう。ほとんどの人は、卵が利用可能であると単に仮定して、レシピを続けたいと思うでしょう。それは、著者がレシピを書くときに行う必要がある判断の呼び出しです。

読者の心を読む必要があるため、良い例外となるものや、すぐに処理すべき問題に関する保証されたルールはありません。最善の方法は経験則であり、「例外は例外的な状況にのみ当てはまる」はかなり良いものです。通常、読者はメソッドを読んでいるときに、メソッドが99%の時間で行うことを探しています。そして、ユーザーが違法な入力やほとんど決して起こらない他のものを入力するなどの奇妙なコーナーケースで散らかってはいけません。彼らはあなたのソフトウェアの通常の流れを、問題が決して起こらないかのように次々と直接指示してレイアウトしたいと考えています。


2

最初のエラーだけでなく、入力で見つかったすべてのエラーについてレポートする必要があるかもしれません。

これが、ここで例外をスローできない理由です。例外はすぐに検証プロセスを中断します。そのため、これを行うには多くの回避策があります。

悪い例:

Dog例外を使用したクラスの検証メソッド:

void validate(Set<DogValidationException> previousExceptions) {
    if (!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        DogValidationException disallowedName = new DogValidationException(Problem.DISALLOWED_DOG_NAME);
        if (!previousExceptions.contains(disallowedName)){
            throw disallowedName;
        }
    }
    if (this.legs < 4) {
        DogValidationException invalidDog = new DogValidationException(Problem.LITERALLY_INVALID_DOG);
        if (!previousExceptions.contains(invalidDog)){
            throw invalidDog;
        }
    }
    // etc.
}

それを呼び出す方法:

Set<DogValidationException> exceptions = new HashSet<DogValidationException>();
boolean retry;
do {
    retry = false;
    try {
        dog.validate(exceptions);
    } catch (DogValidationException e) {
        exceptions.add(e);
        retry = true;
    }
} while (retry);

if(exceptions.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

ここでの問題は、検証プロセスでは、すべてのエラーを取得するために、すでに見つかった例外をスキップする必要があることです。上記はうまくいくかもしれませんが、これは例外の明らかな誤用です。データベースに触れる前に、要求された検証の種類を実行する必要があります。したがって、何もロールバックする必要はありません。また、検証の結果は、検証エラーである可能性が高い(ただし、ゼロが望ましい)。

より良いアプローチは次のとおりです。

メソッド呼び出し:

Set<Problem> validationResults = dog.validate();
if(validationResults.isEmpty()) {
    dogDAO.beginTransaction();
    dogDAO.save(dog);
    dogDAO.commitAndCloseTransaction();
} else {
    // notify user to fix the problems
}

検証方法:

Set<Problem> validate() {
    Set<Problem> result = new HashSet<Problem>();
    if(!DOG_NAME_PATTERN.matcher(this.name).matches()) {
        result.add(Problem.DISALLOWED_DOG_NAME);
    }
    if(this.legs < 4) {
        result.add(Problem.LITERALLY_INVALID_DOG);
    }
    // etc.
    return result;
}

どうして?たくさんの理由があり、ほとんどの理由は他の回答で指摘されています。シンプルに言えば:はるかに簡単です他の人が読んで理解すること。次に、dog間違って設定したことを説明するためにユーザースタックトレースを表示しますか?

場合は第二の例では、コミット時に、まだエラーが発生し、あなたのバリデータが検証されていても、dogゼロの問題で、例外をスローすることは正しいことです。いいね:データベース接続なし、その間、他の誰かがデータベースエントリを変更した、またはそのようなもの。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.