Saturday, March 10, 2018

The Exception to the Rule

Exceptions in Programming Languages

I learned a neat little construct recently: exceptions!

Okay, so it's been 13 years since I first heard about them, but it wasn't until I read this StackOverflow question that I felt like I actually understood them. I finally felt like I knew when to use exceptions and when not to use exceptions.

I don't want to focus on the details of exceptions, but perhaps a little crash course is appropriate.

What are exceptions?

Exceptions can be thrown and caught. When you "throw" an exception, your program stops dead in its tracks. It picks back up wherever you have chosen to "catch" the exception. There's a little behind-the-scenes stuff too, but that's the gist of it.

    try {
        cout << "The program gets to here... ";
        throw exception();
        cout << "... and it never gets here." << endl;
    catch (exception & e) {
        cout << "... and it picks up here." << endl;

If you run this code, it will output the following:

The program gets to here... ... and it picks up here.

It works, but is this good code? Are there better ways to do the same thing?

Pros and Cons of Exceptions

Exceptions are slow. Yes, they can be terribly slow! It is much faster to use conditionals instead of exceptions. (Or, in the case of the program above, just drop the dumb lines that are only there for the example's sake.) Exceptions are just plain slow!

However, not having an exception is faster than conditional execution (like if, while, etc.). If you /can/ throw an exception but you don't, that's super fast! They are only slow when they are actually used.

That's the gist of the answers on the StackOverflow question I linked earlier.

When do you use exceptions?

After meditating on that StackOverflow question and its answers, I feel like I finally know when to use exceptions. Bare with me, and I'll explain.

A few givens:
  • Exceptions are slow, so you don't want to use them.
  • Having exceptions but not using them is fast.
  • Conditionals are almost as fast as unused exceptions. *
* at least in the languages I cared to research last weekend.

My conclusion is that exceptions should never happen. They should never be used. They should never be thrown. They should never be caught.

The exception to the rule "exceptions should never happen" is "an exception happens".

Thoroughly confused...

I get up and go to work every morning at 7:50am. I do this every weekday. This is normal to me.

Six months ago, I got in my car at 7:50am, and it wouldn't start. This is an exception! I had to change my plans because of this unexpected event.

I have come to the realization that exceptions in programming are exactly the same thing. In normal operation, how should the program work? Use exceptions for exceptions to this rule of how things should work.
  1. Use a conditional when: It's something that needs checked or just happens sometimes. Your program is coded to handle anything amiss.
  2. Use an exception when: Uh oh! Something happened, and the program wasn't built for this!
In short, exceptions never happen, and the exception to this rule is when they happen.

I need an example.

Perfect! Let's say you've got a text editor. Usually, you'll be trying to open valid files, but sometimes the user tells you to open a file that doesn't exist.

bool openFile(const char * filename)
    this->f = fopen(filename);
    if (!f)
        return false;
    return true;

This is a member function of our TextEditor class. You try to open a file. If it doesn't exist, then no harm done. The program keeps on chugging away as usual, probably prompting them for a valid file name.

Now let's look at a different case. What if you are trying to open a configuration file for your program?

    FILE *f = fopen("myConfig");
    if (!f)
        throw exception("Config not found!");

It's different. We expect the config file to exist. If it doesn't, we just can't continue. This is an exception.

We will, of course, catch the exception somewhere. If we're good, we might try to recreate the config and try again. But, for the moment, we've hit an unexpected obstacle that needs fixed before we can continue normal operation.