8

What would a piece of code which "uses exceptions to control flow" look like? I've tried to find a direct C# example, but cannot. Why is it bad?

Thanks

GurdeepS
  • 65,107
  • 109
  • 251
  • 387
  • 1
    It was always my understanding that flow-controlling exceptions were generally all custom exceptions rather than the built-in things like FileNotFoundException - I think Kirien's answer is the most correct. But, it's bad because of the overhead required to handle all that. – overslacked Jul 15 '10 at 20:30
  • As a general rule of thumb, you should assume that throwing and handling an exception will take 1000 times as long as normal code. – dthorpe Jul 15 '10 at 20:45

9 Answers9

14

By definition, an exception is an occurrence which happens outside the normal flow of your software. A quick example off the top of my head is using a FileNotFoundException to see if a file exists or not.

try
{
    File.Open(@"c:\some nonexistent file.not here");
}
catch(FileNotFoundException)
{
    // do whatever logic is needed to create the file.
    ...
}
// proceed with the rest of your program.

In this case, you haven't used the File.Exists() method which achieves the same result but without the overhead of the exception.

Aside from the bad usage, there is overhead associated with an exception, populating the properties, creating the stack trace, etc.

ChrisF
  • 134,786
  • 31
  • 255
  • 325
Eric Olsson
  • 4,805
  • 32
  • 35
  • 9
    Ouch, not a good example. File.Exists() is *not* a reliable alternative on a multi-tasking operating system. – Hans Passant Jul 15 '10 at 20:32
  • True. Still, the contrast between relying on an exception vs. using a framework-provided method (pun intended) is a valid one. – Eric Olsson Jul 15 '10 at 20:38
  • @Malfist, "method" meaning (in the general English sense of the word) a means of accomplishing something vs. (in an object-oriented context) an operation supported by a class. Not my best work, but I take 'em where I can get 'em. :-) – Eric Olsson Jul 15 '10 at 21:02
  • Hans: If you just checked that a file exists, and then you go to open it and get a `FileNotFoundException`, would it be better to try to create a new file in its place, or throw an exception? – StriplingWarrior Jul 15 '10 at 21:09
  • What if I need to test if an object was created? Example: creating an object of a class you didn't write fails, which breaks a system/application, and I need to switch control flow to a method that rolls back certain operations to fix the system/application? In this case, the only choice I have is to catch it as an Exception and then switch flow. Right? – JohnZaj Sep 16 '11 at 17:22
11

It's roughly equivalent to a goto, except worse in terms of the word Exception, and with more overhead. You're telling the code to jump to the catch block:

bool worked;
try
{
    foreach (Item someItem in SomeItems)
    {
        if (someItem.SomeTestFailed()) throw new TestFailedException();
    }
    worked = true;
}
catch(TestFailedException testFailedEx)
{
    worked = false;
}
if (worked) // ... logic continues

As you can see, it's running some (made-up) tests; if they fail, an exception is thrown, and worked will be set to false.

Much easier to just update the bool worked directly, of course!

Hope that helps!

Kieren Johnstone
  • 41,277
  • 16
  • 94
  • 144
  • 2
    +1 While some of the other examples are also controlling flow, I think this is the nightmare scenario that's being warned against. – Greg Jul 15 '10 at 20:29
10

Bad

The below code catches an exception that could easily be avoided altogether. This makes the code more difficult to follow and typically incurs a performance cost as well.

int input1 = GetInput1();
int input2 = GetInput2();

try
{
    int result = input1 / input2;
    Output("{0} / {1} = {2}", input1, input2, result);
}
catch (OverflowException)
{
    Output("There was an overflow exception. Make sure input2 is not zero.");
}

Better

This code checks for a condition that would throw an exception, and corrects the situation before the error occurs. This way there is no exception at all. The code is more readable, and the performance is very likely to be better.

int input1 = GetInput1();
int input2 = GetInput2();

while (input2 == 0)
{
    Output("input2 must not be zero. Enter a new value.");
    input2 = GetInput2();
}

int result = input1 / input2;
Output("{0} / {1} = {2}", input1, input2, result);
Dan Tao
  • 125,917
  • 54
  • 300
  • 447
  • 4
    So it boils down to not using exceptions to perform logic to cure the exception and doing validation before hand? Therefore, exceptions should be used for something truly exceptional and not really possible to forsee. – GurdeepS Jul 15 '10 at 20:37
  • @dotnetdev: Typically, yes. Although sometimes this can be masked in other ways... – Reed Copsey Jul 15 '10 at 20:38
  • @dotnetdev: yes, although you usually do know what kind of exceptions are likely. If you call a fn that does something with files on disk, file exceptions are a possibility. etc. – dthorpe Jul 15 '10 at 20:42
5

Here's a common one:

public bool TryParseEnum<T>(string value, out T result)
{
    result = default(T);

    try
    {
        result = (T)Enum.Parse(typeof(T), value, true);
        return true;
    }
    catch
    {
        return false;
    }
}
Toby
  • 7,354
  • 3
  • 25
  • 26
  • That's a good example, especially when a good alternative - `TryParse` - already exists – ChrisF Jul 15 '10 at 20:25
  • +1 - I've seen this in code I've worked on - it got called loads of times during a sort and caused performance issues. Using TryParse like ChrisF says removed the performance issue. However, I believe this was the only way pre .NET 2.0. – Alex Humphrey Jul 15 '10 at 20:39
