Exceptions

This may be a naive question from an unlearned youngster, but I'm wondering if there has been research into alternatives to exceptions as a means of detecting and dealing with runtime errors.

The practice of returning error codes is usually discouraged in favor of exceptions for good reasons. Exceptions are sure to be caught somewhere in some way, or else they can cause the program to die with perhaps a moderately useful error message, trigger a debugger in a structured way, etc. Exceptions that aren't being handled can be checked for (using a language with a type system that can do that sort of thing, anyway), and that's often a handy thing to be able to do. Error codes, on the other hand, are very arbitrary, can have overlapping ID numbers, don't carry extra information, etc.

Exceptions, though, seem to carry with them their own baggage and limitations. Sometimes that baggage is nothing more than perceived syntactic "ugliness," but I think there's a little more to it than that. Perhaps I'm just not well versed enough in PL theory (and perhaps real life experience) to really "get" it, but it seems slightly wrong/ugly (to me) that an exception usually takes you out of the normal flow of code and into a kind of mystical land of uncertainty where an error in one situation may lead to being caught in routine X, but the same error in another situation is caught in routine Y - leaving all of the state in between somewhat ambiguous. I realize that many languages have mechanisms for handling those situations (like "finally" blocks to clear up things before you leave the scope for good), but that all feels a bit like the tedium associated with manual memory management rather than the live-and-let-live approach of garbage collection.

For example, say a block of code is writing to an output file and it runs out of disk space. The write() call causes an exception. This exception may not be handled in the routine that was doing the writing on account that the original author may have figured it'd be best for the higher-level logic to decide what to do. Okay - so this exception is thrown and it's a low-level "disk full" kind of error. It ends up getting caught way high up in the logic tree where the programmer originally had called something like: myObject.save(). At that stage in the logic, the programmer should be thinking abstractly about his/her problem and not be so much concerned that the object is being saved to disk, a database, or whatever. But it turns out that now there has to be code there to catch rather specific errors like "disk full." Obviously that write() exception could have been caught earlier and transformed into something more general, but how far are you willing to go? When do you break down and do a catch( * ) { throw Exception('something bad happened') }? On top of all that, where does the application decide on how to proceed? Should it erase the output file on account that it's likely corrupted now and in a half-finished state? Or should it leave it there as-is? What if the application needs to ask the user? How do you pass the answer back down the chain from the UI to the business logic layer that does the actual work? (Note: This may be a badly engineered example, but I think it's a reasonably realistic one that reflects the kinds of problems that crop up in real code that's not been well engineered but can't be thrown away and rewritten from scratch due to time/budget/political pressures.)

Anyway, I'm interested in hearing about other approaches to handling runtime errors that may have popped up over the years and, perhaps, information as to why exceptions are the apparently preferred method of doing things by the mainstream (and even not-so-mainstream) languages. Is it simply because there hasn't been any better alternatives? Or are there much better reasons which I am simply missing the point of. :-)

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

My take: handling 'disk

My take: handling 'disk full' errors is very hard, be it by exception or by return code, so this isn't really an exception problem.

Maybe you'll find this article interesting:
http://digitalmars.com/d/exception-safe.html

The scope(exit) statement is much "prettier" than the finally statement..

Interesting.

It doesn't appear to really be a different way of handling errors so much as a nice syntactic structure that hides some of the "ugly" of the usual try/catch pattern. I like it, though. It seems I'm more often questioning why I haven't yet sat down and used D on a project as opposed to my usual default (due to familiarity) language of C++. (Then again, I hang out on LtU mostly because I have aspirations for building my own uber-language-of-extreme-awesomeness which I'd use for everything, of course. Maybe I should write it in D... :)

Exceptions, reactive programming, CPS.

The concept of exceptions is similar to reactive programming where a reaction is attached to a signal. Traditional mainstream languages have one path of 'normal' execution and exceptions, which in reality are paths of execution themselves. Reactive programming feels more natural in this respect, because there is no 'main' path and secondary paths: all control flow outcomes have equal weight.

Exceptions look similar to CPS. For example, the following Java code:

int foo(int i) throws Ex1, Ex2 {
    if (i) throw new Ex1(); else throw new Ex2();
    return i;
}

int bar(int i) throws Ex2 {
    try {
        return foo(i);
    }
    catch (Ex1 ex) {
        println("error 1");
    }
}

seems similar to using CPS:

fun foo i ex1 ex2 = if i != 0 then ex1 else ex2

fun bar i ex2 = 
local
    fun ex1 = print 'error 1'
