The Developer’s Cry

Yet another blog by a hobbyist programmer

Exception-oriented programming

Like many others, I learned to program in BASIC in my teens. Later Pascal, C and assembly language. These are all procedural languages. Then came Java, C++, Python, and much later also Objective-C. These are object-oriented languages. The object-oriented programming languages typically support exceptions, while the others don’t. Exceptions are kind of necessary because constructors and destructors do not have return values. Exceptions alter the program flow, but may also alter the way in which we write software. To make this apparent, I will give examples in C and Python. Of course, the same applies to other programming languages.

Suppose we want to read in a text file, line by line. First you open() the file for reading, next read lines. But the open() call may fail in various ways;

A proper program always checks the return value for errors when calling a C library function. The special variable errno indicates exactly what error occurred.

errno = 0;
FILE *f = fopen(filename, "r");
if (f == NULL) {
    perror("fopen() failed");
    return -1;
}
while(fgets(buf, sizeof(buf), f) != NULL) {
    parse(buf);
}
if (errno != 0) {
    perror("fgets() failed");
    fclose(f);
    return 0;
}
if (fclose(f) != 0) {
    perror("fclose() failed");
    return -1;
}
return 0;

As you can see, this bit of code is littered with if statements. It makes the code less readable and harder to maintain. It is prone to errors, because if resources need to be released (like closing that file descriptor), it needs to be done in each and every possible code path.

How different things are in a language with exceptions. Essentially the same procedure, now in Python:

try:
    f = open(filename)
    with f:
        while True:
            line = f.readline()
            parse(line)
except IOError as err:
    fatal('error:' + err.strerror)

Here, the error detection and handling code is completely separate from the code path that does the actual work; the code path that matters most is a clean sequence of statements, uninterrupted by error checking.
The code path that does error is an unexpected condition, and so it bails out with fatal(). Note that you could just as well return False or raise another exception.

This was just a simple example, but imagine having a large procedure that involves taking a dozen steps, that each might deliver some kind of error. With exceptions, you can easily write the preferred (successful) code path, and add robustness later by catching any exceptions.
It’s also possible to catch exceptions at a higher level, outside the function that caused an exception to be raised.

Can we adopt the same style in C++? Yes.