メソッドが例外をスローする可能性がある場合、意味のあるtryブロックでこの呼び出しを保護しないのは無謀だと私は常に信じてきました。
私は投稿したばかりです ' try、catchブロックをスローする可能性のある呼び出しは常にラップする必要があります。「この質問に対して、それは「著しく悪いアドバイス」であると言われました-理由を理解したいと思います。
メソッドが例外をスローする可能性がある場合、意味のあるtryブロックでこの呼び出しを保護しないのは無謀だと私は常に信じてきました。
私は投稿したばかりです ' try、catchブロックをスローする可能性のある呼び出しは常にラップする必要があります。「この質問に対して、それは「著しく悪いアドバイス」であると言われました-理由を理解したいと思います。
回答:
メソッドは、何らかの賢明な方法で処理できる場合にのみ、例外をキャッチする必要があります。
それ以外の場合は、呼び出しスタックの上位にあるメソッドがそれを理解できることを期待して、それを渡します。
他の人が述べたように、致命的なエラーが確実にログに記録されるように、コールスタックの最高レベルに未処理の例外ハンドラー(ログ付き)を置くことをお勧めします。
try
ブロックには(生成されたコードの観点から)コストがかかることにも注意してください。スコット・マイヤーズの「より効果的なC ++」には良い議論があります。
try
、最新のCコンパイラではブロックは無料です。その情報はニックの日付です。また、局所性情報(命令が失敗した実際の場所)を失うため、トップレベルの例外ハンドラーを持つことにも同意しません。
terminate
) 。それはより安全なメカニズムです。また、try/catch
例外がなければ、多かれ少なかれ無料です。伝播が1つある場合、それがスローおよびキャッチされるたびに時間が消費されるため、try/catch
その再スローのみのチェーンはコストがかからないわけではありません。
ミッチ と 他の人が述べたように、あなたは、いくつかの方法で処理する予定がないという例外をキャッチしてはいけません。アプリケーションを設計するときは、アプリケーションが体系的に例外を処理する方法を検討する必要があります。これは通常、抽象化に基づいてエラー処理のレイヤーを持つことにつながります。たとえば、データアクセスコード内のすべてのSQL関連エラーを処理して、ドメインオブジェクトと対話するアプリケーションの部分がそこにあるという事実にさらされないようにします。どこかに隠れているDBです。
「どこでもすべてを捕まえる」臭いに加えて、絶対に避けたいいくつかの関連するコード臭があります。
"catch、log、rethrow":スコープベースのロギングが必要な場合は、例外が原因でスタックがアンロールされるときに、デストラクタでログステートメントを発行するクラスを記述します(ala std::uncaught_exception()
)。必要なのは、関心のあるスコープでログ記録インスタンスを宣言することだけです。これで、ログ記録が作成され、不要なtry
/ catch
ロジックはなくなりました。
「キャッチ、スロートランスレート」:これは通常、抽象化の問題を示します。いくつかの特定の例外をもう1つの一般的な例外に変換するフェデレーションソリューションを実装していない限り、不必要な抽象化レイヤーがある可能性があります。
"catch、cleanup、rethrow":これは私のピートピーチの一つです。これがたくさんある場合は、リソース取得は初期化の手法を適用し、クリーンアップ部分を管理人オブジェクトインスタンスのデストラクタに配置する必要があります。
try
/ catch
ブロックが散らばっているコードは、コードのレビューとリファクタリングの良いターゲットだと思います。これは、例外処理が十分に理解されていないか、コードがamabaになり、リファクタリングの深刻な必要性があることを示しています。
Logger
クラスと同様のクラスを使用しlog4j.Logger
、例外がアクティブなときにデストラクタで警告を発しました。
すべてのブロックをtry-catchesでカバーする必要はありません。try-catchは、コールスタックのさらに下の関数でスローされた未処理の例外をキャッチできるためです。したがって、すべての関数にtry-catchを持たせるのではなく、アプリケーションの最上位ロジックに1つ持つことができます。たとえば、SaveDocument()
他のメソッドなどを呼び出す多くのメソッドを呼び出すトップレベルのルーチンがあるかもしれません。これらのサブメソッドは、独自のtry-catchesを必要としません。なぜなら、それらがスローした場合でも、それはのSaveDocument()
catch によってキャッチされるからです。
これは、3つの理由で便利ですSaveDocument()
。エラーを報告する場所が1つしかないため、便利です。つまり、catchブロックです。すべてのサブメソッドでこれを繰り返す必要はありません。とにかくそれが必要なものです。問題が発生した場合の有用な診断を1つの場所でユーザーに提供します。
2つ目は、例外がスローされるたびに保存がキャンセルされることです。すべてのサブメソッドのtry-catchingで、例外がスローされた場合、そのメソッドのcatchブロックに入り、実行は関数を離れ、それからまで続きSaveDocument()
ます。すでに何かがうまくいっていない場合は、すぐに停止したいと思うでしょう。
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コンテナ、スマートポインタ、およびデストラクタでリソースを解放するその他のオブジェクトについて知っておく必要があります。
try
- catch
少し異なるメッセージで少しずつ異なるエラーの順列にフラグを立てようとするブロックですが、実際にはすべて同じように終了します:トランザクションまたはプログラムの失敗と終了!例外に値する障害が発生した場合、ほとんどのユーザーはできる限りの救済をしたい、または少なくとも、それについて10レベルのメッセージを処理する必要なく放っておいてほしいと思っています。
ハーブサッターはこの問題についてここに書いています。確かに読む価値があります。
ティーザー:
「例外セーフなコードを書くことは、基本的に正しい場所に「try」と「catch」を書くことに関するものです。」話し合います。
率直に言って、そのステートメントは例外的な安全性の根本的な誤解を反映しています。例外はエラー報告のもう1つの形式であり、エラーセーフコードを書くことは、戻りコードをチェックしてエラー条件を処理する場所だけではないことは確かです。
実際には、例外の安全性は「試行」と「キャッチ」を書くことについてはめったにないことがわかり、そしてよりまれに優れていることがわかりました。また、例外の安全性がコードの設計に影響することを忘れないでください。まるで調味料のように、いくつかの追加のキャッチステートメントで後付けできる単なる後付けではありません。
他の回答で述べられているように、例外をキャッチできるのは、それに対して何らかの賢明なエラー処理を実行できる場合のみです。
たとえば、質問を生み出した質問で、質問者はlexical_cast
整数から文字列への例外を無視しても安全かどうかを尋ねます。このようなキャストは決して失敗しないはずです。それが失敗した場合は、プログラムで何かがひどく間違っています。その状況で回復するために何ができるでしょうか?プログラムは信頼できない状態にあるため、プログラムを停止させるのがおそらく最善です。したがって、例外を処理しないのが最も安全な方法です。
私が聞いた最良のアドバイスは、例外条件について賢明に何かを行うことができるポイントでのみ例外をキャッチすべきであり、「キャッチ、ログ、およびリリース」は良い戦略ではないということです(ライブラリで時々避けられない場合)。
できるだけ低いレベルでできるだけ多くの例外を処理するという質問の基本的な方向性に同意します。
既存の回答の一部は、「例外を処理する必要はありません。他の誰かがスタックで処理します」のようになります。考えないのは悪い言い訳である私の経験に、現在開発されているコードでの例外処理を、例外処理を他の誰かまたはそれ以降の人の問題にしてしまうのです。
この問題は、同僚が実装したメソッドを呼び出す必要がある分散開発で劇的に増大します。次に、メソッド呼び出しのネストされたチェーンを検査して、彼/彼女が例外をスローしている理由を見つける必要があります。これは、最も深いネストされたメソッドではるかに簡単に処理できたはずです。
私のコンピュータサイエンスの教授がかつて私に与えたアドバイスは、「標準的な方法ではエラーを処理できない場合にのみ、TryブロックとCatchブロックを使用すること」でした。
例として、次のようなことを行うことができない場所でプログラムが深刻な問題に遭遇した場合、彼は私たちに言った:
int f()
{
// Do stuff
if (condition == false)
return -1;
return 0;
}
int condition = f();
if (f != 0)
{
// handle error
}
次に、try、catchブロックを使用する必要があります。例外を使用してこれを処理することはできますが、例外はパフォーマンスの面でコストがかかるため、一般的にはお勧めできません。
すべての関数の結果をテストする場合は、戻りコードを使用してください。
例外の目的は、少ない結果を頻繁にテストできるようにすることです。アイデアは、例外的な(珍しい、まれな)条件をより一般的なコードから分離することです。これにより、通常のコードがよりクリーンでシンプルに保たれますが、それらの例外的な条件も処理できます。
適切に設計されたコードでは、より深い関数がスローされ、より高い関数がキャッチする可能性があります。しかし重要なのは、「中間」にある多くの機能が例外的な条件を処理する負担から解放されることです。彼らは「例外安全」でなければならないだけで、それは彼らが捕まえなければならないという意味ではありません。
この議論に付け加えておきたいのは、C ++ 11以降、すべてのcatch
ブロックrethrow
が、処理できる/すべきところまで例外である限り、それは非常に理にかなっているということです。これにより、バックトレースを生成できます。したがって、以前の意見の一部は古くなっていると思います。
std::nested_exception
してstd::throw_with_nested
StackOverflowの上で説明され、こことここにこれを達成する方法。
派生した例外クラスでこれを行うことができるため、そのようなバックトレースに多くの情報を追加できます!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ブロックを使用してコードをインストルメント化することをお勧めします。
私のチームはUIを大幅に変更せずに問題をどのように修正しましたか?単純です。すべてのメソッドはtry..catchでブロックされ、すべてが失敗した時点でメソッド名、メソッドパラメータ値がエラーメッセージ、エラーメッセージ、アプリ名、日付とともに渡される文字列に連結されてログに記録されました。とバージョン。この情報を使用して、開発者はエラーに対して分析を実行し、最も発生する例外を特定できます。または、エラーの数が最も多いネームスペース。また、モジュールで発生したエラーが適切に処理され、複数の理由が原因ではないことも検証できます。
これのもう1つの利点は、開発者がエラーロギングメソッドに1つのブレークポイントを設定でき、1つのブレークポイントと「ステップアウト」デバッグボタンの1回のクリックで、実際のメソッドへのフルアクセスで失敗したメソッドにあることです。障害発生時のオブジェクト。イミディエイトウィンドウで簡単に利用できます。デバッグが非常に簡単になり、実行をメソッドの先頭にドラッグして戻し、問題を複製して正確な行を見つけることができます。一元化された例外処理により、開発者は例外を30秒で複製できますか?番号。
「メソッドは、それが何らかの賢明な方法で処理できる場合にのみ、例外をキャッチする必要があります。」これは、開発者がリリース前に発生する可能性のあるすべてのエラーを予測できるか、発生する可能性があることを意味します。これがトップレベルである場合、アプリの例外ハンドラーは必要なく、Elastic Searchとlogstashの市場はありません。
このアプローチにより、開発者は本番環境で断続的な問題を見つけて修正することもできます。本番環境でデバッガなしでデバッグしますか?または、電話をかけて、動揺しているユーザーからメールを受け取りますか?これにより、他の誰かに気付かれる前に問題を修正できます。問題を修正するために必要なすべてのものがそこにあるので、サポートにメール、IM、またはSlackする必要はありません。95%の問題を再現する必要はありません。
適切に機能させるには、名前空間/モジュール、クラス名、メソッド、入力、およびエラーメッセージをキャプチャしてデータベースに格納できる集中型ロギングと組み合わせる必要があります。これにより、どのメソッドが最も失敗するかをハイライト表示して集約できるため、最初に修正しました。
開発者は、catchブロックからスタックに例外をスローすることを選択する場合がありますが、このアプローチは、スローしない通常のコードよりも100倍遅くなります。ロギングによるキャッチアンドリリースが推奨されます。
この手法は、12 Devsが2年間開発したFortune 500企業で、ほとんどのユーザーが1時間ごとに失敗するアプリをすばやく安定させるために使用されました。この3000の異なる例外を使用して、4か月で識別、修正、テスト、および展開されました。これは平均して、4か月間平均15分ごとに修正されます。
コードのインストルメント化に必要なすべてを入力するのは楽しいことではないことに同意します。繰り返しのコードを見ない方がよいですが、各メソッドに4行のコードを追加することは、長期的には価値があります。
上記のアドバイスに加えて、個人的には、try + catch + throwを使用しています。次の理由により:
マイク・ウィートの回答は主なポイントをかなりうまくまとめていますが、私は別の回答を追加せざるを得ません。このように思います。複数のことを行うメソッドがある場合、複雑さを増加させるのではなく、追加することになります。
つまり、try catchにラップされたメソッドには、2つの結果が考えられます。あなたは非例外の結果と例外の結果を持っています。あなたが多くの方法を扱っているとき、これは理解を超えて指数関数的に爆破します。
なぜなら、各メソッドが2つの異なる方法で分岐する場合、別のメソッドを呼び出すたびに、潜在的な結果の以前の数を二乗しているからです。5つのメソッドを呼び出すまでに、最低256の結果が得られます。これと比較しない、すべての単一のメソッドでtry / catchを実行すると、たどるパスは1つだけです。
それが基本的に私がそれを見る方法です。どのタイプの分岐でも同じことを行うと主張したくなるかもしれませんが、アプリケーションの状態は基本的に未定義になるため、try / catchは特殊なケースです。
つまり、try / catchesを使用すると、コードを理解するのが非常に難しくなります。
内部のコードのすべての部分を隠す必要はありませんtry-catch
。try-catch
ブロックの主な用途は、エラー処理と、プログラム内のバグ/例外です。try-catch
-の使用法
try-catch
ブロックを使用できます。try
/ catch
はそれと完全に分離/直交しています。より小さなスコープでオブジェクトを破棄する場合は、単に新しいオブジェクトを開くことができます。もちろん、実際に何かを必要としない限り{ Block likeThis; /* <- that object is destroyed here -> */ }
、これをtry
/ にラップする必要はありません。catch
catch