メソッドが例外をスローできる場合、意味のあるtryブロックでこの呼び出しを保護しないのは無謀であると私は常に信じていました。
私はちょうど 'を投稿しました。あなたは常に、tryでスローできる呼び出しをラップし、ブロックをキャッチする必要があります。'を この質問 に伝え、非常に悪いアドバイス」-理由を理解したい。
メソッドは、適切な方法で処理できる場合にのみ例外をキャッチする必要があります。
それ以外の場合は、呼び出しスタックの上位のメソッドがそれを理解できることを期待して、それを渡します。
他の人が指摘したように、致命的なエラーがログに記録されるように、コールスタックの最上位に未処理の例外ハンドラー(ログあり)を置くことをお勧めします。
Mitchandothers のように、何らかの方法で処理する予定がないという例外をキャッチするべきではありません。アプリケーションを設計するときは、アプリケーションがどのように例外を体系的に処理するかを考慮する必要があります。これは通常、抽象化に基づいてエラー処理の層を持つことになります-たとえば、データアクセスコード内のすべてのSQL関連エラーを処理して、ドメインオブジェクトと対話しているアプリケーションの部分が、どこかにあるDBです。
「どこでもすべてを捕まえる」の匂いに加えて、絶対に避けたい関連コードの匂いがいくつかあります。
"catch、log、rethrow":スコープベースのロギングが必要な場合は、スタックがアンロールするときにデストラクタでログステートメントを発行するクラスを記述します例外のため(ala std::uncaught_exception()
)。必要なことは、関心のあるスコープでロギングインスタンスを宣言することだけです。できれば、ロギングがあり、不要なtry
/catch
ロジックはありません。
"キャッチ、翻訳されたスロー":これは通常、抽象化の問題を指します。いくつかの特定の例外をより一般的な例外に変換するフェデレーションソリューションを実装している場合を除き、おそらく不必要な抽象化層があります...明日」。
"キャッチ、クリーンアップ、再スロー":これは私のペットのおしっこです。これがたくさんある場合は、 Resource Acquisition is Initialization テクニックを適用して、デストラクタにクリーンアップ部分を配置する必要がありますjanitorオブジェクトインスタンスの.
try
/catch
ブロックで散らばっているコードは、コードレビューとリファクタリングの良いターゲットであると考えています。例外処理が十分に理解されていないか、コードがアメーバになっており、リファクタリングが必要なことを示しています。
次の質問は「例外をキャッチしたので、次に何をすればいいですか?」あなたは何をしますか?何もしなければ-それはエラーの隠蔽であり、プログラムは何が起こったのかを見つける機会なしに「機能しない」可能性があります。例外をキャッチしたら、何をするかを正確に理解し、わかっている場合にのみキャッチする必要があります。
ハーブ・サッターはこの問題について書いた こちら 。必ず読む価値がある。
ティーザー:
「例外に対して安全なコードを書くことは、基本的に正しい場所に「try」と「catch」を書くことです。」話し合います。
率直に言って、その声明は例外安全性の根本的な誤解を反映しています。例外はエラー報告の別の形式であり、エラーセーフコードを記述することは、リターンコードをチェックしてエラー状態を処理する場所だけではないことは確かです。
実際、例外の安全性は「try」と「catch」を記述することについてはめったにないことがわかりました。また、例外の安全性がコードの設計に影響することを忘れないでください。調味料のようにいくつかの余分なキャッチステートメントを追加することができる後付けだけではありません。
Try-catchは、呼び出しスタックのさらに下の関数でスローされた未処理の例外をキャッチできるため、try-catchesでeveryブロックをカバーする必要はありません。そのため、すべての関数にtry-catchを持たせるのではなく、アプリケーションのトップレベルロジックにtry-catchを持たせることができます。たとえば、他のメソッドなどを呼び出す多くのメソッドを呼び出すSaveDocument()
トップレベルルーチンがあるかもしれません。これらのサブメソッドは、独自のtry-catchesを必要としません。 SaveDocument()
のキャッチ。
これは3つの理由で素晴らしいです。エラーを報告する場所が1つしかないので便利です:SaveDocument()
catchブロック。すべてのサブメソッドを通してこれを繰り返す必要はありません。とにかくそれはあなたが望むものです:間違った何かについての有用な診断をユーザーに提供する1つの場所。
2つ目は、例外がスローされるたびに保存がキャンセルされることです。すべてのサブメソッドのtry-catchingで、例外がスローされた場合、そのメソッドのcatchブロックに入り、実行は関数を離れ、onSaveDocument()
を介して。何かがすでに間違っている場合は、すぐに停止する可能性があります。
3つ目は、すべてのサブメソッドがすべての呼び出しが成功したと想定できることです。呼び出しが失敗した場合、実行はcatchブロックにジャンプし、後続のコードは実行されません。これにより、コードがよりきれいになります。たとえば、次のエラーコードがあります。
int ret = SaveFirstSection();
if (ret == FAILED)
{
/* some diagnostic */
return;
}
ret = SaveSecondSection();
if (ret == FAILED)
{
/* some diagnostic */
return;
}
ret = SaveThirdSection();
if (ret == FAILED)
{
/* some diagnostic */
return;
}
例外を使用してそれを記述する方法を次に示します。
// these throw if failed, caught in SaveDocument's catch
SaveFirstSection();
SaveSecondSection();
SaveThirdSection();
これで、何が起こっているかがより明確になりました。
例外セーフコードは、他の方法で書くのが難しい場合があることに注意してください。例外がスローされた場合、メモリをリークしたくないのです。オブジェクトは例外の前に常に破壊されるため、RAII、STLコンテナー、スマートポインター、およびデストラクターでリソースを解放するその他のオブジェクトについて必ず知っておいてください。
他の回答で述べたように、例外に対して何らかの何らかの理にかなったエラー処理を行うことができる場合にのみ、例外をキャッチする必要があります。
たとえば、質問を生成した 質問 では、質問者は、整数から文字列へのlexical_cast
の例外を無視しても安全かどうかを尋ねます。このようなキャストが失敗することはありません。失敗した場合、プログラムで何かがひどく間違っています。その状況で回復するにはどうすればよいでしょうか?信頼できない状態にあるため、プログラムを終了させるのがおそらく最善です。そのため、例外を処理しないことが最も安全な場合があります。
例外をスローできるメソッドの呼び出し元で常に例外をすぐに処理する場合、例外は役に立たなくなり、エラーコードを使用した方がよいでしょう。
例外の全体的なポイントは、呼び出しチェーンのすべてのメソッドで処理する必要がないことです。
私が聞いた最高のアドバイスは、例外的な状況について賢明に何かできる点でのみ例外をキャッチすべきであり、「キャッチ、ログ、リリース」は良い戦略ではないということです(ライブラリーで避けられない場合があります)。
最低レベルで可能な限り多くの例外を処理するという質問の基本的な方向性に同意します。
既存の回答の一部は、「例外を処理する必要はありません。他の誰かがそれをスタックで処理します」のようになります。私の経験では、それは考えない悪い言い訳現在開発されているコード部分での例外処理についてであり、例外処理は他の人またはそれ以降の問題を処理します。
この問題は、同僚が実装したメソッドを呼び出す必要がある分散開発で劇的に増大します。そして、メソッド呼び出しのネストされたチェーンを調べて、彼/彼女が何らかの例外をスローしている理由を見つける必要があります。これは、最も深いネストされたメソッドではるかに簡単に処理できた可能性があります。
コンピューターサイエンスの教授がかつて私に与えたアドバイスは、「Try and Catchブロックは、標準的な手段を使用してエラーを処理できない場合にのみ使用してください」でした。
例として、彼は、次のようなことができない場所でプログラムが何らかの重大な問題に遭遇した場合、私たちに言った。
int f()
{
// Do stuff
if (condition == false)
return -1;
return 0;
}
int condition = f();
if (f != 0)
{
// handle error
}
次に、try、catchブロックを使用する必要があります。例外を使用してこれを処理することはできますが、例外はパフォーマンスの面でコストがかかるため、一般的に推奨されません。
すべての機能の結果をテストする場合は、戻りコードを使用します。
例外の目的は、結果を頻繁にテストできるようにすることです。アイデアは、より普通のコードから例外的な(異常な、まれな)条件を分離することです。これにより、通常のコードがより簡潔でシンプルになりますが、これらの例外的な条件を処理できます。
適切に設計されたコードでは、より深い関数がスローし、より高い関数がキャッチする可能性があります。しかし、重要なのは、「間にある」多くの機能が例外的な条件を処理する負担からまったく解放されるということです。彼らは「例外安全」である必要があります、それは彼らがキャッチしなければならないことを意味しません。
上記のアドバイスに加えて、個人的にいくつかのtry + catch + throwを使用しています。次の理由により:
この議論に追加したいのは、C++ 11なので、すべてのcatch
ブロックrethrow
sが処理できる/すべきポイントまでの例外。この方法でバックトレースを生成できます。したがって、以前の意見は一部時代遅れだと思います。
std::nested_exception
および std::throw_with_nested
を使用しますStackOverflow here および here でこれを実現する方法について説明しています。
派生した例外クラスでこれを実行できるため、このようなバックトレースに多くの情報を追加できます!また、私の GitHubのMWE を見ると、バックトレースは次のようになります。
Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"
マイク・ウィートの答えは要点をかなりうまくまとめているが、私は別の答えを付けざるを得ないと感じている。このように思います。複数のことを行うメソッドがある場合、複雑さを増やすのではなく、増やします。
つまり、try catchにラップされたメソッドには、2つの結果があります。例外以外の結果と例外の結果があります。たくさんのメソッドを扱っているとき、これは理解を超えて指数関数的に爆発します。
指数関数的に、各メソッドが2つの異なる方法で分岐する場合、別のメソッドを呼び出すたびに、潜在的な結果の以前の数を二乗するからです。 5つのメソッドを呼び出した時点で、少なくとも256の可能な結果になります。これをnotと比較して、すべてのメソッドでtry/catchを実行すると、たった1つのパスしかありません。
それは基本的に私がそれを見る方法です。アプリケーションの状態が基本的に未定義になるため、どのタイプの分岐も同じことをするが、try/catchesは特別なケースであると主張したくなるかもしれません。
要するに、try/catchesはコードを理解するのをはるかに難しくします。
アプリに多くのエラーがあり、ユーザーが問題と回り込みにうんざりしていたため、いくつかのプロジェクトを救う「機会」が与えられ、経営陣が開発チーム全体を置き換えました。これらのコードベースはすべて、トップレベルの回答で説明されているように、アプリレベルでエラー処理を集中管理していました。その答えがベストプラクティスである場合、なぜそれが機能せず、前の開発チームが問題を解決できるのですか?おそらくそれがうまくいかないのでしょうか?上記の回答では、開発者が単一の問題の修正に費やす時間については言及していません。問題を解決する時間が重要な指標である場合、try..catchブロックを使用してコードを計測することをお勧めします。
私のチームはUIを大幅に変更せずにどのように問題を修正しましたか?シンプルで、すべてのメソッドはtry..catchブロックでインスツルメントされ、すべてがメソッド名、エラーメッセージ、エラーメッセージ、アプリ名、日付とともに渡された文字列に連結されたメソッドパラメーター値で障害点でログに記録されました。およびバージョン。この情報を使用して、開発者はエラーに対して分析を実行し、最も多く発生する例外を特定できます!または、エラーの数が最も多い名前空間。また、モジュールで発生したエラーが適切に処理され、複数の理由が原因ではないことも検証できます。
これのもう1つの利点は、開発者がエラーロギングメソッドに1つのブレークポイントを設定し、1つのブレークポイントと「ステップアウト」デバッグボタンを1回クリックするだけで、実際の障害発生時のオブジェクト。イミディエイトウィンドウで便利に使用できます。デバッグが非常に簡単になり、実行をメソッドの先頭にドラッグして戻して問題を複製し、正確な行を見つけることができます。集中化された例外処理により、開発者は30秒で例外を複製できますか?いや.
「メソッドは、例外を適切な方法で処理できる場合にのみ例外をキャッチする必要があります。」これは、開発者がリリース前に発生する可能性のあるすべてのエラーを予測できるか、発生することを意味します。これがトップレベルに当てはまる場合、アプリ例外ハンドラーは不要であり、Elastic Searchとlogstashの市場はありません。
このアプローチにより、開発者は本番環境で断続的な問題を見つけて修正することもできます!本番環境でデバッガなしでデバッグしますか?それとも、電話を取り、動揺しているユーザーからメールを受け取りますか?これにより、問題を修正するために必要なすべてがそこにあるので、他の誰かが知る前に問題を修正し、メール、IM、またはサポート付きのSlackを必要としません。問題の95%を再現する必要はありません。
適切に機能するには、名前空間/モジュール、クラス名、メソッド、入力、エラーメッセージをキャプチャし、データベースに保存できる集中ログと組み合わせて、どのメソッドが最も失敗するかを強調表示できるように集約する必要があります最初に修正。
開発者はcatchブロックからスタックに例外をスローすることもありますが、このアプローチはスローしない通常のコードよりも100倍遅いです。ロギングを使用したキャッチおよびリリースが推奨されます。
この手法は、2年間で12人の開発者が開発したFortune 500企業のほとんどのユーザーが1時間ごとに失敗するアプリを迅速に安定させるために使用されました。この3000の異なる例外を使用して、4か月で特定、修正、テスト、展開を行いました。これは、平均して4か月間、平均15分ごとに修正されます。
コードのインスツルメントに必要なものをすべて入力するのは楽しいことではないことに同意し、繰り返しコードを見ないことを好みますが、各メソッドに4行のコードを追加することは長期的には価値があります。