in
    foo i ex1 ex2
end

In imperative languages, RAII is my preferred why of handling cleanup, because I can write the resource clean up function when I create the resource.

As for errors like 'disk full', solutions like pausing the application in order to make some free space or mounting a new drive seems to be the appropriate way to handle the error, if continue writing of course is desirable.

Common Lisp has a slightly

Common Lisp has a slightly more general system (its condition system) that is used for exceptions. There's a paper here: http://www.nhplace.com/kent/Papers/Condition-Handling-2001.html. Maybe it can give you some ideas.

Conditions

I had no idea about the condition system and am quite fascinated by it. It seems like a very logical way to approach the problem - even allowing for reasonably elegant handling of things like running out of disk space. I also ran across this LtU discussion about Common Lisp exceptions/conditions which was very enlightening.

So far I've seen a lot of what I might call praise for the Lisp condition system. Why wasn't it adopted by other languages? In my experience working with the usual "mainstream" languages, exceptions have always been of the try/catch stack-unwinding form which felt slightly broken to me from the beginning - but I was never quite able to put my finger on why.

Checked Exceptions

I think a previous discussion on Checked Exceptions is relevant. The Parnas link given in that thread for the paper Response To Undesired Events In Software Systems is "The grandfather paper, from which modern exception systems are descended".

Interesting links - thanks!

Interesting links - thanks!

You're asking a few

You're asking a few questions there that have nothing to do with exceptions per se--handling of errors for example is an issue that has to be dealt with regardless of the error handling/detection method used. The answers to which are very dependant upon the program in question.

As to exceptions and where you should catch them, convert them, etc., this is my take: handle them as appropriate, but always convert (and chain) at layer/component/API boundaries. I find checked exceptions help me enforce this idiom, so I prefer them.

I was mostly asking about

I was mostly asking about available alternatives to exceptions as a structure/mechanism. Obviously errors have to be dealt with in some way - I'm curious about the various methods of doing so and why "exceptions" have come to be the de-facto standard. I may not have stated my case clearly enough. :-)

My example with the "disk full" error was just to help illustrate what I think are weaknesses of the model as it exists in relation to my experience. As in - it doesn't feel clean to me. But perhaps it can't be made clean?

My example with the "disk

My example with the "disk full" error was just to help illustrate what I think are weaknesses of the model as it exists in relation to my experience. As in - it doesn't feel clean to me. But perhaps it can't be made clean?

In my opinion a "natural" approach to errors is to use backtracking control. But this alternative is not supported by any major language. However it is used: Berkeley ROC , and Lewis as well as logic programming .

You have to be careful, though...

I agree that backtracking (and nondeterministic methods in general) are very nice ways of handling errors, when they work. But they don't always work.

The main thing you have to be careful of is that if your program has any side effects, then you must to be able to undo those effects before you can safely backtrack. Some side effects, such as mutating memory, can be undone. Haskell's STM system actually supports this idiom now -- you can specify runtime assertions that will trigger rollback if they fail. It's safe because the STM monad only supports side effects that it knows how to roll back.

But other side-effects, like user interaction, network communication, or disk I/O, cannot always be undone. Here, the programmer really MUST specify what the correct compensating action is, since there's no obvious answer that will always work.

IMO, these sorts of errors are the main reason why the task of building "business rules" type systems always ends up being programming, rather than something the business types can handle on their own.

The main thing you have to

The main thing you have to be careful of is that if your program has any side effects, then you must to be able to undo those effects before you can safely backtrack.

In a system like Lewis many side effects are undone as the program backs over the original action. For instance files are closed on backtracking over the open statement, mutable memory is removed in a similar way. There is also a do/undo construct that can specify any specific action and its undo action on backtracking. More generally these systems are predicate systems which can diagnose errors if the right "knowledge" is provided as a part of the database. Typically an alternate path is provided to check the "situation" and decide a new action. Even if no specific error recovery is provided the user knows that the system will backtrack on the error and not continue doing more damage. If no specific recovery is provided it will back up to the beginning and quit normally or continue with something else that still works. In Lewis code errors and type errors have this effect whereever they occur.

But other side-effects,

But other side-effects, like user interaction, network communication, or disk I/O, cannot always be undone. Here, the programmer really MUST specify what the correct compensating action is, since there's no obvious answer that will always work.

that is right, and in my opinion emphasizes the lack of supporting idioms.

a most interesting related concept is this idea by alexei alexandrescu.

