The OpenD Programming Language

Errors

I came, I coded, I crashed. $(cite Julius C'ster)

$(HTMLTAG3 img, src="images/dman-error.jpg" border="0" align="right" alt="Erroneous D-Man" height="200")

All programs have to deal with errors. Errors are unexpected conditions that are not part of the normal operation of a program. Examples of common errors are:

  • Out of memory.
  • Out of disk space.
  • Invalid file name.
  • Attempting to write to a read-only file.
  • Attempting to read a non-existent file.
  • Requesting a system service that is not supported.

The Error Handling Problem

The traditional C way of detecting and reporting errors is not traditional, it is ad-hoc and varies from function to function, including:

  • Returning a NULL pointer.
  • Returning a 0 value.
  • Returning a non-zero error code.
  • Requiring errno to be checked.
  • Requiring that a function be called to check if the previous function failed.

To deal with these possible errors, tedious error handling code must be added to each function call. If an error happened, code must be written to recover from the error, and the error must be reported to the user in some user friendly fashion. If an error cannot be handled locally, it must be explicitly propagated back to its caller. The long list of errno values needs to be converted into appropriate text to be displayed. Adding all the code to do this can consume a large part of the time spent coding a project - and still, if a new errno value is added to the runtime system, the old code can not properly display a meaningful error message.

Good error handling code tends to clutter up what otherwise would be a neat and clean looking implementation.

Even worse, good error handling code is itself error prone, tends to be the least tested (and therefore buggy) part of the project, and is frequently simply omitted. The end result is likely a "blue screen of death" as the program failed to deal with some unanticipated error.

Quick and dirty programs are not worth writing tedious error handling code for, and so such utilities tend to be like using a table saw with no blade guards.

What's needed is an error handling philosophy and methodology such that:

  • It is standardized - consistent usage makes it more useful.
  • The result is reasonable even if the programmer fails to check for errors.
  • Old code can be reused with new code without having to modify the old code to be compatible with new error types.
  • No errors get inadvertently ignored.
  • 'Quick and dirty' utilities can be written that still correctly handle errors.
  • It is easy to make the error handling source code look good.

The D Error Handling Solution

Let's first make some observations and assumptions about errors:

  • Errors are not part of the normal flow of a program. Errors are exceptional, unusual, and unexpected.
  • Because errors are unusual, execution of error handling code is not performance critical.
  • The normal flow of program logic is performance critical.
  • All errors must be dealt with in some way, either by code explicitly written to handle them, or by some system default handling.
  • The code that detects an error knows more about the error than the code that must recover from the error.

The solution is to use exception handling to report errors. All errors are objects derived from the abstract class Error. Error has a pure virtual function called toString() which produces a string with a human readable description of the error.

If code detects an error like "out of memory," then an Error is thrown with a message saying "Out of memory". The function call stack is unwound, looking for a handler for the Error. Finally blocks are executed as the stack is unwound. If an error handler is found, execution resumes there. If not, the default Error handler is run, which displays the message and terminates the program.

How does this meet our criteria?

It is standardized - consistent usage makes it more useful.
This is the D way, and is used consistently in the D runtime library and examples.
The result is reasonable result even if the programmer fails to check for errors.
If no catch handlers are there for the errors, then the program gracefully exits through the default error handler with an appropriate message.
Old code can be reused with new code without having to modify the old code to be compatible with new error types.
Old code can decide to catch all errors, or only specific ones, propagating the rest upwards. In any case, there is no more need to correlate error numbers with messages, the correct message is always supplied.
No errors get inadvertently ignored.
Error exceptions get handled one way or another. There is nothing like a NULL pointer return indicating an error, followed by trying to use that NULL pointer.
'Quick and dirty' utilities can be written that still correctly handle errors.
Quick and dirty code need not write any error handling code at all, and don't need to check for errors. The errors will be caught, an appropriate message displayed, and the program gracefully shut down all by default.
It is easy to make the error handling source code look good.
The try/catch/finally statements look a lot nicer than endless if (error) goto errorhandler; statements.

How does this meet our assumptions about errors?

Errors are not part of the normal flow of a program. Errors are exceptional, unusual, and unexpected.
D exception handling fits right in with that.
Because errors are unusual, execution of error handling code is not performance critical.
Exception handling stack unwinding is a relatively slow process.
The normal flow of program logic is performance critical.
Since the normal flow code does not have to check every function call for error returns, it can be realistically faster to use exception handling for the errors.
All errors must be dealt with in some way, either by code explicitly written to handle them, or by some system default handling.
If there's no handler for a particular error, it is handled by the runtime library default handler. If an error is ignored, it is because the programmer specifically added code to ignore an error, which presumably means it was intentional.
The code that detects an error knows more about the error than the code that must recover from the error.
There is no more need to translate error codes into human readable strings, the correct string is generated by the error detection code, not the error recovery code. This also leads to consistent error messages for the same error between applications.

Using exceptions to handle errors leads to another issue - how to write exception safe programs. [exception-safe.html, Here's how].

$(COMMENT $(NUMBERED_LIST

* Programmers, especially inexperienced ones, tend to neglect to test for the special error return value. Their code just assumed the function completed successfully. This leads to erratic and unpredictable behavior if the function did fail.

* How each function deals with errors tends to be unique and inconsistent, leading to more unintended programmatic errors.

* How the error gets reported to the user tends to vary arbitrarily from one program to the next and one error case to the next.

* Dealing with error cases causes tedious and error-prone code to be written, and so can consume much programming effort.

* Error handling logic tends to be buggy because it rarely gets tested by the test team.

* Functions that should have clean interfaces wind up cluttering them with error return parameters and cases.

More...

) )

traits, Traits, unittest, Unit Tests