Tuesday, April 28, 2009

Exceptional Programming

I grew up programming. My first computer was a Laser 128 - an Apple IIc clone. I learned AppleSoft BASIC. Those were the days when the command line was also the BASIC interpreter. I programmed everything on that computer. Well, if 'everything' is some science fair projects and cute programs with blocky graphics. I grew up and got an 8086 and cut my teeth on QuickBASIC, then eventually C. In college I was taught C++ and I also picked up Perl and PHP. But despite all of this I never learned about exceptions until I earnestly got into Python.

We see exceptions all the time on our computers. "Firefox caused an exception in blah blah blah blah." I didn't really know what it meant past 'the program screwed up'. I had done most of my programming in C which doesn't have exceptions (or if it does I've never used them). Sometimes I screwed up in my programs and all sorts of crazy things happened. Gibberish printing out on the screen, random lockups, the computer starts beeping and won't stop, etc. I can't be sure, but once I think I caused the gibberish to come out on the printer. I'm probably imagining that though.

But after working with Python I found out those screwups were actually called exceptions. And what's more, you could handle them. Just put everything inside a try statement and if something bad happens you can catch it below and go on your merry way. It's great to just be able to deal with it and continue. But I noticed something. Exceptions weren't always exceptional. Try to access element number -1 of an array? C would happily let you do it and you could go crazy trying to figure out why. But in an exception-driven language it would catch it for you and stop you. But some functions throw exceptions instead of just telling you that you did something wrong. Well, I suppose that's HOW they tell you you did something wrong. But if you have three statements inside a try block and you just get a generic exception back, you're going to need to do more work to figure out what went wrong. And imagine my horror when I learned that some people will use exceptions for something as mundane as input validation! Maybe I'm just old fashioned, but in C you did your own input validation. You didn't just accept whatever the user gave you and then throw a fit when it turned out to be wrong. But what's worse is that this wasn't on your run-of-the-mill PC, but on an embedded device!

Exceptions? On an embedded device?

I've always been told that embedded devices needed to be ultra-reliable. All failure modes had to be accounted for and handled. True, exceptions are one way of handling failure, but for a lazy programmer it's entirely too easy to wrap the entire program in one big try block and just restart in case something bad happens. It's too easy to not even plan for the failure modes because you have exceptions. That may fly for your DVD player but certainly not for your airplane.

So what's the alternative? For one, strict input validation for everything - not just user data. Don't even assume that your own code will always pass you valid values in functions - always check! But then how do you signal an error? In C many functions would return a non-valid value if there was an error. For instance, abs() might return -1 if there was an error (and you had better check!). But this scheme doesn't always work. For some functions there may be no possible invalid values. You can't take a chance on using a valid value as your error signal. Sure, you'll bury the true meaning of the return value somewhere in the documentation but honestly who's going to look until there's a problem? And by then they've already cursed you for being so tricky.

No, the solution is to return a status along with your return value. In some serial systems the receiving device will respond to any message with a status message to tell you that it successfully received your information. Typically zero is 'OK' and everything else has a specific value - either the byte as a whole means something or each bit has significance. This can be done with functions as well. You can either pass all of the parameters as reference and the status as the return variable, or return the status and parameters in a struct if you don't like pointers. Of course it may be more work to work with a struct (and it's dangerously close to object-oriented programmng!). In this way a function can tell you that it failed and why it failed. You can use an enum to define all of the different error messages and then handle each of them. Of course some errors cannot be handled by your code alone. If data shows up late in a control system there's not much you can do about it except not use it in calculations. And your status returned will tell you if it was late (assuming you check it).

Exceptions are very good form for PC applications but it's easy to be lazy with them. Embedded programming sets a higher bar for the programmer, so bring your A game when developing for embedded devices. Always check inputs, parameters and status returned to make sure that you are working with valid data. The passengers on your airplane will thank you.

Update: I had a conversation with one of my colleagues on this issue and we're of the same opinion - in a sense. In essence he agrees with me except but he thinks exceptions are a valid method of achieving the same goals. BUT - we both agree that in embedded development you shouldn't be catching general exceptions or just wrap your entire main() in a try block. You have to know what failure you expect to happen and exactly how to correct it in EVERY case - exceptions or not. The way exceptions work on PCs (Oops, Firefox caused an exception - it's quitting) is unacceptable for embedded development. I shudder to think that the same approach would be applied to an embedded device by an unaware programmer, but that doesn't indict exceptions in general. Just their misuse.