try catch-blocksの良い使用法は?


15

私は常にこれに取り組んでいます... try / catchingと、ホットポテトのようにコールスタックにスローバックされるタブ、ブラケット、例外のこのわいせつな混乱にならないコードとの適切なバランスを見つけようとしています。たとえば、SQLiteを使用する現在開発中のアプリがあります。SQLite呼び出しを抽象化するデータベースインターフェイスと、データベースに出入りするものを受け入れるモデルがあります。したがって、SQLite例外が発生した場合は、モデル(呼び出し元)に投げ込まれます。 )、AddRecord / DeleteRecord / whateverを呼び出した人に渡す必要がある人...  

エラーコードを返すとは対照的に、エラーコードがなど、忘れられ、無視することができますので、私は(許可され、私は例外は基本的に処理する必要があるのに対し、例外のファンだ可能性があり、私はよ...キャッチし、すぐに移動します)確かに、私が今していることよりも良い方法があるはずです。

編集:  私はこれを少し異なって表現する必要がありました。私は異なるタイプなどとして再スローすることを理解しています。私の質問は...そうするとき、どのようにコードをきれいに保つのが最善ですか?しばらくすると、私にとって非常に混乱し始めます。


どのプログラミング言語ですか?
アパララ

2
現時点ではC#ですが、一般的に考えようとしています。
-trycatch

C#は、スローされた例外の宣言を強制しません。これにより、合理的である場合に例外を処理しやすくなり、プログラマーが実際に処理せずに例外をキャッチしようとするのを防ぎます。アンダース・ヘルスバーグ、C#の設計者は、この記事でチェック例外に対する訴訟になりartima.com/intv/handcuffs.html
アパラーラ

回答:


14

強く型付けされた言語を使用していない場合でも、強い型付けの観点から考えてください。メソッド期待する型を返せない場合は、例外をスローする必要があります。

また、SQLExceptionをモデル(またはさらに悪いことにUI)にスローするのではなく、各レイヤーは既知の例外をキャッチし、そのレイヤーに適した例外でラップ/変更/置換する必要があります。

Layer      Handles Exception
----------------------------
UI         DataNotFoundException
Model      DatabaseRetrievalException
DAO        SQLException

これは、各レイヤーで探している例外の数を制限し、組織化された例外システムを維持するのに役立ちます。


私はいくつかの編集を行い、元のQの表現が不十分でした。私の質問は、すべての試行錯誤やその他を処理しながらコードをクリーンに保つ方法についての詳細です。try catchブロックが至る所にあると、非常に厄介に感じ始めます
...-trycatch

1
@Edは、UIまたはモデルが「SQLException」をキャッチしていることを気にしませんか?私にとって、それはあまり関係ありません。それぞれに、私は推測する。
ニコール

1
@Ed、それはチェック例外を持たない言語では大丈夫かもしれませんが、チェック例外がある言語でthrows SQLExceptionは、SQLが関与していることを暗示していないメソッドを持っているのは本当にreallyいです。また、一部の操作をファイルストアに送信することにした場合はどうなりますか?ここでthrows SQLException, IOException、などを宣言する必要があります。手に負えなくなります。
マイクダニエルズ

1
エンドユーザーへの@Ed SQLExceptionはあまり意味がありません。特に、システムがリレーショナルデータベース以外の複数の種類の永続性をサポートできる場合はそうです。GUIプログラマーとして、低レベルの例外のセット全体よりも、DataNotFoundExceptionを処理する必要があります。多くの場合、低レベルライブラリの例外は、上記の通常の生活の一部に過ぎません。この場合、抽象レベルがアプリケーションのレベルと一致する場合、それらははるかに簡単に処理できます。
ニュートピア

1
@Newtopian-エンドユーザーに生の例外を提示するとは決して言いませんでした。エンドユーザーにとっては、簡単な「プログラムが動作を停止しました」というメッセージで十分ですが、例外をログに記録して、サポートをサポートするのに役立ちます。デバッグモードでは、例外を表示すると便利です。このような例外の特定のハンドラーがない限り、APIがスローする可能性のあるすべての例外を気にする必要はありません。未処理の例外キャッチャーにすべてを処理させるだけです。
エドジェームズ

9

例外は、その大部分が通常のケースを処理し、例外的なケースは後で、異なるコンテキストであっても処理できるため、よりクリーンなコードを書くことができます。

例外を処理(キャッチ)するためのルールは、実際に何かを行うことができるコンテキストによって行われる必要があるということです。ただし、例外があります。

例外は、モジュールの境界(特にレイヤーの境界)でキャッチする必要があります。例外を囲むためだけに、呼び出し側にとって意味のあるより高いレベルの例外をスローする必要があります。各モジュールとレイヤーは、例外に関する実装の詳細も隠す必要があります(ヒープモジュールはHeapFullをスローできますが、ArrayIndexOutOfBoundsは決してスローしません)。

あなたの例では、上位層がSQLite例外について何もできないことはまずありません(もしそうなら、データ層を他のものに切り替えることができなくなるほどSQLiteに結合されます)。追加/削除/更新などが失敗する予測可能な理由がいくつかあり、それらの一部(同時トランザクションでの互換性のない変更)は、データ/永続層(整合性ルール違反、fi)からでも回復することが不可能です。永続層は、例外をモデル層の用語で意味のあるものに変換して、上位層が再試行するか失敗するかを決定できるようにします。


私はそれに同意し、私が質問した場所がひどい言葉であったことを反映するように編集しました。これは、試行錯誤によって物事が乱雑にならないようにする方法の問題になることを意味していました。
-trycatch

