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;
- what if the file doesn’t exist
- what if we don’t have read access
- what if the operating system is not in a sane state right now, out of memory for example
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.