Friday, April 21, 2006

Returning Error values and Exception handling

Exception Series Part 1

Returning and handling error values was the only option available to programmers using procedural language such as C. With the introduction of technologies such as SEH on Windows and concepts such as OO, Object Oriented languages such as C++ gave programmers another approach for handling errors by using C++ Exceptions. However since C++ is backward compatible with C, the error value returning approach is still widely used. Newer languages such as Java have gone one step ahead supporting newer concepts such as checked and unchecked exceptions.

In this article, I try to document some of the issues I have encountered when programming using error values and some comparison between the two approaches.

Consistency issues when returning error values

Returning error values from methods are very context sensitive and this nature leads to lot inconsistencies in the design. For example, some method could return a status value to indicate failure or success, while some other could return null address to indicate failure and thus fracturing the design. Moreover, if a method had to return an integer by its nature, then the error value will need to be a “special value” in the range of valid values, which needs to be handled specially. For example, consider a method, which returns the employee ID. We could have a negative value to indicate error value and a positive number if a legal employee id. However, then we could have a method, which needs to return a tri-state such as positive if greater, negative if lesser and 0 if equal. How do we qualify a return value as error here? Such issues over a considerable period will lead to a lot of design level inconsistencies in the project.

Strange signatures

Many C libraries to be consistent in their API structure take a general approach to always return a status about the outcome. Such design though provides a consistent interface, unnecessarily complicates the signatures. For example an API to return employee ID would then return some positive value on success and 0 on failure and return the employee ID itself as an "out" parameter leading to a very clumsy API structure. Where you would have expected the employee ID to be returned by the method, now you have to pass in the reference or pointer to a variable that will hold the employee ID.

Complexity and Modularization

On Unix systems, “errno” is famous for its pitfalls. Many Unix System calls set this global variable and provide APIs to retrieve the error number making such designs unnecessarily complex. Also, error checking needs to be done right in the context while making invocations and thereby tightly coupling the business logic and error handling. If the global error value is missed for one method, then another could overwrite the old error value. Also, this approach forces error value check after every call. For example -


STATUS doSomething(int a, int b) {
STATUS st;
st = doThing1(a);
if (st != SGOOD) return st;
st = doThing2(b);
if (st != SGOOD)
return st;
return SGOOD;
}


Here doSomething is an intermediate context and it unnecessarily has to carry the overhead of propagating the error context to the calling function.

Exceptions

With the introduction of the concept of Exception, designers can cleanly separate the business logic from the exception handling. The code is much cleaner, where we dont have to check for error values after each and every method invocation. Also, with the introduction of RTTI - Runtime Type Information - exception handling can be very sophisticated which the C programmers could only dream of. doSomething method could be written simply as -

void doSomething(int a, int b) {
doThing1(a);
doThing2(b);
}

The caller of doSomething is bothered about error handling, but the intermediate context - doSomething - does not have to bother anything.

Checked and Unchecked Exception

In C++, programmers have the fexibility to catch or leave an exception being thrown from a called method. Ofcourse, if the exception is not handled any point in the stack, then the thread or the process could be terminated. All exceptions are said to be Unchecked.

However, in Java, any exception inheriting from java.lang.Exception (but for those inheriting from Runtime exception) needs to be caught explicitly in the calling context or else needs to be stated in the signature that it is throwable. The idea here is that the language is forcing the programmer to acknowledge the exception and act. Whether this is good or bad is very debatable and a lot of discussion can be "grepped" over in google, but one experience that I have faced is that lot of us "lazy" programmers end up consuming the excpetion unnecessarily.