Failing Properly

Recently I've run into more and more code from programmers that simply don't know how to handle errors and exceptions. The other day I had to replace something like 30 generic try/excepts to actually figure out what the bug was that I was trying to fix. And unsurprisingly, there were numerous bugs that were hiding beneath the generic try/excepts. Below are some guidelines for failing properly. Though the code is in python, the concepts apply to many languages.

1. Allowing exceptions to bubble allows you to see problems in your code

There are some specific cases where a programmer might be expecting an exception and knows how to handle it. In other circumstances, the exception raised is most likely an unexpected bug. Wrapping code in a generic try/except usually means that you don't understand what's causing the error, and aren't handling it properly. Allowing most exceptions to bubble up to the highest point in the code that will cause the least impact will help reduce bugs in the code. If your system isn't designed to be failsafe, often times letting it bubble all the way up the stack so the program fails gets your attention and causes you to better understand the exception.

At the very least, LOG the exceptions that you aren't expecting. Don't just catch it and continue on your way.

2. Be specific in your handling

If there is a possibility of an exception, and you know what that exception is, specify it in the handler. It's not uncommon for a different unexpected exception to be raised from function calls, and you need to fix those situations rather than glaze past them. Consider the code below.

def divide(a, b):
    return a/b

   return divide(1, 0)
   # must be a divide by zero, fine to return None here
   return None
The problem with the above code is, what if it's not a divide by zero error? What if one of the numbers being passed in is a string? You need to handle the divide by zero case as a divide by zero exception so that you can fix other issues up the stack.
    return divide("1", 0)
except ZeroDivisionError, e:
    #ok to return None here
    return None
The exception raised here is specific and will allow you to see that whatever variable "1" is stored in was set incorrectly, allowing you to fix the code previous to this. Always document what you're catching and why.

3. Don't be afraid to fail and cause failure

Exceptions are not in themselves bugs in the code. If there is a problem in a function, let that function raise an exception (preferably one specific to the problem with a very well defined error message). Yes, it may cause other bugs to fail, but doing things like returning None values or 0's may be safer for the calling code, but is actually a change in the logic of the function, and may introduce harder to find bugs further down the line. Stack traces are easy to read and easy to navigate to the error. A value being averaged with 4 90's and a random 0 in an entirely different module is not as easy.

4. Handle the errors

To have a stable platform, you of course can't allow the application to crash continuously with exceptions. You need to have an intelligent way of handling exceptions. In a web site, this may mean just not to display certain widgets, or to catch errors in an intermediate logging class. Think about error handling and how you can inhibit the user the least from doing what they want to do, but that gives you the maximum exposure into parts of the system that you need to fix.

The above principles seem to somewhat go against defensive programming and what we're taught, but create more stable code by forcing us to fix errors in the end. Be defensive, but don't be stupid. Don't ignore problems in your code, or in the code that you're calling.