2009年1月20日編集:
マネージコードのパフォーマンスの向上に関するこのMSDNの記事を読んでいたところ、この部分でこの質問を思い出しました。
例外をスローすることによるパフォーマンスコストは重要です。構造化例外処理はエラー状態を処理するための推奨される方法ですが、エラー状態が発生する例外的な状況でのみ例外を使用するようにしてください。通常の制御フローに例外を使用しないでください。
もちろん、これは.NET専用であり、特に高性能アプリケーションを開発している人(私のような)を対象としています。ですから、それは明らかに普遍的な真実ではありません。それでも、私たち.NET開発者はたくさんいるので、注目に値するものだと感じました。
編集:
OK、まず第一に、1つのことをまっすぐにしましょう:私はパフォーマンスの質問について誰かとの戦いを選ぶつもりはありません。一般的に、実際、私は時期尚早の最適化が罪であると信じている人々に同意する傾向があります。ただし、2つの点を指摘しておきます。
ポスターは、例外は控えめに使用されるべきであるという一般通念の背後にある客観的な論理的根拠を求めています。読みやすさと適切なデザインについて、私たちが望むすべてについて話し合うことができます。しかし、これらはどちらの側でも議論する準備ができている人々の主観的な問題です。ポスターはこれを知っていると思います。実際のところ、例外を使用してプログラムフローを制御することは、多くの場合、非効率的な方法です。いいえ、常にではありませんが、多くの場合。これが、赤身の肉を食べたりワインを控えめに飲んだりするのと同じように、例外を控えめに使用することが合理的なアドバイスである理由です。
正当な理由のない最適化と効率的なコードの記述には違いがあります。これに対する当然の結果として、最適化されていなくても堅牢なものを書くことと、単に非効率的なものを書くことには違いがあります。例外処理のようなことについて議論するとき、彼らは根本的に異なることを話し合っているので、実際にはお互いを超えて話しているだけだと思うことがあります。
私のポイントを説明するために、次のC#コード例を検討してください。
例1:無効なユーザー入力の検出
これは、私が例外の乱用と呼ぶものの例です。
int value = -1;
string input = GetInput();
bool inputChecksOut = false;
while (!inputChecksOut) {
try {
value = int.Parse(input);
inputChecksOut = true;
} catch (FormatException) {
input = GetInput();
}
}
このコードは、私にはばかげています。もちろん動作します。誰もそれについて議論していません。しかし、それは次のようなものでなければなりません:
int value = -1;
string input = GetInput();
while (!int.TryParse(input, out value)) {
input = GetInput();
}
例2:ファイルの存在を確認する
このシナリオは実際には非常に一般的だと思います。ファイルI / Oを処理するため、多くの人にとっては確かにはるかに「受け入れられる」ように思われます。
string text = null;
string path = GetInput();
bool inputChecksOut = false;
while (!inputChecksOut) {
try {
using (FileStream fs = new FileStream(path, FileMode.Open)) {
using (StreamReader sr = new StreamReader(fs)) {
text = sr.ReadToEnd();
}
}
inputChecksOut = true;
} catch (FileNotFoundException) {
path = GetInput();
}
}
これは十分に合理的なようですよね?ファイルを開こうとしています。そこにない場合は、その例外をキャッチして別のファイルを開こうとします...何が問題なのですか?
本当に何もありません。ただし、例外をスローしないこの代替案を検討してください。
string text = null;
string path = GetInput();
while (!File.Exists(path)) path = GetInput();
using (FileStream fs = new FileStream(path, FileMode.Open)) {
using (StreamReader sr = new StreamReader(fs)) {
text = sr.ReadToEnd();
}
}
もちろん、これら2つのアプローチのパフォーマンスが実際に同じである場合、これは実際には純粋に教義上の問題になります。それでは、見てみましょう。最初のコード例では、10000個のランダムな文字列のリストを作成しましたが、いずれも適切な整数を表していないため、最後に有効な整数文字列を追加しました。上記の両方のアプローチを使用して、これらは私の結果でした:
使用try
/catch
ブロック:25.455秒
使用int.TryParse
:1.637ミリ秒
2番目の例では、基本的に同じことを行いました。10000個のランダムな文字列のリストを作成しましたが、いずれも有効なパスではなく、最後に有効なパスを追加しました。結果は次のとおりです。
使用try
/catch
ブロック:29.989秒
使用File.Exists
:22.820ミリ秒
多くの人がこれに「そうですね、10,000の例外をスローしてキャッチすることは非常に非現実的です。これは、結果を誇張します」と答えます。もちろんそうです。1つの例外をスローすることと、自分で不正な入力を処理することの違いは、ユーザーには気付かれません。例外の使用は、これら2つのケースでは、同じように読みやすい代替アプローチよりも1,000倍から10,000倍以上遅くなるという事実が残っています。
そのため、GetNine()
以下の方法の例を含めました。それが耐えられないほど遅い、または容認できないほど遅いということではありません。本来よりも遅いということです...理由はありません。
繰り返しますが、これらは2つの例にすぎません。うちもちろん例外を使用してのパフォーマンスヒットはこの厳しい(;すべての後に、それは実装に依存しないパベルの権利)でない時があります。私が言っているのは、事実に直面しましょう。上記のような場合、例外をスローしてキャッチすることは、GetNine()
;に類似しています。これは、簡単に改善できることを行うための非効率的な方法です。。
あなたは、これが理由を知らずに誰もが時流に乗ったような状況の1つであるかのように論理的根拠を求めています。しかし実際には答えは明白であり、あなたはすでにそれを知っていると思います。例外処理には、ひどいパフォーマンスがあります。
OK、特にビジネスシナリオには問題ないかもしれませんが、比較的言えば、例外をスロー/キャッチすると、多くの場合、必要以上のオーバーヘッドが発生します。あなたはそれを知っています、私はそれを知っています:ほとんどの場合、プログラムの流れを制御するために例外を使用しているなら、あなたはただ遅いコードを書いているだけです。
あなたは尋ねたほうがいいかもしれません:なぜこのコードは悪いのですか?
private int GetNine() {
for (int i = 0; i < 10; i++) {
if (i == 9) return i;
}
}
この関数のプロファイルを作成すると、通常のビジネスアプリケーションで非常に高速に実行されることがわかると思います。それは、それがはるかに良くできることを達成するためのひどく非効率的な方法であるという事実を変えません。
それが、例外的な「虐待」について話すときの人々の意味です。