TITLE: Arguments for the use of exceptions [IS] = Ian Cameron Smith (clint@vsfl.demon.co.uk) [RM] = rmartin@rcmcon.com (Robert Martin) [IS] This document presents the author's view of some problems with the use of exceptions in languages such as C++ and ADA. This document represents the opinions of the author only. [RM] I would like to answer some of your issues directly. Although you raise some thoughtful arguments, I think your conclusions are too severe to be implemented. An indenfinite proscription of exceptions will be difficult to maintain over the lifecycle of your project. Exceptions are not evil. Nor are they intrinsically dangerous. Like any tool, the craftsman must know how to use them, and that takes time and experience. That experience cannot be gained by proscribing their use. [IS] The Basic Problem ***************** The problem, put simply, is that the control flow of the programme is broken by an exception, in much the same way that it would be by a `goto', but with much wider scope. [RM] I disagree. Certainly the flow of control is transferred non-locally. But the cleanup of local variables provides you with the mechanisms you need to clean up the exceptions. Thus, it is not an unconstrained transfer of control like a goto. A goto is dangerous because it leaps from one part of the code to another in an unconstrained fashion. An exception can only leap to places which are expecting it, and must follow a cleanup procedure to do so. [IS] You could, for example, contrive a `turn_printer_on' object, which turns the printer back off in its destructor; but this is hardly neat or clean. [RM] I disagree. Not only is it neat and clean, it is also much safer even when exceptions are not being used. Managing resources in the destructors of auto objects INSURES that those resources will be returned when the function exits, regardless of when or how it exits, or even if it exits normally. [IS] For example, say the C++ application registers a C++ routine as a callback handler. If an exception is raised in the callback, it propagates up the stack of C++ routines, then through the C code of the windowing system, before coming back into the C++ application. The problem is that it may well *not* come back; worse, the code may crash. We have had major problems with this in ADA, where exceptions have been lost, the stack has been corrupted, etc. [RM] All callback functions written in C++ should be declared with an empty exception specification: void callback() throw(); This prevents them from throwing exceptions. It explicitly says "this function does not throw exceptions.". The compiler does what checks it can to make sure that the function does not throw an exception. And the runtime system traps the rest. (it will abort). To prevent the abort, put an unconstrained catch block at the highest level of the callback and don't throw out of it. [IS] The answer to these "initialisation / de-initialisation" problems is, of course, to code exception handlers in the relevant places. Let's say that routine A calls B, and B calls C, and C raises an exception (in some error case) which is caught by A. This is fine, unless B does some initialisation which needs to be undone under any circumstances. In that case, B would need to have an exception handler which catches the exception, performs the de-initialisation, and re-raises the exception. [RM] No, B simply needs to have automatic variables whose destructors perform the deinitialization. [IS] Say, in the above example, that B doesn't have any initialisation / de-initialisation, and therefore doesn't have an exception handler. If someone later modifies B, and adds some de-initialisation, it will not be obvious to him that he needs to add an exception handler, [RM] He doesn't. His deinitialization should be done in the destructor of an auto variable. Also, IT WILL BE OBVIOUS if the function is properly declared. Any function which can throw an exception (or calls a function which can throw an exception) should be declared with an exception specification: void f() throw(x,y,z) // I can throw x y or z If a programmer modifies such a function, he will be aware that he need to put his deinitialization in an auto destructor. [IS] The situation is made even worse by the fact that C++ will soon be generating exceptions on its own; via the new `typeid' and `dynamic_cast' operators. This means that even a routine which calls only routines which do not use `throw' may still encounter exceptions. [RM] Herein lies the weakness of the proscriptive approach. The compiler's runtime system is being changed to throw exceptions. Thus you must incorporate some minimal exception handling into your application. Sooner or later an error condition which is "so much like what happens in the compiler" will present itself, and the temptation will be overwhelming to use the exception model which is already in place. Bit by bit this will grow and spread until you are using exceptions everywhere. Better that you DESIGN your exception structure now, enforce the use of exception specifications, and code-review with exceptions in mind, than to let the exceptions evolve into your application. [IS] It is my humble opinion that exceptions should not be used. For non-terminal cases, a consistent strategy of returning suitable error codes should be developed; for terminal cases, a fatal error reporting routine should be called to halt the system *and* an error code returned. [RM] IMHO this carries a much higher maintenance burden than exceptions do. These error codes will be ignored and forgotten and mistaken. We all know the drill, we have all programmed with them before. [IS] This approach has the advantage that it is visible in the source of a routine that it calls subroutines, and therefore that errors may originate in those routines, and need to be handled. [RM] This is true of C++ exceptions too. In fact it is much better and safer since the compiler typechecks the exception specification. Thus the exceptions that a function may raise are semantically declared in the signature of that function! void f() throw (x,y,z) // I produce x y and z errors. // deal with them if you call me [IS] As mentioned above, exceptions can be raised "invisibly" by the language's own run-time system. Unfortunately, this problem may spread to third-party libraries raising exceptions; [RM] It definitely will. Several third party packages are already written to throw exceptions but the exception code is #defined out. (i.e. the Booch Components.) More and more third party packages will incorporate the exception model. In the end, your proscription of exceptions will make it very difficult for you to take advantage of the latest third party technology. You will either forego them or rescind your proscription. [IS] This must be guarded against; when using third-party software, we must determine under what circumstances it may raise an exception, and insert code to catch these exceptions at the lowest level and turn them into error codes. [RM] This is very easy to say, but it will be extremely difficult to enforce. You want catch blocks in every function that uses a third party library? No way. Programmers will defer the catch blocks up a few levels in the subsystems that they are working on. And voila! you have an exception hierarchy in your application. Unfortunately your proscription has prevented you from designing the hierarchy in. That proscription will allow other programmers to ignore the possibility of exceptions. After all, exceptions aren't supposed to happen. In the end, the proscription will do more damage to your application than good. You will have exceptions anyway, and will not have a structure in place to handle them. [IS] The point of this memo, however, is that exceptions are inherently hard to manage. [RM] Software is hard to manage. Harder than exceptions. And holding back the wind is even harder to manage. The industry is apparently moving into exceptions. You will not be able to hold this wind back for long. Eventually your need to use third party products will overwhelm your proscription of exceptions. The proscription will be rescinded and you will be in a pickle. Managing exceptions in an application that was not designed for them will be very very hard indeed. [IS] The suggested solution, of not using exceptions, may seem drastic; however, C++ has managed since 1980 without them (at the time of writing, Jan 1994, they are still not widely supported), so I wonder just how necessary they are. [RM] If man were meant to fly.... The issue is not how useful they are, it is how USED they will be in the industry. You are gambling the safety of your application on the stance that exceptions will not be heavily used and that your application can survive without managing them. But this is an extremely risky stance (IMHO). I think exceptions will be used, and used heavily. In the next few years, the majority of third party software will make prodigious use of exceptions, and you will be playing a difficult, if not unwinable, game of catch-up.