私の質問は、ほとんどの開発者がエラー処理、例外、またはエラー戻りコードに対して何を好むかです。言語(または言語ファミリ)を具体的に説明し、どちらを優先するのかを説明してください。
好奇心からこれをお願いしています。個人的には、エラーリターンコードの方が爆発性が低く、ユーザーが望まない場合でも例外のパフォーマンスペナルティを支払うようユーザーコードに強制しないため、私はそれを好みます。
更新:すべての回答に感謝します!例外的なコードフローの予測不能性は嫌いですが、戻りコード(およびその兄のハンドル)に関する答えは、コードに多くのノイズを追加します。
C++はRAIIに基づいています。
失敗、戻り、またはスローする可能性のあるコード(つまり、ほとんどの通常のコード)がある場合は、スマートポインター内にポインターをラップする必要があります(が非常に優れていると仮定してオブジェクトをスタックに作成しない理由)。
それらは冗長で、次のようなものに発展する傾向があります:
if(doSomething())
{
if(doSomethingElse())
{
if(doSomethingElseAgain())
{
// etc.
}
else
{
// react to failure of doSomethingElseAgain
}
}
else
{
// react to failure of doSomethingElse
}
}
else
{
// react to failure of doSomething
}
結局のところ、コードは識別された命令のコレクションです(この種のコードは製品コードで見ました)。
このコードは次のように変換できます。
try
{
doSomething() ;
doSomethingElse() ;
doSomethingElseAgain() ;
}
catch(const SomethingException & e)
{
// react to failure of doSomething
}
catch(const SomethingElseException & e)
{
// react to failure of doSomethingElse
}
catch(const SomethingElseAgainException & e)
{
// react to failure of doSomethingElseAgain
}
cangoodであるコードとエラー処理を明確に分離します。
1つのコンパイラからのあいまいな警告( "phjr"のコメントを参照)でない場合、それらは簡単に無視できます。
上記の例では、誰かが考えられるエラーを処理するのを忘れたと仮定します(これは起こります...)。エラーは「返された」場合は無視され、後で爆発する可能性があります(つまり、NULLポインター)。同じ問題は例外なく発生しません。
エラーは無視されません。時々、あなたはそれが爆発しないことを望みます、しかし、あなたは慎重に選ばなければなりません。
次の関数があるとします。
呼び出された関数の1つが失敗した場合のdoTryToDoSomethingWithAllThisMessの戻り値のタイプは何ですか?
オペレーターはエラーコードを返すことができません。 C++コンストラクタもできません。
上記のポイントの当然の結果。私が書きたい場合はどうなりますか:
CMyType o = add(a, multiply(b, c)) ;
戻り値は既に使用されているため(そして、変更できない場合もあるため)、できません。つまり、戻り値は最初のパラメーターになり、参照として送信されます。
例外の種類ごとに異なるクラスを送信できます。リソースの例外(つまり、メモリ不足)は軽いはずですが、それ以外のものは必要なだけ重いものになる可能性があります(Java例外がスタック全体を与えるのが好きです)。
その後、各漁獲を特化することができます。
通常、エラーを非表示にするべきではありません。再スローしない場合は、少なくとも、エラーをファイルに記録し、メッセージボックスを開いてください...
例外のある問題は、それらを使いすぎるとtry/catchesでいっぱいのコードが生成されることです。しかし、問題は別の場所にあります。誰がSTLコンテナを使用して自分のコードを試行/キャッチするのでしょうか。それでも、これらのコンテナは例外を送信できます。
もちろん、C++では、例外をデストラクタから出さないでください。
それらがひざまずいてあなたのスレッドを引き出す前にそれらを捕まえるか、あなたのウィンドウズメッセージループの中で伝播するようにしてください。
したがって、解決策は何かが発生したときにスローすることだと思いますnotが発生します。そして、何かが発生する可能性がある場合は、戻りコードまたはパラメーターを使用して、ユーザーがそれに対応できるようにします。
したがって、唯一の質問は、「発生してはならないことは何か」ということです。
それはあなたの機能の契約に依存します。関数がポインターを受け入れるが、ポインターが非NULLでなければならないことを指定している場合、ユーザーがNULLポインターを送信したときに例外をスローしても問題ありません(C++では、関数の作成者が代わりに参照を使用しなかった場合の質問)ポインタの、しかし...)
時々、あなたの問題はあなたがエラーを望まないことです。例外やエラーの戻りコードを使用するのはいいことですが、それについて知りたいのです。
私の仕事では、一種の「アサート」を使用しています。構成ファイルの値に応じて、デバッグ/リリースのコンパイルオプションに関係なく、次のようになります。
開発とテストの両方で、これにより、ユーザーは問題が検出されたときではなく、検出されたときに正確に特定できます(一部のコードが戻り値を気にするとき、またはキャッチ内)。
レガシーコードに追加するのは簡単です。例えば:
void doSomething(CMyObject * p, int iRandomData)
{
// etc.
}
次のようなコードをリードします:
void doSomething(CMyObject * p, int iRandomData)
{
if(iRandomData < 32)
{
MY_RAISE_ERROR("Hey, iRandomData " << iRandomData << " is lesser than 32. Aborting processing") ;
return ;
}
if(p == NULL)
{
MY_RAISE_ERROR("Hey, p is NULL !\niRandomData is equal to " << iRandomData << ". Will throw.") ;
throw std::some_exception() ;
}
if(! p.is Ok())
{
MY_RAISE_ERROR("Hey, p is NOT Ok!\np is equal to " << p->toString() << ". Will try to continue anyway") ;
}
// etc.
}
(デバッグでのみアクティブな同様のマクロがあります)。
本番環境では構成ファイルが存在しないため、クライアントがこのマクロの結果を見ることはないことに注意してください...しかし、必要なときにそれをアクティブにするのは簡単です。
リターンコードを使用してコーディングするときは、失敗に備える準備ができており、テストの要塞が十分に安全であることを願っています。
例外を使用してコーディングすると、コードが失敗する可能性があり、通常はコード内の選択した戦略的な位置に反撃のキャッチを配置します。しかし、通常、コードは「何をしなければならないか」よりも「私が恐れていること」がより重要です。
しかし、コードを作成するときは、自由に最高のツールを使用する必要があります。「エラーを非表示にせず、できるだけ早く表示する」という場合もあります。上で話したマクロは、この哲学に従っています。
私は実際に両方を使用しています。
既知のエラーである可能性がある場合は、戻りコードを使用します。私が知っているシナリオであり、実際に発生する場合は、返送されるコードがあります。
例外は、私が予期していないものにのみ使用されます。
フレームワーク設計ガイドライン:再利用可能な.NETライブラリの規約、イディオム、およびパターンの「例外」というタイトルの第7章によれば、OO C#などのフレームワーク。
おそらくこれが最も説得力のある理由です(179ページ)。
「例外はオブジェクト指向言語とうまく統合します。オブジェクト指向言語は、非OO言語の関数によって課されないメンバーシグネチャに制約を課す傾向があります。たとえば、コンストラクター、演算子オーバーロード、およびプロパティ、開発者は戻り値を選択できません。このため、オブジェクト指向フレームワークの戻り値ベースのエラー報告を標準化することはできません。エラー報告方法、メソッドシグネチャの範囲外の例外は唯一のオプションです。 "
私の好み(C++およびPython)は例外を使用することです。言語が提供する機能により、例外の発生、キャッチ、および(必要な場合)再スローの両方が明確に定義されたプロセスになり、モデルが見やすく、使いやすくなります。概念的には、特定の例外を名前で定義でき、それに付随する追加情報があるという点で、リターンコードよりもきれいです。リターンコードを使用すると、エラー値のみに制限されます(ReturnStatusオブジェクトなどを定義する場合を除く)。
作成しているコードがタイムクリティカルでない限り、スタックの巻き戻しに関連するオーバーヘッドは心配するほど重要ではありません。
例外は、予期しないことが発生した場合にのみ返されます。
もう1つの例外は、歴史的に、戻りコードは本質的に独自仕様であり、C関数から成功を示すために0が返されることもあり、-1の場合もあります。列挙されている場合でも、列挙があいまいになる可能性があります。
例外は、より多くの情報を提供することもでき、具体的には「何かが間違っている、ここに何があるか、スタックトレースとコンテキストのいくつかのサポート情報」を詳しく説明できます
そうは言っても、よく列挙された戻りコードは、既知の一連の結果、つまり単純な「関数のn個の結果があり、このように実行された」に役立ちます。
Javaでは、次の順序で使用します。
契約による設計(試行する前に前提条件が満たされていることを確認anything失敗する可能性があります)。これはほとんどのことをキャッチし、私はこのエラーコードを返します。
処理中(および必要に応じてロールバックを実行中)にエラーコードを返します。
例外ですが、これらは使用されますonly予期しないことのために。
私が得たすばらしいアドバイスThe Pragmatic Programmerは、「プログラムはすべての主要機能を例外を使用せずに実行できるはずです」という言葉に沿ったものでした。
私はこれについて ブログ投稿 を少し前に書いた。
例外をスローすることによるパフォーマンスのオーバーヘッドは、決定に影響を与えません。あなたが正しくやっているなら、結局のところ、例外はexceptionalです。
戻りコードは、ほぼ毎回 " Pit of Success "テストに失敗します。
パフォーマンスについて:
リターンコードが嫌いです。コード全体で次のパターンが急増します。
CRetType obReturn = CODE_SUCCESS;
obReturn = CallMyFunctionWhichReturnsCodes();
if (obReturn == CODE_BLOW_UP)
{
// bail out
goto FunctionExit;
}
すぐに、4つの関数呼び出しで構成されるメソッド呼び出しは、12行のエラー処理で膨張します。その一部は決して発生しません。 ifとswitchのケースはたくさんあります。
例外を上手く使用すれば、例外はより明確になります...例外的なイベントを通知するために..その後、実行パスを続行できません。多くの場合、エラーコードよりも説明的で情報提供的です。
メソッド呼び出しの後に、異なる方法で処理する必要がある(例外的なケースではない)複数の状態がある場合は、エラーコードまたは出力パラメーターを使用します。 Personalyですが、これは珍しいと思いました。
C++/COMの世界では「パフォーマンスのペナルティ」の反論について少し探しましたが、新しい言語では、それほどの違いはないと思います。いずれにせよ、何かが爆発した場合、パフォーマンスの問題はbackburnerに委ねられます:)
リターンコードはコードノイズを増加させると思います。たとえば、戻りコードのために、COM/ATLコードの外観は常に嫌いでした。コードのすべての行に対してHRESULTチェックが必要でした。エラーの戻りコードは、COMの設計者が行った悪い決定の1つだと思います。コードの論理的なグループ化が難しくなるため、コードのレビューが難しくなります。
行ごとに戻りコードの明示的なチェックがある場合のパフォーマンス比較についてはわかりません。
まともなコンパイラやランタイム環境では、例外が発生しても大きなペナルティは発生しません。これは、例外ハンドラにジャンプするGOTOステートメントに似ています。また、ランタイム環境(JVMなど)で例外をキャッチすると、バグの特定と修正が非常に簡単になります。 Cでは、segfaultを介してJavaでいつでもNullPointerExceptionを受け取ります。
私はpythonの例外を例外と非例外の両方の状況で使用しています。
エラー値を返すのではなく、例外を使用して「要求を実行できなかった」ことを示すことができるのはよくあることです。それはあなたが/ always /を知っているということは、戻り値が適切な型であることを知っているということです。これは、戻り値の条件ではなく、例外ハンドラーを使用したい場合の良い例です。
try:
dataobj = datastore.fetch(obj_id)
except LookupError:
# could not find object, create it.
dataobj = datastore.create(....)
副作用として、datastore.fetch(obj_id)が実行されたときに、戻り値がNoneであるかどうかを確認する必要がなく、すぐに無料でエラーが発生します。これは、「プログラムは、例外をまったく使用せずに、プログラムのすべての主要機能を実行できる必要がある」という反対の意見です。
競合状態の影響を受けないファイルシステムを処理するためのコードを記述するために、例外が「例外的に」役立つ別の例を次に示します。
# wrong way:
if os.path.exists(directory_to_remove):
# race condition is here.
os.path.rmdir(directory_to_remove)
# right way:
try:
os.path.rmdir(directory_to_remove)
except OSError:
# directory didn't exist, good.
pass
2つではなく1つのシステムコール。競合状態はありません。これは、ディレクトリが存在しない場合よりも多くの状況でOSErrorで失敗するため、これは不適切な例ですが、厳密に制御された多くの状況では「十分な」ソリューションです。
エラー処理と戻り値(またはパラメーター)の例外を関数の通常の結果として使用することを好みます。これにより、簡単で一貫性のあるエラー処理スキームが提供され、正しく実行すると、見た目がはるかにきれいなコードになります。
大きな違いの1つは、例外ではエラーを処理する必要があるのに対し、エラーの戻りコードはチェックされないことがあります。
エラーリターンコードを頻繁に使用すると、次の形式と同様のifテストが多数含まれる非常に見苦しいコードが発生する可能性があります。
if(function(call) != ERROR_CODE) {
do_right_thing();
}
else {
handle_error();
}
個人的には、呼び出しコードが対処する必要があるエラーに対して例外を使用し、何かを返すことが実際に有効で可能である「予期される失敗」に対してのみエラーコードを使用することを好みます。
リターンコードよりも例外を優先する理由はたくさんあります。
例外はエラー処理ではありません、IMO。例外はそれだけです。あなたが期待していなかった例外的なイベント。注意して使用してください。
エラーコードはOKですが、メソッドから404または200を返すのは適切ではありません、IMO。代わりに列挙型(.Net)を使用してください。これにより、他の開発者にとってコードが読みやすくなり、使いやすくなります。また、数値と説明の表を維持する必要はありません。
また; try-catch-finallyパターンは私の本のアンチパターンです。 try-finallyは良いことですが、try-catchも良いですが、try-catch-finallyは決して良いことではありません。多くの場合、try-finallyは「using」ステートメント(IDisposeパターン)に置き換えることができます。これは、IMOの方が優れています。そして、処理できる例外を実際にキャッチするTry-catchは良いです。
try{
db.UpdateAll(somevalue);
}
catch (Exception ex) {
logger.Exception(ex, "UpdateAll method failed");
throw;
}
したがって、例外をバブルし続けさせる限り、問題はありません。別の例はこれです:
try{
dbHasBeenUpdated = db.UpdateAll(somevalue); // true/false
}
catch (ConnectionException ex) {
logger.Exception(ex, "Connection failed");
dbHasBeenUpdated = false;
}
ここで私は実際に例外を処理します。 updateメソッドが失敗したときにtry-catchの外で何をするかは別の話ですが、私の主張はなされたと思います。 :)
では、なぜtry-catch-finallyがアンチパターンなのでしょうか?理由は次のとおりです。
try{
db.UpdateAll(somevalue);
}
catch (Exception ex) {
logger.Exception(ex, "UpdateAll method failed");
throw;
}
finally {
db.Close();
}
Dbオブジェクトがすでに閉じている場合はどうなりますか?新しい例外がスローされ、処理する必要があります!これの方が良い:
try{
using(IDatabase db = DatabaseFactory.CreateDatabase()) {
db.UpdateAll(somevalue);
}
}
catch (Exception ex) {
logger.Exception(ex, "UpdateAll method failed");
throw;
}
または、dbオブジェクトがIDisposableを実装していない場合は、次のようにします。
try{
try {
IDatabase db = DatabaseFactory.CreateDatabase();
db.UpdateAll(somevalue);
}
finally{
db.Close();
}
}
catch (DatabaseAlreadyClosedException dbClosedEx) {
logger.Exception(dbClosedEx, "Database connection was closed already.");
}
catch (Exception ex) {
logger.Exception(ex, "UpdateAll method failed");
throw;
}
それはとにかく私の2セントです! :)
私は例外のみを使用し、戻りコードは使用しません。 Javaここで話しています。
私が従う一般的なルールは、doFoo()
というメソッドがある場合、 "fooを実行しない"場合は、例外が発生し、例外がスローされるということです。
例外について私が恐れていることの1つは、例外をスローするとコードフローが台無しになることです。例えばあなたがするなら
void foo()
{
MyPointer* p = NULL;
try{
p = new PointedStuff();
//I'm a module user and I'm doing stuff that might throw or not
}
catch(...)
{
//should I delete the pointer?
}
}
あるいは、私が持ってはならないものを削除したが、残りのクリーンアップを行う前にキャッチするようにスローされた場合、さらに悪いことになります。投げることは、貧しいユーザーIMHOに多くの重みを与えました。
例外と戻りコードの引数の私の一般的なルール:
私は単純なルールのセットを持っています:
1)即時の発信者が反応することを期待するものに戻りコードを使用します。
2)より広い範囲のエラーには例外を使用し、呼び出し元よりも多くのレベルで処理されることが合理的に期待できるため、エラーの認識が多くの層に浸透する必要がなく、コードがより複雑になります。
Java私はこれまでチェックされていない例外のみを使用していましたが、チェックされた例外は単に別の形式のリターンコードになり、私の経験では、メソッド呼び出しによって「返される」可能性のあるものの双対性は一般にヘルプよりも障害。
戻りコードが例外よりも見苦しくないとは思いません。例外として、try{} catch() {} finally {}
があり、戻りコードと同様にif(){}
があります。私は以前、投稿で示された理由で例外を恐れていました。ポインターをクリアする必要があるかどうかはわかりません。しかし、戻りコードに関しても同じ問題があると思います。問題の関数/メソッドの詳細がわからない限り、パラメーターの状態はわかりません。
いずれにしても、可能であればエラーを処理する必要があります。リターンコードを無視してプログラムをsegfaultさせるのと同じくらい簡単に、例外をトップレベルに伝播させることができます。
結果には値(列挙?)を返し、例外的なケースには例外を返すという考え方が好きです。
Javaのような言語の場合、例外が処理されないとコンパイラーがコンパイル時エラーを出すため、例外を使用します。これにより、呼び出し側の関数は例外を処理/スローします。
Pythonの場合、私はもっと対立しています。コンパイラーがないため、ランタイム例外につながる関数によってスローされた例外を呼び出し側が処理しない可能性があります。戻りコードを使用する場合、適切に処理されないと予期しない動作が発生する可能性があり、例外を使用するとランタイム例外が発生する可能性があります。