@Ed Jamesと私が言及た原則を、レイヤーまたはモジュール内で適用できます。あらゆる場所から直接SQLiteを呼び出す代わりに、SQLiteと通信し、例外から回復するか、より一般的なものに変換するメソッド/関数をいくつか用意します。トランザクションに複数のクエリと更新が含まれる場合、それぞれの例外をすべて処理する必要はありません。1つの外側のtry-catchが例外を変換し、内側のtry-catchがロールバックで部分的な更新を処理できます。更新を独自の関数に移動して、より簡単な例外処理を行うこともできます。
アパララ

1
このようなものは、コード例を理解することが容易になるだろう。..
Upvoteクリックして

これはおそらく、最初からスタックオーバーフローに適した質問でした。
アパラーラ

5

一般的なルールとして、特定の例外(IOExceptionなど)のみをキャッチする必要があります。また、例外をキャッチした後、特定のことを行う必要がある場合に限ります。

それ以外の場合は、例外を公開して対処できるように、例外を表面にバブルアップさせるのが最善です。一部の人々はこれをフェイルファストと呼びます。

下からバブルアップした未処理の例外をキャッチするために、アプリケーションのルートに何らかの種類のハンドラーが必要です。これにより、適切な方法で例外を提示、報告、または管理する機会が与えられます。

例外のラップは、分散システム全体で例外をスローする必要があり、クライアントにサーバー側の障害の定義がない場合に役立ちます。


例外をキャッチしてサイレンシングするのは恐ろしいことです。プログラムが破損している場合、例外とトレースバックでクラッシュするはずです。それは静かに物事を記録しますが、データを混乱させます。
-S.ロット

1
@ S.Lott例外を黙らせてはいけないことに同意します。なぜなら、彼らは迷惑だと思うからです。ただアプリケーションをクラッシュさせるのは少し極端です。例外をキャッチして既知の状態でシステムをリセットできる場合が多くあります。そのような場合、すべてのグローバルハンドラーは非常に便利です。ただし、リセットプロセスが順番に失敗し、安全に処理できない場合は、砂に頭を突き刺すよりもクラッシュさせた方がはるかに優れています。
ニュートピア

2
繰り返しますが、それはすべてあなたが構築しているものに依存しますが、私は一般に、抽象化の漏れを作成するためにそれらをバブルアップさせることに同意しません。APIを使用するときは、APIのモデルと一致する例外が公開されることを望みます。また、驚きも嫌いなので、APIが実装に関連する例外を予告なしに覗き見するのは嫌です。私に何が来ているのかわからない場合、どうすれば反応できますか?予告なしにアプリがクラッシュするのを防ぐために、はるかに広いキャッチネットを頻繁に使用していることに気付きます。
ニュートピア

@Newtopian:「ラップ」例外と「書き換え」は、抽象化の漏洩を減らします。彼らはまだ適切にバブルします。「私に何が来ているのかわからない場合、どうすれば反応することができます!私はあまりにも多くのキャッチネットを頻繁に使用していることに気づきます」すべてをキャッチする必要はありません。80%の時間、正しいことは何もキャッチしないことです。20%の時間に意味のある応答があります。
S.Lott

1
@Newtopian、通常はオブジェクトによってスローされるためラップする必要のある例外と、オブジェクトのコードのバグが原因でラップすべきではない例外を区別する必要があると思います。
ウィンストンイーバート

4

あなたがスタッククラスを書いていると想像してください。クラスに例外処理コードを配置しないでください。その結果、次の例外が生成される可能性があります。

  1. ArrayIndexError-ユーザーが空のスタックからポップしようとすると発生します
  2. NullPtrException-実装のバグによりnull参照を参照しようとしたために発生

例外をラップする単純なアプローチでは、これらの例外の両方をStackError例外クラスにラップすることがあります。ただし、これは例外をラップするポイントを実際に見逃しています。オブジェクトが低レベルの例外をスローした場合、オブジェクトが壊れていることを意味します。ただし、これが許容される場合が1つあります。オブジェクトが実際に壊れている場合です。

例外をラップするポイントは、オブジェクトが通常のエラーに対して適切な例外を与える必要があるということです。空のスタックからポップする場合、スタックはArrayIndexErrorではなくStackEmptyを発生させる必要があります。その目的は、オブジェクトまたはコードが破損した場合に他の例外をスローすることを避けることではありません

私たちは本当に避けたいことは、高レベルのオブジェクトを通過してきた低レベルの例外をキャッチされました。空のスタックからポップするときにArrayIndexErrorをスローするスタッククラスは、小さな問題です。そのArrayIndexErrorを実際にキャッチすると、深刻な問題が発生します。低レベルのエラーの伝播は、それらをキャッチするよりもはるかに深刻な罪です。

これをSQLExceptionの例に戻すと、なぜSQLExceptionが発生しますか?1つの理由は、無効なクエリを渡すためです。ただし、データアクセスレイヤーが不適切なクエリを生成している場合は、破損しています。DataAccessFailure例外で破損を再ラップしようとしてはなりません。

ただし、データベースへの接続が失われると、SQLExceptionが発生する可能性もあります。その時点での私の戦略は、防御の最終段階で例外をキャッチし、データベース接続が失われたことをユーザーに報告してシャットダウンすることです。アプリケーションはデータベースにアクセスできなくなるため、実際に実行できることはあまりありません。

あなたのコードがどのように見えるかわかりません。しかし、あなたは盲目的にすべての例外をより高いレベルの例外に変換しているように思えます。比較的少数の場合にのみそれを行うべきです。最も低いレベルの例外は、コードのバグを示しています。それらをキャッチして再ラップすると、逆効果になります。

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