7 Error handling

Many code bases are completely dominated by error handling. It is nearly impossible to see what the code does because of all of the scattered error handling. Error handling is important, but if it obscures logic, it’s wrong.

Use exceptions rather than return codes

The problem with setting an error flag or returning an error code is that they clutter the caller. The caller must check for errors immediately after the call. Unfortunately, it’s easy to forget. For this reason it is better to throw an exception when you encounter an error. The calling code is cleaner. Its logic is not obscured by error handling.

Write Your Try-Catch-Finally Statement First

One of the most interesting things about exceptions is that they define a scope within your program. When you execute code in the try portion of a try-catch-finally statement, you are stating that execution can abort at any point and then resume at the catch. Your catch has to leave your program in a consistent state, no matter what happens in the try. For this reason it is good practice to start with a try-catch-finally statement. This helps you define what the user of that code should expect, no matter what goes wrong. Try to write tests that force exceptions, and then add behavior to your handler to satisfy your tests. This will cause you to build the transaction scope of the try block first and will help you maintain the transaction nature of that scope.

Use Unchecked Exceptions

When checked exceptions were introduced in the first version of Java, they seemed like a great idea. The signature of every method would list all of the exceptions that it could pass to its caller. However, it is clear now that they aren’t necessary for the production of robust software. Other languages don’t have checked exceptions and yet it is possible to write robust software in all of these languages.
The price of checked exceptions is an Open/Closed Principle violation. If you throw a checked exception from a method in your code and the catch is three levels above, you must declare that exception in the signature of each method between you and the catch. This means that a change at a low level of the software can force signature changes on many higher levels.

Provide Context with Exceptions

Each exception that you throw should provide enough context to determine the source and location of an error. Create informative error messages and pass them along with your exceptions.

Define Exception Classes in Terms of a Caller’s Needs

There are many ways to classify errors. We can classify them by their source (one component or another?) or their type (device, network, programming). However, when we define exception classes in an application, our most important concern should be how they are caught. In most exception handling situations, the work that we do is relatively standard regardless of the actual cause. We have to record an error and make sure that we can proceed. We can simplify our code considerably by wrapping the API that we are calling and making sure that it returns a common exception type. Often a single exception class is fine for a particular area of code. The information sent with the exception can distinguish the errors. Use different classes only if there are times when you want to catch one exception andallow the other one to pass through.

Define the Normal Flow

You create a class or configure an object so that it handles a special case for you. When you do, the client code doesn’t have to deal with exceptional behavior. That behavior is encapsulated in the special case object. This is called the SPECIAL CASE PATTERN [Fowler].

Don’t Return Null

When we return null, we are essentially creating work for ourselves and foisting problems upon our callers. All it takes is one missing null check to send an application spinning out of control. If you are tempted to return null from a method, consider throwing an exception or returning a SPECIAL CASE object instead. If you are calling a null-returning method from a third-party API, consider wrapping that method with a method that either throws an exception or returns a special case object.

Don’t Pass Null

Returning null from methods is bad, but passing null into methods is worse. In most programming languages there is no good way to deal with a null that is passed by a caller accidentally. Because this is the case, the rational approach is to forbid passing null by default.

Conclusion

Clean code is readable, but it must also be robust. These are not conflicting goals.

Previous: 6 Objects and Data StructuresUp: ContentsNext: 8 Boundaries