2

Probably the grossest violation I've ever seen:

// I haz an array...
public int ArrayCount(object[] array)
{
    int count = 0;
    try
    {
        while (true)
        {
            var temp = array[count];
            count++;
        }
    }
    catch (IndexOutOfRangeException)
    {
        return count;
    }
}
Tesserex
  • 17,166
  • 5
  • 66
  • 106
2

I'm currently working with a 3rd party program that does this. They have a "cursor" interface (basically an IEnumerable alternative), where the only way to tell the program you're finished is to raise an exception. The code basically looks like:

// Just showing the relevant section
bool finished = false;

public bool IsFinished()
{
    return finished;
}

// Using something like:
// int index = 0;
// int count = 42;

public void NextRecord()
{
    if (finished)
        return;

    if (index >= count)
        throw new APIProgramSpecificException("End of cursor", WEIRD_CONSTANT);
    else
        ++index;
}

// Other methods to retrieve the current value

Needless to say, I hate the API - but its a good example of exceptions for flow control (and an insane way of working).

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
1

One example would be using exceptions to return a result from a recursive method:

public void Search(Node node, object data)
{
    if(node.Data.Equals(data))
    {
        throw new ResultException(node);
    }
    else
    {
        Search(node.LeftChild, data);
        Search(node.RightChild, data);
    }    
}

Doing something like this is a problem for several reasons.

  1. It's completely counter-intuitive. Exceptions are designed for exceptional cases. Something working as intended should (we hope) never be an exceptional scenario.
  2. You can't always rely on an exception being thrown and propagated to you. For example, if the exception-throwing code runs in a separate thread, you'll need some extra code to capture it.
  3. It is a potential performance problem. There is an overhead associated with exceptions and if you throw a lot of them, you might see a performance drop in your application.

There are a few more examples and some interesting discussion on this subject here.

Disclaimer: The code above is adapted from the first sample on that wiki page to turn it into C#.

Adam Lear
  • 38,111
  • 12
  • 81
  • 101
  • Well, what is the problem with this ? If `GetValueOfX` is a complex routine which calls a lot of functions, an each of them can potentially fail, you *may* have a use case for your code if you need a sensible default for `x`. – Alexandre C. Aug 12 '11 at 11:28
  • @Alex Thanks for your comment. I actually went ahead and completely rewritten my answer. [This answer](http://stackoverflow.com/questions/3259660/example-of-using-exceptions-to-control-flow/3259691#3259691) and also [this one](http://stackoverflow.com/questions/3259660/example-of-using-exceptions-to-control-flow/3259719#3259719) address your question to me, though. Hope that helps. – Adam Lear Aug 12 '11 at 15:23
  • Very well. Actually this idiom is quite common in scheme (using `call/cc`) and to a certain extent in C (using `setjmp`/`longjmp`). If C# had lightweight exception handling (eg. like C++ has), I would have found it acceptable. After all, you *do* want to jump up the call stack as soon as you find the right item. However, exceptions in C# carry a lot of debugging related state (eg. call trace), so this is frowned upon. – Alexandre C. Aug 12 '11 at 15:40
1

I'm not fond of C# but you can see some similarities between try-catch-finally statements and normal control flow statements if-then-else.

Think about that whenever you throw an exception you force your control to be passed to the catch clause. So if you have

if (doSomething() == BAD) 
{
  //recover or whatever
}

You can easily think of it in terms of try-catch:

try
{
  doSomething();
}
catch (Exception e)
{
  //recover or do whatever
}

The powerful thing about exception is that you don't have to be in the same body to alter the flow of the program, you can throw an exception whenever you want with the guarantee that control flow will suddently diverge and reach the catch clause. This is powerful but dangerous at the same time since you could have done actions that need some backup at the end, that's why the finally statement exists.

In addition you can model also a while statement without effectively using the condition of it:

while (!finished)
{
  //do whatever
}

can become

try
{
  while (true)
  {
     doSomethingThatEventuallyWillThrowAnException();
  }
}
catch (Exception e)
{
  //loop finished
}
Jack
  • 131,802
  • 30
  • 241
  • 343
1

A module developed by a partner caused our application to take a very long time to load. On closer examination, the module was looking for a config file at app startup. This by itself was not too objectionable, but the way in which it was doing it was outrageously bad:

For every file in the app directory, it opened the file and tried to parse it as XML. If a file threw an exception (because it wasn't XML), it caught the exception, squelched it, and tried the next file!

When the partner tested this module, they only had 3 files in the app directory. The bonehead config file search didn't have a noticeable effect on the test app startup. When we added it to our application, there were 100's of files in the app directory, and the app froze for nearly a minute at startup.

To add salt to the wound, the name of the config file the module was searching for was predetermined and constant. There was no need for a file search of any kind.

Genius has its limits. Stupidity is unbounded.

dthorpe
  • 35,318
  • 5
  • 75
  • 119