Upon further reflection and merciless ragging, I realize that I have at least two motivations for expressing this page, IlluminateTheMainline and RefactorMercilessly. A short comment on each here. They're discussed further on their own pages.
We often see code with try/catch pairs or the equivalent conditional pairs. Yet the normal flow of the code is that the exceptions do not occur, or one branch of the condition does not occur.
The effect of this is that the mainline flow of the code is obfuscated by the surrounding syntax, which is usually irrelevant. It would be nice if the code could be shown in light gray or something.
It's easier to develop the code to the mainline and then deal with the exceptional behavior. It's even easier if you can arrange never to deal with the exceptions at all - and often you can.
Well structured exception handling code has ManyThrowsFewCatches?. The throws are fairly innocuous and the try/catch clauses appear only where they actually are part of the mainline logic.
Code such as that shown below by PhilGoodwin often has multiple occurrences of the structure:
try { ... } catch { ... }The fact that this structure occurs frequently is an invitation to factor it out. This can be done in some cases by enclosing the entire program in one big try/catch. It can be done in other cases by encapsulating the individual uses inside a class, reducing the appearance of the try/catch code to one internal method. Sometimes it may not be possible to do either of these things. Personally, I'd still want to.
In larger programs you'll probably want more than one try/catch. They provide something akin to commit/rollback semantics and should be used in situations where those semantics are appropriate. see ExceptionsCancelOperations.
This is advice. It is not a rule. If you know how to write it as a rule, please do so.
Most modern software systems throw exceptions when something goes wrong: a zero divide, no more memory, etc. There's not much you can do to stop this from happening, except of course to have nothing go wrong.
In your own code, the best options are:
Example: In Smalltalk, if you look in a Dictionary for a key and it isn't present, you'll get an exception if you code it thus:
someDictionary at: someKeyIf you can't recover the program if the key is missing, let your outside exception handler report the error and eject. However, if you think that the key might legitimately be missing, in Smalltalk you can code:
someDictionary at: someKey ifAbsent: [^self noKeyAction]which will take the specific no key action. This is generally more clear than using an exception, which in ST would look something like this:
self handle: [ :ex | self noKeyAction] do: [someDictionary at: someKey]The exception-handling code above is a bit less descriptive of intent, and cannot be substituted for the ifAbsent: form in every case, owing to the fact that the exception code will drop through. Returning from the handle: clause is also problematical, depending just how it's coded.
The suggestion applies in all the languages I've used that support exceptions: throwing and fielding a lot of exceptions rapidly increases code complexity. It requires other users of your objects to be cognizant of exceptions in ways that might have been avoided had you taken the trouble to avoid using exceptions.
There are exceptions(!) but in general, equivalent code with exception throw/field is more complex than code without. Unless you're getting paid for complexity, you'll do well to CodeWithoutExceptions.
-- RonJeffries
Moved some commentary to IlluminateTheMainline. Maybe AvoidExceptionalConditions? would be better advise. In the presence of exceptional conditions throwing and catching exceptions actually keeps the code simpler than other error handling techniques. And in fact acts more to IlluminateTheMainline than to obscure it. -- pg
Do you think that it was appropriate for the coder of the / method to throw the ZeroDivide? exception? If not, what should they have done? And if they were correct to throw the exception, what makes their case different? -- DaveCleal
Yes. The responsibility of the implementor of arithmetic is to correctly model math. An alternative, in floating point at least, is to answer a NAN, but that's not available in most computers or in integer.
Their case is different for two reasons: first, they are modeling math, we are doing some application where an alternative response may be appropriate, and should be sought in the interest of simplicity. Second, they are writing a framework, and we (presumably) aren't.
My general strategy is to write code that always works, obviating the need for most exceptions. In a framework, I'd throw nicely differentiated exceptions where they were necessary. Where practical, I'd answer differentiated results instead. In an app, I'd go as far as practical not to use exceptions. And no further. In my experience and opinion, they don't help the code be more clear or more reliable. YMMV. -- RonJeffries
I'm happy to see you put things this way, Ron. In some of your other writings on the subject (in AvoidExceptionsWheneverPossible, for example) you have seemed to me to be much more strongly anti-exception. In analyzing your objections to exceptions, the thought occurred to me that your ideas made sense in an application, but not so much in a framework or reusable library. Reading this, it seems we're mostly in agreement. (I think a middle ground is applicable in server applications, by the way ...) -- GlennVanderburg
Ron, the comment about frameworks makes sense to me: the rule therefore is:
Don't use exceptions when you know how to handle the error and the handling should always be the same. Do throw an exception when either you don't know how the calling code wants to handle the exception, or when different callers want to handle the error differently.
The last clause is to do with another case where I would throw an exception. This is where the same error must be handled differently in different contexts. An example in my environment is valuation of exotic derivative positions, where the mathematical models we use sometimes fail when we encounter supposedly "impossible" market conditions. The valuation routines are executed from all over the app: and the response to an error varies from "ignore and carry on" (overnight reporting) through "throw up message on screen" (interactive pricing) and others.
I used to think that exceptions were used to signal unexpected failure (things like "out of disk space"). I'm now thinking that these are just a special case of you don't know how the calling code wants to handle the exception -- DaveCleal
you don't know how the calling code wants to handle the exception
This rule doesn't tell me why you shouldn't return an error code instead of an exception. A better rule would be in terms of surprise - how likely is the error to occur?
In practice I don't often remap exceptions as the second example does above. That's mainly useful in frameworks or applications so large they might as well be frameworks. You consider whether whoever is catching the exception is getting any extra value out of the remapping
I generally do allow exceptions from lower levels to pass through. Sometimes I catch them, do a bit of necessary clean-up and then rethrow them. Whether or not I catch and rethrow, I'd say I was "using" exceptions.
I also throw my own exceptions. I think this is my preferred way of dealing with errors. But I also provide a way to avoid them getting thrown. In the example, a caller can avoid getting ZeroDivideException? by manually testing for zero before the call, so that is OK. As another example, my file read would would throw an exception on end-of-file, but I'd also provide an IsEof?() method. If you made sure IsEof?() was false before performing a read, you'd never get the exception. (In Java terms, the exception would be Runtime - that is, you wouldn't have to declare it.) Thus if you expect end of file, you call IsEof?() and have no handler. If you don't expect end of file and one occurs, something has gone very wrong so you pass the exception back to the top-level handler. In neither case do you have a local handler.
Here's a summary:
Some of the languages with exceptions make an explicit assumptions that exceptions are slow to throw and catch, but not to have in the code. This matches Dave's approach in that you should provide checks for things like boundary conditions (e.g. end of file) and use exceptions only for unexpected behaviour (e.g. network failure). Within that, there's a distinction between other people's failures (e.g. network failure) which you should handle and clean up, and your failures (e.g. corrupt data structure) which imply buggy code and probably you should stop before you do any more damage. -- SteveFreeman
From Ron's example above:
someDictionary at: someKey ifAbsent: [^self noKeyAction]which will take the specific no key action. This is generally more clear than using an exception, which in ST would look something like this:
self handle: [ :ex | self noKeyAction] do: [someDictionary at: someKey]I don't know smalltalk well enough to be sure of my parsing of this, but it looks like two different instances of exception handling with the first one having much nicer syntax. In C++ it might look like this:
try{ someDictionary[ someKey ]; } catch( KeyIsAbsent ex ){ return noKeyAction(); }Assuming that someDictionary throws an exception of type KeyIsAbsent? when it cannot retrieve an entry for a key. I can see why you would want to avoid that ugly second syntax, is it really doing something different? -- PG
Just passing through years later, so I'll answer. A more common usage of the if:ifAbsent: is to use a default value, then proceed with the normal flow. In the example given the only difference is that you don't have the leap-into-the-air-and-come-down-elsewhere of the exception: you have normal flow of control instead of the JustSayGo behavior of exceptions. Perhaps no biggie, but I don't like random transfer of control. -- rj
I keep seeing reasons to not use exception handling that sound exactly like the reasons why I do use exception handling. Chief among them are these: I can code as if nothing could go wrong and I can write less code. Here's an example first with exceptions then with error codes:
void DoMyThing() { try{ if( QueryThatCouldFail() ) DoSomeUpdateThatCouldFail(); else DoAnotherThingThatCouldFail(); }catch(...) { TellUserItDidntWork(); } }Now with error codes:
void DoMyThing() { bool query_value; ErrorCode result; result = QueryThatCouldFail( &query_value ); if( result == OK ) { if( query_value ) result = DoSomeUpdateThatCouldFail(); else result = DoAnotherThingThatCouldFail(); } if( result != OK ) TellUserItDidntWork(); }It's both longer and harder to understand. What's more, the more complicated the algorithm gets the more retrieving and checking of error codes you have to do. This is how system level Windows programming works. Approximately half the lines of code have something to do with error checking.
With exceptions I only do error handling when I want to - using catch clauses. The rest of the time I ignore the possibility of anything going wrong except for some simple disciplines that make exception propagation safe. When something can go wrong I test for the condition throw an exception if it exists and then just go on if it doesn't. I don't understand where the extra code for exception handling is supposed to come from. -- PhilGoodwin
If the only alternative to exceptions is to write code like that immediately above, by all means one should use exceptions. However, even simpler than the exception example above is:
if (Query()) Update() else Other()The above, if it were possible, saves about half the code volume over the try/catch. What would it take to have all the query sequences look like that?
Maybe we could refactor our first two queries into a DatabaseAction object, and evolve from there.
Since queries, updates and others will surely be all over the program, it is worth investing to make them never fail (i.e. to make it be that you never have to check), and to deal with handling the exceptions (because still, however rarely, they do fail) in one and only one place in the program, rather than everywhere a query sequence is done.
But use all the catch/throw you want, you're the person doing the work. I'm just some guy. -- RonJeffries
As you point out catch is expensive. But throw is cheap, if you throw whenever you've decided not to handle some exceptional condition locally you'll still get some kind of fairly reasonable behavior. Adding catch clauses can make that behavior more reasonable but most of the time YouArentGonnaNeedIt, so don't add catch clauses until you need them.
I'll think about when that happens and maybe update ExceptionsCancelOperations. -- Phil Goodwin
I really have always liked SmallTalk's error handling and in many ways it inspired try...catch. However, when of my biggest problems with the SmallTalk method is that it is hard to compile optimally. We have to create a new object and block even if YouArentGonnaNeedIt. The equivalent in the JavaLanguage would be something like the following:
class Dictionary public Object atIfAbsent( Object key, Block noKey ) { Entry entry = entryOf( key ); return ( entry == null ) ? noKey.eval() : entry.getValue(); }This is nasty, but you get the idea. One would use something like this:
Person value = (Person) people.atIfAbsent( "Antonin Artaud", new Block() { public Object eval() { return Person.getNullPerson(); } } );This isn't so bad looking (for Java). I also like that the (so-called) exception handling is coupled directly to the code that knows best how to handle it. No chaotic JustSayGo hoping that someone is saying maybe come here. But the problem is that we must instantiate the Block object even when the key is present. Contrast this to the following method more traditional for CeePlusPlus and the JavaLanguage:
Person value; try { value = people.at( "Antonin Artaud" ); } catch ( Exception e ) // Any exception type means missing { value = Person.getNullPerson(); }Besides being pretty ugly, it is fairly optimal - which is one of the primary reasons why CeePlusPlus style languages use this method. However, it is entirely overused. For example, this could be much better stated as:
Person value = people.at( "Antonin Artaud" ); if ( value == null ) value = Person.getNullPerson();To me, this is much clearer. The usually objection is that what if the key exists but the value is null? To which my reply is, So what? The result is that the value for this key is null. If you really want a key that is present but has no value, use a no value object. In this instance called at will either give us (a) a non-empty Person object, (b) a reference to Person.getNullPerson(), or (c) a null value. Only in the case of (c) do we have an absent key. If you are only interested in whether or not the key is present, one can simply say the following:
boolean bNoArtaud = people.has( "Antonin Artaud" );These methods are much more clear that all this exception handling. Personally (non-pun intended), I tend to only throw an exception in program error conditions. The absence of a key is not a program error. It is a valid program eventuality. Something like a non-responsive server may be a program error or an illegally formatted property file the program needs to have correct behavior.
So, I guess what I am saying is that the biggest arguments over exception handling take an all or none point of view. Those who argue against it correctly state that exceptions should not be used for normal program behavior while those in favor correctly argue that it is an economical way of dealing with exceptional situations. The rift really is whether or not CeePlusPlus-style try..catch exception-handling should be used to direct a program's normal flow of control. To this I would say no. Exceptions should be reserved for errors. This would provide us with the following preconditions to exception use: "Is this a normal state or an error?".
This becomes more complex for a generalized behaviors such as sending a query to Database Statement object. One application may treat a query failure as a program error while for another application the query be looking for unimportant data. In this case, the case statement answers the implementation question for us. The former responds to the query failure by throwing an Error exception while the latter continues execution after handling the invalid query with in-line program logic -- no jumps.
I'm sorry but I have a serious problem with the above approach. This in-band transmission of exceptional conditions is fraught with error and confusion. Coming from a C background myself, I appreciate the value of an out-of-band channel for transmitting this sort of information. The simple fact is that there are many situations where there is no invalid value available for indicating an error (let alone more then one). Worse, every time you do this you have left another mine behind to blowup in some unsuspecting maintainers face should the semantics of the operation change and your invalid'' value ceases to be invalid! If you refuse to make use of exceptions, you must either ReturnErrorValueOrParameter?. So the code becomes...
ErrorValue error = Person.getErrorValue(); Person value = people.at("Antonin Artaud", error); if (!error.success()) { value = Person.getNullPerson(); }or
Person value = new Person(); if (!people.at("Antonin Artaud", value)) { value = Person.getNullPerson() }Personally I don't like either of them, and so I do use Exceptions for alternative flows of control.
Person value; try { value = people.at("Antonin Artaud"); } catch (Exception e) { value = Person.getNullPerson(); }I personally can't see the problem with this. The only hassle I have with Java's treatment of try/catch is that it creates a new lexical scope which is a pain in the proverbial.'' -- AndraeMuys
Andrae, the problem is that this isn't an exceptional condition, is a perfectly valid condition. An exceptional condition is one that you can't know about and do not anticipate. But others have explained this much better than I could. -- RobertDiFalco
The simple fact is that there are many situations where there is no invalid value available for indicating an error (let alone more then one).
How come? Did this all of the sudden not become an ObjectOriented language?
If you refuse to make use of exceptions, you must either ReturnErrorValueOrParameter?.
I think you are viewing the operation differently than me. When I say:
Person person = people.at( "Antonin Artaud" );I don't care whether the key is exists in the people collection, what I care about is whether or not there is a person associated with that key. I can very clearly check for that condition with the following assertion:
assert( person != null );This seems much clearer to me than the code you've shown with or without the try...catch construct. Be more specific about the problems...I want the cases not the axioms. -- RobertDiFalco
What I find wrong here is that the example above doesn't conform to the TellDontAsk principle. Why would you ask if there is an object in container? If you need the object, take it. If you need to add the object to container, add it. If you need to replace, replace. And if the state of the container is incompatible with your intention, catch the exception where your intention went wrong, not where you just discovered the problem. -- NikitaBelenki
A long time ago I was inspired to CodeWithoutExceptions by observing how Lisp functions were defined, and realizing that it was often possible to return nil or null or -1 without comment when some out-of-bounds condition occurred. Then I would use UnitTests to ensure that any possible use of that function (or method) kept the input values in bounds and the output value defined.
Sure in practice this often leads to cryptic undefined values or segmentation faults which require a debugger to analyze. But I found that exception-laden libraries for the most part leave you with no greater clues how to fix your problem either. -- ScottJohnston
Wow, what a horrible world we live in.
Humor is nice, but what does that mean? Are you saying that landing in a debugger is an OK experience for a programmer, or the opposite?
Another thing to consider, if you're using Java : every time an exception is thrown, an object of class Exception is instantiated. That isn't in itself a Bad Thing. But keep in mind that the constructor fills the StackTrace? for each new instance. Throwing many more exceptions than you really need (which is the case if you use exceptions for "normal" processing instead of truly exceptional cases) will seriously degrade performance, especially in environments where your code operates "deep within the stack", e.g. under a Servlet engine.
This isn't just a matter of principle - I've seen actual, supposedly production quality code that was using as much as (by my estimate) 50 times the memory it should have due to exception abuse.
I'm reading the above discussion and I'm still left not knowing if exceptions are any good. They are ugly, and apparently are only to be used where recovery is not an option. That leaves a big hole where I need an error stream. NULL objects aren't good enough. I would tend to use exceptions as flow-control entities but they are awkward, requiring a scope per method in the general case.
I'm tempted to create an alternative "soft" error channel and convert exceptions if they are recoverable, and never throw user exceptions unless they are "crash and burn" exceptions. That seems to be the best alternative right now. -- RichardHenderson
...the problem is that this isn't an exceptional condition, is a perfectly valid condition. An exceptional condition is one that you can't know about and do not anticipate.
Is the problem really one of semantics? Suppose Java had an extended message syntax and allowed in-line blocks like SmalltalkLanguage. What would it look like? I imagine that, from the client's perspective, it wouldn't look too much different from:
{ return person.at("Mark Addleman"); } (NotFound) { return Person.nullPerson(); }Although a little less verbose than the same code with exceptions, obviously this is the same as a try/catch block. A poor man's extended message syntax.
-- MarkAddleman
This discussion makes me wonder: does any other language have a construct like CommonLisp's restarts? I'm suspecting not, and that that is the reason why this topic is so controversial. -- AlainPicard
(Dylan has them, off course :) -- AurelienCampeas
In fact resume and resumeWith: are quite close to the cerror macro of CommonLisp, which creates an implicit restart just after the cerror expression, but without the idea of subsidizing a return value. -- AurelienCampeas
SmallTalk does not offer an equivalent to CommonLisp's restarts : restarts are meant to be closed over the lexical context in which signalment happens (signal ...) and thus can do whatever repair/recovery action is possible ; they are distinct from handlers. When you resume in Smalltalk (from a handler), you transfer control back to the instruction following the one that signalled, and optionally you can provide a return value ; but the condition handler has no access to the faulting context. "Repairing" it is therefore impossible. Oh ! Plus, retry/retryUsing: has nothing to do with restarts : it's just a convenient way to re-call (optionally using new parameters) the protected expression, after it signalled and was unwound (thus, the previous computation has been destroyed). It releases the programmer of putting the whole handler in an explicit loop and manually checking for exit conditions, etc. -- AurelienCampeas
I am not sure I understand this discussion, because I don't understand the relative complexity of code which uses exceptions and conditionals. However, it seems that the consensus is diverging from the common wisdom I have heard outside the wiki, which is that you should return WithYourShieldOrOnIt. There are technical problems with doing this in languages where exceptions are poorly implemented or only optionally available, and of course there are sometimes performance considerations, but exceptions are generally a good fast path to failure. -- GlyphLefkowitz
Beware of "common wisdom" for it leads quickly to "common sense" and "least common denominator" ... The CommonLisp ConditionSystem? is a very feature full, complete system, but none says you have to overuse it. From time to time it proves extremely convenient and will help you establish a protocol (control and data flow) between different "times" of the execution of a program. When you will have studied it, you will feel enlightened! -- AurelienCampeas
Exceptions are largely driven by the concept of state, i.e., some operations must be performed sequentially. For example, one should do a file open, then a file read, then a file close; doing a file read before an open or after a close will result in unpredictable behavior.
When writing software, there are two alternatives concerning state. One can trust the caller to adhere to the necessary convention and not verify, or one can not trust the caller, provide verification, and return a failure if verification did not pass. Exceptions are one solution to the last element.
In smaller programs, one can often assume the entire program is trusted, i.e., one will verify the correct data going into the system and ensure the correct sequential access. On larger systems, though, it is often helpful, if not necessary, to segment the systems into somewhat independent components, with the internals of a component being trusted, but external calls not being trusted and verified.
For example, suppose one must log on, access, and then log off of an external program. It would seem prudent for the external program to return an error if one tries to access the program before log on or after log off. The external program has no way to ensure that callers follow the required sequence and can choose either to return unpredictable results (from the perspective of the caller), or return an error.
Use of exceptions is a design choice that must be made on a case by case basis. Every method does not require that input conditions be checked and errors reported; otherwise method decomposition becomes almost impossible. Likewise, there are cases where it is appropriate to do the validation. A single rule, like CodeWithoutExceptions does not reflect this level of trade-off.
-- WayneMack
See DontUseExceptionsForFlowControl