A famous technique for getting an algorithm right is to code it from front to back, considering no exceptional behavior. To take a stupidly simple case, let's write a method to answer the ratio of two integers.
ratio: topInteger over: bottomInteger ^topInteger / bottomIntegerNow of course this almost always works, except when bottomInteger isn't as nonZero as it might be.
One possible solution is to have the method handle the exception for the zero divide, translate it into some other exception, and rethrow all the others. Something like:
ratio: topInteger over: bottomInteger self handle: [ :ex | self handleException: ex] do: [self primRatio: topInteger over: bottomInteger] primRatio: topInteger over: bottomInteger ^topInteger / bottomInteger handleException: anException anException isZeroDivide ifTrue: [ZeroRatioException? raise] ifFalse: [anException raise]Note in passing that this code has 10 lines instead of the original 2. This means that this feature contains an expected number of defects five times that of the original. And the code to deal with the exception is still unresolved.
An alternative is to code the test directly and throw the exception:
ratio: topInteger over: bottomInteger bottomInteger isZero ifTrue: [ZeroRatioException? raise]. ^topInteger / bottomIntegerThis is certainly shorter, and more clear. It leaves dealing with the exception still unresolved, however.
Well, and isn't that a good thing? We don't know why this method was called. We only know that it can't supply the answer that was requested. So we do the SimplestThingThatCouldPossiblyWork and throw an exception.
In many cases, it is possible to answer an object that will work just fine in the exceptional case. Perhaps if the divisor is zero, we're happy to get back zero:
ratio: topInteger over: bottomInteger bottomInteger isZero ifTrue: [^0]. ^topInteger / bottomIntegerIn this trivial case, one could object that one can't tell whether the zero that comes back is legit or not. This is true, but if you care, that means you're going to check the result of the ratio:over: method. In that case, you might as well check the input parameter instead. Same code volume, more clear, less screwing about.
Excuse me but what are you telling us here? You suggest that a division method returns zero for an undefined result? That means that the caller calls the method, checks whether the result is zero and if yes checks whether the divisor was zero (and in that case realizes that he made a mistake to call the method in the first place), otherwise continue as planned? This is the worst caricature of an idiotic error handling scheme I have ever seen!
I would prefer to write my code with the assumption that ratio:over: always works. If it throws an exception whenever it doesn't work I can do just that: if it fails then my code will also fail without any special intervention from me.
In more realistic cases, it is also quite reasonable to answer a MissingObject? (or NullObject, as they are called in some writeups).
Where this is possible, the code is more direct, more clear, shorter, and less prone to errors. --RonJeffries
Agreed. Exceptions should only be used in situations where the code really can't do what it promised to do. NullObject works brilliantly in those situations where the object requested doesn't exist but a NullObject can take its place without the client noticing. In this case, however, I don't think that returning a value of zero is equivalent to returning a null object, because it can cause the client to misbehave and is, therefore, not transparent. The point is well taken though: throwing an exception is never as good as not allowing an exceptional condition to occur in the first place.
Exception handling need not obscure the mainline. When done properly it will actually serve to IlluminateTheMainline in two ways: first, it moves the code that handles exceptions to a very small number of centralized locations. Second, it allows the code that first detects the condition to react in a very simple and understandable way -- it just throws. All the code that will end up in the call stack between the throw and the catch can be written just as if nothing at all can go wrong (with a few caveats about resource allocation and the stack unwinding). -- PhilGoodwin
Agreed - the lesson here is to avoid exception handlers. It is OK to throw exceptions as long as it doesn't require your caller to catch them. -- DaveHarris
I was just referred to this page. I hate to sound like a philistine, but what is that sample code? Smalltalk? I've never seen anything like it. -- 11011011
Yes, SmalltalkLanguage.