私はしばしば、著者が他の言語よりも自分の優先言語を主張するために「例外vs明示的なエラーチェック」という引数を使用する白熱したブログ投稿に遭遇します。一般的なコンセンサスは、例外を利用する言語は、明示的な関数呼び出しによるエラーチェックに大きく依存する言語よりも本質的に優れている/よりクリーンであるようです。
例外の使用は、明示的なエラーチェックよりも優れたプログラミング手法と見なされますか?そうであれば、なぜですか?
私の考えでは、最大の議論は、プログラマーがエラーを犯したときに何が起きるかの違いです。エラーを処理するのを忘れることは、非常によくある簡単な間違いです。
エラーコードを返す場合は、警告なしでエラーを無視できます。たとえば、mallocが失敗すると、NULL
が返され、グローバルerrno
が設定されます。だから正しいコードは
void* myptr = malloc(1024);
if (myptr == NULL) {
perror("malloc");
exit(1);
}
doSomethingWith(myptr);
しかし、代わりに次のように書くだけでも非常に簡単で便利です。
void* myptr = malloc(1024);
doSomethingWith(myptr);
これにより、予期せずNULL
が他のプロシージャに渡され、慎重に設定されたerrno
が破棄される可能性があります。これが可能であることを示すコードに目に見える問題はありません。
例外を使用する言語では、代わりにあなたは書くでしょう
MyCoolObject obj = new MyCoolObject();
doSomethingWith(obj);
この(Java)の例では、new
演算子は有効な初期化オブジェクトを返すか、OutOfMemoryError
をスローします。プログラマがこれを処理する必要がある場合、彼らはそれをキャッチすることができます。致命的なエラーである通常の(そして便利なことに、遅延も)場合、例外の伝搬により、プログラムは比較的クリーンで明示的な方法で終了します。
これが、例外を適切に使用すると、明確で安全なコードをはるかに簡単に記述できる理由の1つです。このパターンは、メモリの割り当てだけでなく、失敗する可能性のある多くのことに当てはまります。
スティーブンの答え は良い説明を提供しますが、私がかなり重要だと思う別のポイントがあります。エラーコードを確認するときに、失敗したケースをすぐに処理できないことがあります。コールスタックを通じてエラーを明示的に伝達する必要があります。大きな関数をリファクタリングするとき、サブ関数にすべてのエラーチェックボイラープレートコードを追加する必要がある場合があります。
例外を除いて、メインフローを処理するだけで済みます。コードの一部がInvalidOperationErrorをスローした場合でも、そのコードをサブ関数に移動でき、エラー管理ロジックが維持されます。
したがって、例外を使用すると、リファクタリングを迅速に実行し、ボイラープレートを回避できます。
別の角度からの視点:エラー処理はすべてセキュリティに関するものです。チェックされていないエラーは、次のコードが基づいているすべての仮定と前提条件を破ります。これにより、多くの外部攻撃ベクトルが開かれる可能性があります。単純なDoSから無許可のデータアクセス、データの破損、完全なシステムへの侵入まで。
確かに、それは特定のアプリケーションに依存しますが、仮定と前提条件が破られた場合、すべての賭けは無効になります。複雑なソフトウェアの中では、それ以降何が可能で、何がexternから使用でき、何が使用できないかを確実に言うことはできません。
それを踏まえると、基本的な観察があります。セキュリティを後で接続できるオプションのアドオンとして扱うと、ほとんどの場合失敗します。これは、最初の基本的な設計段階で既に検討され、最初から組み込まれている場合に最適に機能します。
これは、基本的には例外として得られるものです。気にしなくてもアクティブになっている組み込みのエラー処理インフラストラクチャです。明示的なテストでは、自分で作成する必要があります。最初のステップとしてそれを構築する必要があります。アプリケーション開発の最終段階に入るまで、全体像を考えずにエラーコードを返す関数を書き始めただけで、事実上失敗するアドオンエラー処理になります。
組み込みシステムが何らかの形で役立つわけではありません。 ほとんどのプログラマーはエラー処理の方法を知りません。ほとんどの場合、複雑すぎます。
発生する可能性のある非常に多くのエラーがあり、各エラーには独自の処理と独自のアクションと対応が必要です。
同じエラーでも、コンテキストに基づいて異なるアクションが必要になる場合があります。見つからないファイルやメモリ不足についてはどうですか?
注意深く分離されたすべての抽象化レイヤーを介したエラー処理リーク。最低レベルのエラーは、GUIメッセージでユーザーに通知する必要がある場合があります。これから何をするかについて、ユーザーの決定が必要になる場合があります。ロギングが必要な場合があります。別の部分でリカバリ操作が必要になる場合があります。データベースまたはネットワーク接続を開きます。等。
状態はどうですか?状態変更を呼び出すオブジェクトのメソッドを呼び出すと、エラーがスローされます。
エラー処理が最初から厳密に設計され、その結果すべての例で使用されている学習者向けの本を1つだけ見せてください。これが教育的POVから当てはまるかどうかは別の問題ですが、多くの場合、エラー処理が最初または2番目または3番目に考えれば十分です。
例外を次のように考えたい...
それらは自然に書かれる傾向がある方法でコードを書くことを可能にします
コードを書くとき、人間の脳が操作しなければならないことがいくつかあります。
これらの各弾丸は、ある程度の集中力を必要とし、集中力は限られた資源です。これが、プロジェクトに不慣れな人がリストの下の方にあるものを忘れることが多い理由です。彼らは悪い人ではありませんが、非常に多くのことが新しい可能性があるため(言語、プロジェクト、奇妙なエラーなど...)、彼らは単に他の弾丸に対処する能力がありません。
私はコードレビューを共有しましたが、この傾向は非常に明白です。経験の少ない人は、多くのものを忘れます。これらには、スタイルと、多くの場合十分なエラー処理が含まれます。人々がコードを機能させるのに苦労しているとき、エラー処理は彼らの頭の中にある傾向があります。時々戻って、ifステートメントをあちこちに追加することがありますが、安心して、たくさんのことを忘れてしまいます。
例外処理を使用すると、エラーがないかのようにロジックの主要部分を記述するコードを記述できます。関数呼び出しを行うと、次の行は単に前の行が成功したと見なすことができます。これには、はるかに読みやすいコードを生成するという追加のボーナスがあります。 「//エラー処理はわかりやすくするために省略されています」というコメントのあるコード例を含むAPIリファレンスを見たことがありますか?例を読みやすくするためにドキュメントのエラー処理を削除する場合、本番用コードで同じことを行い、それも読みやすくすることは素晴らしいことではありませんか?それこそが、例外によって実現できることです。
現在、この投稿を読んでいる少なくとも1人の人は、「pffttt some noobはコーディング方法がわかりません。エラー処理を忘れることはありません。」と言うでしょう。 a)あなたにとっては良いが、もっと重要なことはb)上で述べたように、コーディングにはあなたの脳が集中することが必要であり、周りを移動するのはそれほど多くない。それはすべての人に適用されます。コードが読みやすい場合は、集中力が低下します。大きなチャンクの周りにtry/catchを配置して、アプリケーションロジックを単純にコーディングできる場合は、集中力が低下します。これで、追加の容量を使用して、実際のジョブをはるかに速く実行するなど、より重要なことを実行できます。
例外がスローされてエラーコードが返されることの利点:
例外はより簡単に標準化されます。例外により自己文書化コードが作成され、ほとんどの人がすべてのプログラムをゼロから作成するわけではないため、さまざまな一般的なケースをカバーする例外タイプがすでに利用可能です。最も安価な解決策は既存の解決策であるため、これらの組み込みの例外タイプは、最初に作成されたものと同様の状況で使用されます。現在、InvalidOperationExceptionは、どのサードパーティライブラリからどの関数を呼び出そうとしているかに関係なく、オブジェクトの現在の状態(正確にはエラーメッセージに詳細が示されている)と矛盾する何かを実行しようとしたことを意味します。
戻りコードとは対照的です。 1つのメソッドでは、-1は「nullポインタエラー」であり、-2は「無効な操作エラー」である可能性があります。次のメソッドでは、「無効な操作」条件が最初にチェックされたため、エラーは戻りコード-1を受け取り、「nullポインター」は-2を受け取りました。数字は数字であり、数字をエラーに割り当てるための標準を自由に作成でき、他のすべての人も自由に作成できるため、多くの競合する標準につながります。
例外を使用すると、より楽観的になることができます。実際にステートメントを実行する前にcouldステートメントで問題が発生するすべてを悲観的にチェックする代わりに、単純にtryステートメントを実行し、catch anyそれによって生成された例外。エラーの原因を解決して再試行できる場合は、うまくいかない場合でも問題ありません。おそらく、あなたが事前に知っていれば、それについて何もできなかったでしょう。
したがって、例外ベースのエラー処理は、機能するまで機能すると仮定して、より効率的なコードを作成します。初期コストのプロセッサ時間を返すガードステートメント。したがって、これらは主に、前もって確認することの利点がコストを上回る場合に使用する必要があります。数クロックで入力が有効な出力を生成しないと判断できても、関数の本体が同じ結論に達するまで数秒かかる場合は、必ずガード節を挿入してください。ただし、問題が発生する可能性があり、そのほとんどが高コストの実行を必要とするダースがある場合、通常のプログラム実行の「ハッピーパス」は、単純にトライ/キャッチすると数倍速くなります。
私にとっての基本的な違いは、読み取りと書き込みです。
エラーコードの処理は、intentを散らかす傾向があります。ソースはがすべきことに焦点を当てているため、例外ベースのコードは通常、読みやすくなります何がmightではなく発生します。
OTOH、正確性分析のために、構築/破棄の順序、部分的に構築されたオブジェクト、ガベージコレクションなどの「無関係な」詳細について議論する必要があるため、多くの例外ベースのコードを書くのは困難です。
意味のある例外を生成すると、ソースも混乱する可能性があります。コードベースには、事実上すべてのコード行の後に2行または3行が続き、読み書きの例外が発生します。
意味のある例外をトップレベルに転送するために、それらはしばしば再パッケージされる必要があります。 「リビッカーセル」をクリックすると、 "%tmp%\ foo\file536821"の共有違反はよりもはるかに役に立ちません「エラー77」。
私は本当に好みはありません。どちらのオプションも同じように醜いです。さらに悪いことに、どちらが優れているかは、予測が困難な方法で、プロジェクトに大きく依存しているようです。私の経験では、低レベルのハードウェアの相互作用はエラーコード(クライアントである可能性があります...)でスムーズになり、例外を除いて低レベルのデータアクセスの方が優れています。
多くの言語の実装の問題はさておきましょう。例外の利点は、プログラムのすべての種類(理論上)の例外状態を処理するために集中化できることです。彼らが取り組む問題は、例外的なことが起こり、ほとんど予測できないということです。例外を除いて(理論的に)優れているのは、次の条件が満たされている限り、安全であることです。
例外ベースのコードは、エラー処理をメインプログラムフローの外に移動します。代わりに、エラー処理はオブジェクトデストラクタまたはキャッチコンストラクトに移動されます。
例外の最大の利点と最大の欠点は、エラー処理について考えさせられないであることです。例外を除いて、単純なコードを記述してデストラクタにその仕事を任せて、デストラクタが処理せず手動で実行する必要がある1つの小さなシナリオを忘れることがよくあります(これは明確に文書化されているため、完全にあなた自身の責任です)忘れる)。同じシナリオがエラーコードでも発生する可能性がありますが、エラーコードを含むコードを記述する場合は、常にマニュアル、ドキュメント、ソースを確認する必要があり、その1つの小さな条件を忘れることはほとんどありません。
はい、これは例外が正しいコードを書くことを時々難しくするかもしれないことを意味します。
エラーコードを使って悪いコードを書くのは簡単です。エラーコードを使って良いコードを書くのは難しいです。例外でまともなコードを書くのは簡単です(エラーが発生するとプログラムが終了するか、非常に高レベルのハンドラーによってキャッチされ、サイレントに渡されるのではなくDoomを警告する可能性があるため、例外を含む適切なコードを書くのは本当に難しいためですエラーが発生している場所から離れた場所でエラーを処理しています)。