|Programming Is Hard - No Exceptions Ever!|
|Written by Mike James|
|Thursday, 29 July 2021|
Page 2 of 2
Exceptions Not Considered Harmful
Let's get this clear.
I'm not saying exceptions are to be considered harmful - in theory.
In principle exceptions are a way of handling what should happen when things go wrong.
You may wonder why such going wrong things cannot be handling in standard code without having to invent the whole idea of an exception?
The reason you need exceptions is so that you can "unwind" the call stack.
It is often said, and there is some truth in it, that an exception is different from a error condition because it is unexpected. To borrow some well know jargon - an error is a known unknown but an exception is an unknown unknown.
There is more to it than this. The reason that some conditions are best handled using an exception is that that they need to "climb" back up the call stack to be fixed. If function A calls function B, calls function C and something goes wrong in function C then if it can be fixed in function C then its an error condition. If it can't be fixed in function C then the call stack has to be unwound and we have to go back to function B or even A to fix the problem then this is an exception.
Consider for example the dreaded divide by zero error - which isn't a good example of an exception but it is simple. This has been around since the first days of computing and yet we still don't handle it well.
Simple languages expect you to test for the possibility of a divide by zero before you do the divide. For example:
other languages will throw an exception if
cannot be computed because a is zero.
in both cases we have put off the moment when a user sees an error message and the program stops.
What we haven't done is work out what should happen next.
In most cases some part of the program will have to be done again. The fact that we are trying to divide by zero means that some how a got to be zero and it shouldn't be zero. We need to go back and have another go at getting a non-zero value for a.
This is where the real problem lies. Most programs have a forward flow of control. Functions call other functions and these deliver up their results. What usually doesn't happen is that a function says to the function that called it - "let's start again, forget you called me and do it over again". This is a bit like the tail wagging the dog and this is what exceptions are supposed to allow for.
When you call a function you need to prepare for something to go wrong and write an exception handler that tries the task over again.
This is generally very difficult.
The reason is that for a retry to be successful you have to figure out what caused the problem. In this case what caused a to be zero. If a was determined by a function higher up the call stack then there may be no choice but to throw the exception one higher and so on.
This theory is great but in practice it usually doesn't work like this.
Unwinding The Stack
You write a function that tests for a divide by zero and you throw an exception when it occurs. Your assumption is that that the function that called your function will work out why the divide by zero happened and call your function again with a correct or at least reasonable value.
What the programmer writing the function calling your function, and yes it could still be you, thinks is
"That function just threw an exception - it isn't working and I don't know why or how. I will just throw the exception and see if something higher up knows how to handle it."
The exception handling mechanism is designed to allow you to unwind the call stack and try again, not pass the buck.
The exception isn't harmful but it does allow programmers to adopt a frame of mind that usually ends up with the user being the ultimate exception handler.
The goal is not to have the user act as the routine exception handler but the exception handler of last resort.
So how can the keep going mentality help?
Rather than throwing an exception as soon as say a divide by zero occurs and leaving someone else to deal with the problem you really should consider your options.
Is there anything you can do which allows the process to continue in such a way that the user can get some value out of what is going on?
in the case of the divide by zero error it is tempting to think that the best fixt is to set the result to the machine infinity - after all that's what dividing by ever smaller numbers tends to. Sometimes the limiting argument is reasonable but in many cases the result should be set to some sort of mean result and the input a should be ignored.
For example if you are working out a graphics layout then taking an reasonable value for the result lets the process continue and might result in poor layout but at least the user gets to see the result and gets some feedback.
Ban The Error Message
It's not always easy to see what to do next when an error has occurred but you can always look for a way to return the control back to the user. Not to simply present them with a
"Something unexpected happened this program is going to terminate now"
but to present them with the wreck of the task and see if they can work out what to do next.
After all you may not be able to implement the artificial intelligence needed but to solve the problem but you can always rely on the user to supply some intelligence.
Try to keep as much of the application's state intact and return to the most general form of the user interface appropriate for the state. And yes you do have to explicitly manage state. To keep a program running when something goes wrong you need to have a good enough control of its state to be able to roll back to the last stable point that loses the smallest amount of data or work.
For example, the user has just "lost" a document due to a disk or communications error. Restore all state data so that the document is still editable and return to edit mode. The user can then decide what to do. You also need to try not to constrain the user in the choice of next action so you might have to rollback some of the state - allowing the user to select a storage path or medium for example.
Keep the state data but try to ensure that it can be subsequently changed.
We could go on examining examples for a long while and each example would present a different set of difficulties - but so far I've yet to find one that defeats the basic desire to "keep going". It's more an attitude of mind than something inherent in the technology.
Digital hardware might be all or nothing but the software that runs on top of it is much more flexible.
The "keep going" philosophy has at least four slogans:
Of course slogans are by their very nature not binding:
- you can always make an exception.
Programming Is Hard
* Recently revised
or email your comment to: email@example.com
|Last Updated ( Thursday, 29 July 2021 )|