Design Patterns – Are Error Variables an Anti-Pattern or Good Design?

anti-patternsdesign-patternsexceptionsobject-orientedpatterns-and-practices

In order to handle several possible errors that shouldn't halt execution, I have an error variable that clients can check and use to throw exceptions. Is this an Anti-Pattern? Is there a better way to handle this? For an example of this in action you can see PHP's mysqli API. Assume that visibility problems (accessors, public and private scope, is the variable in a class or global?) are handled correctly.

Best Answer

If a language inherently supports exceptions, then it is preferred to throw exceptions and the clients can catch the exception if they do not want it to result in a failure. In fact, the clients of your code expect exceptions and will run into many bugs because they will not be checking the return values.

There are quite a few advantages to using exceptions if you have a choice.

Messages

Exceptions contain user readable error messages which can be used by the developers for debugging or even displayed to the users if so desired. If the consuming code cannot handle the exception, it can always log it so the developers can go through the logs without having to stop at every other trace to figure out what was the return value and map it in a table to figure out what was the actual exception.

With return values, there is no additional information can be easily provided. Some languages will support making method calls to get the last error message, so this concern is allayed a bit, but that requires the caller to make extra calls and sometimes will require access to a 'special object' that carries this information.

In the case of exception messages, I provide as much context as possible, such as:

A policy of name "foo" could not be retrieved for user "bar", which was referenced in user's profile.

Compare this to a return code -85. Which one would you prefer?

Call stacks

Exceptions usually also have detailed call stacks which help debug code faster and quicker, and can also be logged by the calling code if so desired. This allows the developers to pinpoint the issue usually to the exact line, and thus is very powerful. Once again, compare this to a log file with return values (such as a -85, 101, 0, etc.), which one would you prefer?

Fail fast biased approach

If a method is called somewhere that fails, it will throw an exception. The calling code has to either suppress the exception explicitly or it will fail. I have found this to be actually amazing because during development and testing (and even in production) the code fails quickly, forcing the developers to fix it. In the case of return values, if a check for a return value is missed, the error is silently ignored and the bug surfaces somewhere unexpected, usually with a much higher cost to debug and fix.

Wrapping and Unwrapping Exceptions

Exceptions can be wrapped inside other exceptions and then unwrapped if needed. For example, your code might throw ArgumentNullException which the calling code might wrap inside a UnableToRetrievePolicyException because that operation had failed in the calling code. While the user might be shown a message similar to the example I provided above, some diagnostic code might unwrap the exception and find that an ArgumentNullException had caused the issue, which means it is a coding error in your consumer's code. This could then fire an alert so the developer can fix the code. Such advanced scenarios are not easy to implement with the return values.

Simplicity of code

This one is a bit harder to explain, but I learnt through this coding both with return values as well as exceptions. The code that was written using return values would usually make a call and then have a series of checks on what the return value was. In some cases, it would make call to another method, and now will have another series of checks for the return values from that method. With exceptions, the exception handling is far simpler in most if not all cases. You have a try/catch/finally blocks, with the runtime trying its best to execute the code in the finally blocks for clean-up. Even nested try/catch/finally blocks are relatively easier to follow through and maintain than nested if/else and associated return values from multiple methods.

Conclusion

If the platform you are using supports exceptions (esp. such as Java or .NET), then you should definitely assume that there is no other way except to throw exceptions because these platforms have guidelines to throw exceptions, and your clients are going to expect so. If I were using your library, I will not bother to check the return values because I expect exceptions to be thrown, that's how the world in these platforms is.

However, if it were C++, then it would be a bit more challenging to determine because a large codebase already exists with return codes, and a large number of developers are tuned to return values as opposed to exceptions (e.g. Windows is rife with HRESULTs). Furthermore, in many applications, it can be a performance issue too (or at least perceived to be).

Related Topic