The Role of Exceptions in an Ideal World
I was truly thrilled when I first came across exceptions in C++ after using the clumsy error reporting style of C. It made my algorithms cleaner and, therefore, it made me happy. After several years, I tried Java and I found it interesting to impose the discipline of explicitly declaring the exceptions that may be raised in a routine.
Again, after a while, I found this obligation cumbersome and found myself relieved to see that C# did not impose this constraint. So much for my pre-Eiffel to exposure exceptions.
When I began programming in Eiffel, I noticed too aspect of the Eiffel attitude toward exceptions. First, in his coding guidelines, I saw that Eric Bezault suggested that we document in the routine's header the exceptions that could rise from an execution of the mentioned routine. On the other hand, in OOSC Meyer shows several design guideline to avoid using exceptions such as a priority validity checks (e.g. in preconditions) and a posteriori checks which are done by loosing a routine's postcondition by saying "X will be true if there was no errors" instead of "X will be true".
I then noticed that not many routines use exceptions handling and they seem to be divided in two categories. The first is the category of routines that interfaces with a non Eiffel and (let's say it) less reliable system. The second is category of routines which aim at factoring out the processing of uncaught exceptions (usually near the main).
So here is where the question comes up: what are exceptions good for when we have design by contract? Let's go to the extreme and say that we are able to formally prove the correctness of our Eiffel system. As I see it, the only cause for exception handling then would be for memory errors and interfacing with less well-behaved systems.
Having proven the validity of our system, we don't need to catch assertion violations or void calls and it seems that the error conditions in all Eiffel code could be handled by a priori or a posteriori checks.
My conclusion is therefore that we probably need to put exception declaration in the header of routines and design rules for the interaction with the inheritance mechanism. Furthermore, I would add that the convention is adopted by Java is not a bad one but the clumsy style it creates is only a sign that Java does no distinction between a contract violation and exceptional (unplanned) condition.
Adopting it in Eiffel would therefore give a precise criterion to the programmer as to where efforts has to be spent to make sure the invariant of a class must be reestablished after an exception.
I would be very interested in hearing about your opinions and experience on that topic.
Note: In what consider an ideal world, all Eiffel systems are ideal. In that respect, this entry is only concerned about ideal Eiffel systems which are systems for which the total correctness has been formally proved. I didn't include memory conditions in this correctness criteria so you can consider it a weak idealism on my part.
This is a great description and analysis of exceptions. In my personal opinion, there are only two kinds of exceptions: intenional and unintentional.
Intentional exceptions are exceptions that you expect to be raise. If the exception is raised, you can directly point to the source of the problems. The rescue clause then has a predefined mechanism to deal with these exceptions.
Java uses intentional exceptions as a regular way of flow control within the program, but that is normally rejected by the Eiffel community.
The main reason to have intentional exceptions in Eiffel systems is for executing code that you know might fail, but you have no way of testing before the execution. Examples are interfacing C routines or running test suites.
Declaring intentional exceptions in the header of a routine (Java style) has its advantages. But as there are just few applications for intentional exceptions, I am not sure that the increase in complexity of the language is justified.
The main application of exceptions in Eiffel are unintential exceptions. Eiffel, with Design by Contracts and its other assertion mechanisms, has an extensive system for a program to detect at run-time that something "went wrong" (in Eiffel this is called "a failure"), as the overall consistency of the system was violated.
Unintential exceptions cannot be declared and completely rescued: if you know that you can raise this exception, you can as well just go ahead and fix the bug. You also cannot completely recover such an exceptions as you do not know what went wrong.
The rescue clause normally does not have the job of recovering from an unintential exception, but instead to implement robustness (with degraded functionallity).
I agree that assertion violations and lack of memory must cause unintentional exception but, aside from running test suites there are still two different and important use for intentional exceptions. Interfacing with another software system (C, .NET, Java, etc) and interfacing with a hardware system (e.g. a network interface).
In Java, you have both intensional and unintentional exception. The NullPointerException is an example of unintentional exception. For the rest, we have a mix of contract violation exception and of actual failures. It happens that mixing the two makes the interface of Java classes and routines full of noise for which we should be (and probably are) glad that it is not the case in Eiffel. Getting rid of those noisy cases may make it both usable and useful to have this kind of documentation.
It is acceptable that, in order to keep the development of Eiffel compiler management and well organized, we may reject the integration of very good and helpful feature.
So even under the assumption that the explicit declaration of intentional exceptions is not a priority for any compiler developer, I find it interesting to study its value in the context of the Eiffel method.
The heuristic I use for evaluating the usefulness of this kind of feature is checking at the impact it would have on the ability to prove (say) local correctness, which, as I see it, is a total correctness where we assume the call graph is acyclic.
It may appear as a totally irrelevant heuristic for people whose method is based on testing but I maintain that is very useful. Indeed, if it is impossible (or inefficient) to reason formally about a particular construct, I think it is totally hopeless to try and reason informally about it.
In this case, I think using explicit intensional exception would greatly increase the productivity of such development because the mere fact of looking up at a classes interface would allow a programmer to know if he has to guard his code against an intensional exception.
Finally, thank you for giving me of the terminology of intensional and unintentional, I was not aware that it existed. However, I am wondering if a more symmetric one exists like intensional and silent or intensional and discrete to name but two examples.
Exceptions are bad in Eiffel. The biggest problem is: what is the class invariant if one of its routines throw an exception? There's only one case to throw an exception and that is to bubble up an error to the layer that stores the error for later analysis, before dying.
There might be some minor cases, as pointed out, you might or could rescue from C/C++ exceptions, but Eiffel exceptions are contract violations, and hardly any Eiffel program can recover from that.
Exceptions are useful when you want to take shortcuts: for example eposix, and other libararies such as ECLI do to, optionally throw exceptions on error. So you can be lazy and know your program doesn't continue in case weird things happen. And if you want to build full-blown error handling, you can do to by disabling such.
Thank you very much. This provides a piece of information I very interested in: the scarce use of exceptions in Eiffel. Would you assume that it is an implicit convention to avoid exceptions like the plague?
Also, don't you think that making mandatory the declaration of exceptions in Eiffel would solve the problem you refer to in your question?
Whenever you call a feature which may raise an exception you have to do something about it. It seems like a good indication that you have to take care of your class invariant in that routine.
I might add: we all agree that introducing a construct as a tool for lazy programming is something to avoid. Therefore, it would seem that applying the systematic declaration of exception would get rid of lazy designs. Good, isn't it?
Simon: Would you assume that it is an implicit convention to avoid exceptions like the plague?
Simon: Also, don't you think that making mandatory the declaration of exceptions in Eiffel would solve the problem you refer to in your question?
A bit like Java does? I.e. if one of the routines can thrown an exception, you have to specify how you handle it or so? Very cumbersome. Given that an exception in Eiffel is impossible for a correct program on an ideal machine (enough memory etc.) I think we don't need that. Would help little if at all, and make every feature you write so much harder.
But given the case we identified, it seems unlikely that we will see a computer with infinite memory be sold or that we will run out of system written in C++ or .NET to interface with. However, I am not abandoning my (somewhat weak) idealism that we will have Eiffel systems whose total correctness is proven.
Berend: A bit like Java does? I.e. if one of the routines can thrown an exception, you have to specify how you handle it or so? Very cumbersome.
Yes I would choose something like what Java has. It is certain that it is cumbersome for Java but, then again, Java does not have Design by Contract. By my experience, it seems that most exception in Java are used to report precondition violations. If we remove the exceptions declared for the sake of reporting such violations, it seems very bearable to force the declaration of exceptions where we interface with non-Eiffel systems since they are a minority.
As such, the exception mechanism could almost be classified like
external features are. The only thing keeping me from doing this classification is the fact that, even with the proof of total correctness, we still need to report memory insufficiency.
Simon: we still need to report memory insufficiency.
I think this is an architectural issue, not a language issue. I wrote programs that can safely crash if they run out of memory. Things just get retried (either by users, or by programs). You can't handle out of memory in Eiffel just as you can't handle out of CPU or out of disk.
You can handle out of disk by buffering and write to disk once more disk is available, but of course only a subclass of problems can be handled by this approach. So I rather have a program that is written for an idea machine, and let the architecture handle the real world problems.
Berend: I think this is an architectural issue, not a language issue. I wrote programs that can safely crash if they run out of memory.
Here, you are referring to a software architecture, right?
Berend: I wrote programs that can safely crash if they run out of memory.
I would very much like to hear more about that. Not that I am doubtful of such a deed but I held the belief that memory management was the only issue that required exception handling even if it's not related to the interface with an external system.
Berend: You can't handle out of memory in Eiffel just as you can't handle out of CPU or out of disk.
I'm a bit puzzled by this sentence. Do you mean that the strategy for recovering from a failed memory allocation is different of that for recovering for lack of disk space? Or do you mean that they are both equally impossible things or that just one of them is?
Berend: So I rather have a program that is written for an idea machine, and let the architecture handle the real world problems.
I agree that it is better to design a program while abstracting from the constraint of the machine that will execute it. However, I am not clear as to what kind of distinction you make between the program and the architecture (if it's a software architecture). Do you identify as the program the collection of executable statements of your system whereas the architecture would be the class hierarchy and their interfaces?
The architecture in this case is specifically the architure on how you handle failure. First some individual examples:
For example web sites are resilient against failure. If the server cannot handle the load, users can just retry later.
Same for updates to the db: if you do that in a transaction, even if the program fails, that will not change the db.
Communication between programs can be handled likewise: you can use message queues to make sure messages don't get lost.
The collection of all such techniques is the architecture in this case. As I'm a big fan of the REST approach, if you built web applications you get most of this for free. It's built-in. You do something, if you don't get a response, you try again. So the architecture is not the software, but how the software behaves in case of an error.
A simple example of architecture that handles software faults is a daemon wrapper: the wrapper starts the daemon, if the daemon fails (out of memory), the wrapper starts it again.
On strategies for memory or disk: I think both failure cases are similar, than you can solve subcases in some situations doesn't mean you can solve most of the problems. I.e. in both cases you could trade memory for disk and vice versa, but that solves only minor cases.
Computing takes place on a dynamic platform with no guarantees and it can run out of resources. Handling that gracefully is architecture.
And when you speak of retrying, it is mostly done by the software, right? And in this case, without exceptions so most of what you do is calling an attempt procedure which includes failure in its contract, right?
Also, what is the REST approach?
Your ideas remind me, Simon, of something I read a few years ago at http://research.microsoft.com/SpecSharp/ about checked exceptions in Spec# (Microsoft's research language that retrofits contracts onto C#). I think the exact link may have been http://research.microsoft.com/SpecSharp/papers/krml135.pdf .
(The Spec# team's research into not-null types influenced the addition of Void-safety to Eiffel, if I remember rightly.)
Thanks for the link I did not know of the resemblance. My previous exposure to the treatment of exceptions in Spec# gave me the impression that they are expending a great lot of efforts to be able to prove correctness in the presence of exceptions.
Several designs I saw in systems written in C# made a great impression on me: exception handling seemed to be used as a usual control structure. I tried to fix it up but it seemed to be caused by the structure of the .NET framework. If this kind of design is indeed a good characterization of the .NET style, I would not be surprised that it would justify the effort that the Spec# team dedicated to handling exceptions.
Anyhow, this opinion is probably what prompted me to wonder about the amount of efforts that is worth spending for guaranteeing exception safety. I'm hoping that it is very little since its usage obfuscates the control flow.