EDIT 11/20/2009:
I was just reading this MSDN article on improving managed code performance and this part reminded me of this question:
The performance cost of throwing an exception is significant. Although structured exception handling is the recommended way of handling error conditions, make sure you use exceptions only in exceptional circumstances when error conditions occur. Do not use exceptions for regular control flow.
Of course, this is only for .NET, and it's also directed specifically at those developing high-performance applications (like myself); so it's obviously not a universal truth. Still, there are a lot of us .NET developers out there, so I felt it was worth noting.
EDIT:
OK, first of all, let's get one thing straight: I have no intention of picking a fight with anyone over the performance question. In general, in fact, I am inclined to agree with those who believe premature optimization is a sin. However, let me just make two points:
The poster is asking for an objective rationale behind the conventional wisdom that exceptions should be used sparingly. We can discuss readability and proper design all we want; but these are subjective matters with people ready to argue on either side. I think the poster is aware of this. The fact is that using exceptions to control program flow is often an inefficient way of doing things. No, not always, but often. This is why it's reasonable advice to use exceptions sparingly, just like it's good advice to eat red meat or drink wine sparingly.
There is a difference between optimizing for no good reason and writing efficient code. The corollary to this is that there's a difference between writing something that is robust, if not optimized, and something that is just plain inefficient. Sometimes I think when people argue over things like exception handling they're really just talking past each other, because they are discussing fundamentally different things.
To illustrate my point, consider the following C# code examples.
Example 1: Detecting invalid user input
This is an example of what I'd call exception abuse.
int value = -1;
string input = GetInput();
bool inputChecksOut = false;
while (!inputChecksOut) {
try {
value = int.Parse(input);
inputChecksOut = true;
} catch (FormatException) {
input = GetInput();
}
}
This code is, to me, ridiculous. Of course it works. No one's arguing with that. But it should be something like:
int value = -1;
string input = GetInput();
while (!int.TryParse(input, out value)) {
input = GetInput();
}
Example 2: Checking for the existence of a file
I think this scenario is actually very common. It certainly seems a lot more "acceptable" to a lot of people, since it deals with file 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();
}
}
This seems reasonable enough, right? We're trying to open a file; if it's not there, we catch that exception and try to open a different file... What's wrong with that?
Nothing, really. But consider this alternative, which doesn't throw any exceptions:
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();
}
}
Of course, if the performance of these two approaches were actually the same, this really would be purely a doctrinal issue. So, let's take a look. For the first code example, I made a list of 10000 random strings, none of which represented a proper integer, and then added a valid integer string onto the very end. Using both of the above approaches, these were my results:
Using try
/catch
block: 25.455 seconds
Using int.TryParse
: 1.637 milliseconds
For the second example, I did basically the same thing: made a list of 10000 random strings, none of which was a valid path, then added a valid path onto the very end. These were the results:
Using try
/catch
block: 29.989 seconds
Using File.Exists
: 22.820 milliseconds
A lot of people would respond to this by saying, "Yeah, well, throwing and catching 10,000 exceptions is extremely unrealistic; this exaggerates the results." Of course it does. The difference between throwing one exception and handling bad input on your own is not going to be noticeable to the user. The fact remains that using exceptions is, in these two case, from 1,000 to over 10,000 times slower than the alternative approaches that are just as readable -- if not more so.
That's why I included the example of the GetNine()
method below. It isn't that it's intolerably slow or unacceptably slow; it's that it's slower than it should be... for no good reason.
Again, these are just two examples. Of course there will be times when the performance hit of using exceptions is not this severe (Pavel's right; after all, it does depend on the implementation). All I'm saying is: let's face the facts, guys -- in cases like the one above, throwing and catching an exception is analogous to GetNine()
; it's just an inefficient way of doing something that could easily be done better.
You are asking for a rationale as if this is one of those situations where everyone's jumped on a bandwagon without knowing why. But in fact the answer is obvious, and I think you know it already. Exception handling has horrendous performance.
OK, maybe it's fine for your particularly business scenario, but relatively speaking, throwing/catching an exception introduces way more overhead than is necessary in many, many cases. You know it, I know it: most of the time, if you're using exceptions to control program flow, you're just writing slow code.
You might as well ask: why is this code bad?
private int GetNine() {
for (int i = 0; i < 10; i++) {
if (i == 9) return i;
}
}
I would bet that if you profiled this function you'd find it performs quite acceptably fast for your typical business application. That doesn't change the fact that it's a horribly inefficient way of accomplishing something that could be done a lot better.
That's what people mean when they talk about exception "abuse."