The Life of a Programmer

The Necessity of Exceptions

Exceptions often get a bad rap. They are called ineffective, inefficient, and hard to work with. In a way such accusations aren’t unfair, as most languages have totally screwed up their implementation. Hatred of exceptions is mainly a backlash against poor syntax, bad compiler implementation, and unmanageable exception hierarchies. Despite these stumbling blocks, exceptions are still far better than the alternative: no exceptions.

By Example

Consider the following pseudo-code. The function “sum_file” sums up the integers on each line in the file.

[sourcecode language=”cpp”]
int sum_file( string name )
{
File in = open( name );

int sum = 0;
while( !end_of_file(in) )
{
sum += parse_int( get_line(in) );
}

return sum;
}
[/sourcecode]

In this form the intent and flow of the function is very clear. You don’t have to study it for long to see what is happening. In particular there is no error handling cluttering up the function. We have decided that if an exception occurs the caller will have to deal with it. This is reasonable since we would have no valid return value if that were to happen.

What would this look like if we didn’t have exceptions? What if we had to handle errors individually and find a way to propagate them to the caller. This code is below. Here we’ll even assume that we have good language support for multiple return values: a return tuple where the first value is the real result, and the second the error value.

[sourcecode language=”cpp”]
int,error_code sum_file( string name )
{
error_code err;
File in;
in,err = open( name );
if( err )
return 0,err;

int sum = 0;
while( true )
{
bool eof;
eof,err = end_of_file(in);
if( err )
return 0,err;
if( eof )
break;

string line;
line,err = get_line(in);
if( err )
return 0,err;

int value;
value,err = parse_int(line);
if( err )
return 0,err;

sum += value;
}

return (sum, okay);
}
[/sourcecode]

Yuck! The purpose of our code is entirely lost in error handling. It is now very difficult to see what the main purpose of the code is. Even worse, we’re forced to return a result even if one isn’t available, so we return 0. If the caller forgets to check the error code they will simply use 0, which is most likely not what is intended. It is very easy to ignore return codes.

You may protest that such a level of error handling is not required. In the wild you’ll indeed find functions like the above which lack several of the checks. But you’d be wrong and those functions would be broken. Every check for an error in the above is necessary to catch some kind of error condition. Consider even “end_of_file”, though rare it can actually fail. What if the USB stick was removed in the middle of your function and the file just doesn’t exist anymore? If the function simply returns “true” instead of an error, you won’t sum up all the values and thus you’ll have a defect. Even in the above we may be missing a check on the “+=” operator. We’re just assuming integer addition can’t fail, but in some languages it can, and instead of a raw integer we might be using a BigDecimal type instead.

Contextual Information

In the case of error we also have very little ability to report contextual information to the caller. All we can report is a simple error code. Anybody who has done work with C system functions knows how limiting this can be. I even had a case recently with “dlopen” simply returning a “file not found” code. That wasn’t actually the problem, but it didn’t really have a way to report anything else. In the above code possibly the caller will just further return the same error code, and every step up we take we lose contextual information about what actually happened.

With an exception the lower function in the chain has the ability to better explain what happened. For example, if the USB stick was removed, the thrown exception can actually contain information indicating that error and possibly what mount point it had, or the name of the file. While a single bit of extra information isn’t a lot, it can often mean a great deal while debugging.

Ideally each function in the chain could add a little bit of information to provide a full context. This would be a tagged exception system. Unfortunately I don’t know of any natural support of such a system. I only know boost exceptions in C++ which allows you to use tagged exceptions — truly an excellent approach once you understand it. Not having this system greatly reduces the ability of an exception to report context. More on this in a future article.

The Way Forward

This article is not intended to be a thorough exploration of exceptions. The examples are simply here to show that exceptions allow much cleaner code than a lack of exceptions. Code which cannot rely on exceptions ends up bloated and obscuring all intent with an excessive amount of error handling. Clearly, exceptions are a very powerful tool and should be considered a standard feature in any new language.

Don’t misunderstand this to mean that exceptions should always be used. There are certainly domains where an exception flow is not desired. But those cases are not the norm. Exactly when you shouldn’t use exceptions is a difficult subject; too much to cover here. And perhaps these cases only exist because current languages have implemented exceptions very poorly.

Yes, you read that correctly. Despite my assertion that languages need exceptions I maintain that most languages have gotten them terribly wrong. We should still use them, but we should also be looking at a way to improve them. Boost’s C++ tagged exceptions looks to be a very promising approach. In time I’ll look further at what went wrong, and how to improve it.

Followup

There appears to be some debate that I am simply not doing any error handling at all in the first version (the one where I claim exceptions do the work). That is part of the point I am making, that function has no way to deal with an exception therefore just passes it up to the caller.

Obviously somebody up the chain will have to deal with it. Here is a possible caller who wishes to deal with it directly, just to show the above functions are in fact complete.

[sourcecode language=”cpp”]
//with exceptions
try {
int sum = sum_file( a_file );
printf( "The Sum is %d", sum );
} catch( Exception ex ) {
printf( "Sorry, it failed: %s", ex.to_string() );
}

//with error codes
int sum, err;
sum, err = sum_file( a_file );
if( !err )
printf( "The Sum is %d", sum );
else
printf( "Sorry, it failed: %s", code_to_text( err ) );
[/sourcecode]

Please join me on Discord to discuss, or ping me on Mastadon.

The Necessity of Exceptions

A Harmony of People. Code That Runs the World. And the Individual Behind the Keyboard.

Mailing List

Signup to my mailing list to get notified of each article I publish.

Recent Posts