it proposes a trivial extension to the classic try/catch exception model: exit handlers that can be installed along code inside try blocks, which are to be executed if an exception should occur. this way it is possible to specify actions and their respective rollback at the same place, resulting in a nice abstraction beyond the separation of regular and exceptional program flow.

i have implemented such exit handlers to control rollback in inherently side-effect heavy telephony application code, where the concept worked very well.

i have always wondered if similar idioms exist in some languages. it seems like the afore-mentioned lewis system and its do/undo construct is such an example. does anyone know of others, maybe even coupled with transactional memory?

This idea has been

This idea has been implemented in D, see my post above for the link to the reference.

I agree that this is a "good idea"TM.

Rollbacks

It's easy with a concise notation for λ. In my language Kogut:

Ensure {finally} => body
Ensure {proceed} {finally} => body
Ensure {proceed} {commit} {rollback} => body

Which means: proceed (do nothing by default); then execute body; if body succeeds, commit, or if it fails with an exception, rollback. finally is common for commit and rollback.

Having a distinguished proceed instead of just executing that at tbe beginning matters when asynchronous signals are supported. The body extends syntactically to the right as far as possible.

Perhaps I should use another name than Ensure, to avoid confusion with postconditions.

The erlang philosophy - Let it crash!

Sections 4.3-4.5 of Joe Armstrong's thesis has some interesting ideas on error handling. Erlang does have exceptions, but they don't climb up stack frames the way most 'normal' exceptions do.

http://www.sics.se/~joe/thesis/armstrong_thesis_2003.pdf

Forward or backward?

My understanding of Erlang is that they execute in small processes. If one fails they simply dump it and continue. This is really not different. A failed process is lost in any case, the question is what do you do next. Erlang is forward oriented so they find a new action by "forward chaining" in some sense. Direction is an implementation detail not a difference in logic.

To elaborate...

I was talking (or at least trying to talk) about the philosophy of error handling in erlang, not so much the implementation details. There are supervisor processes that are only concerned with error handling, and worker processes that try to get the work done. This contrasts against traditional exception handling, where your try/catch is intertwined with your 'normal' code.

Apologies for the unclear original post.

Isn't letting it crash in

Isn't letting it crash in the Erlang style where a supervisor monitors sub-processes more or less the same as exceptions, though? Basically an error causes it to rewind all the way back to the beginning where "beginning" is defined as being wherever the process was spawned from and any intermediate state is lost - just like when climbing the stack frames in a typical exception. To me that'd be about the same as writing a small module in the form of a collection of methods/functions and then just putting a try/catch around the part of the code that instantiates/enters that module.

Could writing a program that requires a correct environment from the start (basically the "let it crash if things are wrong" philosophy) be considered to be similar to extending a strict type system beyond the language/runtime itself? In other words, the type of the entire program becomes a rule which encapsulates everything the program may need from file system access, existence of certain files, free disk space, network availability, etc. and any failures to meet those expectations at or during run time would be a type failure and force the program to abort. (It's entertaining to ponder the idea that a type system could include traits of the user - such as needing the ability to read or understanding the problem domain, etc. and any failure to comply on the user's part would result in a type error and the program would quit.)

Could writing a program that

Could writing a program that requires a correct environment from the start (basically the "let it crash if things are wrong" philosophy) be considered to be similar to extending a strict type system beyond the language/runtime itself?

Why not? A program always has an implied environment, or input it can accept. Why not deal with it explicitly. I think this is one of the key ideas in security from external attacks.

But I would be careful with the word "crash". You want to prevent the crash by detecting a fault early or before it causes damage beyond the scope of the immediate process. Crashes in general can damage the system in such a way that it can't continue.

Exception-Handling Bugs in Java and a Language Extension to Avoi

Exception-Handling Bugs in Java and a Language Extension to Avoid Them by Westley Weimer has some practical ideas on improving the ease of using exceptions. Abstract:

It is difficult to write programs that behave correctly in the presence of exceptions. We describe a dataflow analysis for finding a certain class of mistakes made while programs handle exceptions. These mistakes involve resource leaks and failures to restore program-specific invariants. Using this analysis we have found over 1,200 bugs in 4 million lines of Java. We give some evidence of the importance of the bugs we found and use them to highlight some limitations of destructors and finalizers. We propose and evaluate a new language feature, the compensation stack, to make it easier to write solid code in the presence of exceptions. These compensation stacks track obligations and invariants at run-time. Two case studies demonstrate that they can yield more natural source code and more consistent behavior in long-running programs.