During software development, a developer receives a functional specification that outlines a function the application needs to be able to perform. Then he goes on to implement the functional requirement. A developer’s mindset makes it inherently tough to account for abnormal situations caused by input limitations, malformed data, connectivity issues, and other possibilities that could cause an application to malfunction. A well-designed and implemented application is capable of elegantly handling errors. It implies providing meaningful feedback to the user, maintaining data integrity, and easing debugging process.
Error handling techniques
There are several well established error handling techniques. It is the developer’s responsibility to choose the most appropriate ones for the particular situation.
Return a neutral value
In some cases, it is appropriate to return a neutral value such as zero, an empty string, or null and continue execution.
Substitute the next piece of valid data
There are applications that are more tolerant to potential errors. When a temperature monitor encounters a reading error, it may just wait until the next reading period and return the next read value.
Return the same answer as the previous one
Similar to “substitute the next piece of valid data” technique. Instead of waiting for the next available piece of data, return the previous valid one.
Substitute the closest legal value
In cases where the return data is out of valid range defined by minimum and maximum, it is appropriate to return a valid minimum or maximum. If a computation of a string length results in a negative value, it is appropriate to return a zero.
Log a warning message to a file
It is usually used in conjunction with other techniques.
Display an error message wherever the error has occurred
Although it minimizes the overhead, it spreads the UI throughout application.
Shut down
In safety critical situations it maybe appropriate to shut down and application when an error occurs. In .NET framework this is accomplished calling System.Environment.FailFast(System.String).
Call an error-processing routine
Requires a centralized error-processing routine. The method is tightly coupled with error-processing routine.
Return an error code
The routine returning the error code does not handle the error on its own. It relies on other parts of the system to do it. The routine can set a status variable, throw an exception, or return status of the method’s return value.
Exception handling
It is a common mistake to abuse the programming language exception handling capabilities. Just because the language supports exception handling, it should not be used as error handling. Exception handling should be used to supplement and not replace error-handling techniques. The opponents of exception handling claim that it bloats the code and makes it less readable. It also negatively affects the performance. If properly used, it can improve user experience and ease debugging. Exception handling consists of two major pieces. The first one throws an exception, whereas the second one catches and handles it. Let’s see what the design guidelines and best practices are recommended by Microsoft and what I have found to work the best in my experience.
Throwing exception
Exceptions should be thrown only in exceptional situations. A method should be able to handle all the errors developer is aware of, and throw an exception only if it is unable to handle the encountered error.
It is a good practice to avoid throwing exceptions in constructors and destructors unless they are caught there as well. The reason is that some languages such as C++ do not call destructors unless the object has been fully constructed. This can potentially create a memory leak.
Exception should not be thrown from “finally” block.
If an exception thrown is not meaningful to the application, then it should be wrapped in a more meaningful one and included as inner exception and thrown.
Designing exception
Exceptions thrown are part of the method’s interface. Therefore, they should be thrown at the right abstraction. For instance, if a method is reading a file having phone numbers and encounters “FileNotFoundException” it should map it to its own custom exception such as “NoPhoneNumbersFoundException”.
Exception thrown should be derived from the base Exception class and the class name should have the “Exception” suffix as shown:
////// Exception generated when incorrect number of items are returned. /// [Serializable()] public class IncorrectItemCountException : Exception { public IncorrectItemCountException() { } public IncorrectItemCountException(string message) : base(message) { } public IncorrectItemCountException(string message, Exception inner) : base(message, inner) { } // Constructor needed for serialization // when exception propagates from a remoting server to the client. protected IncorrectItemCountException(SerializationInfo info, StreamingContext context) : base(info, context) { } }
Including as much information as possible in exception being thrown can help troubleshoot the issue. The exception message needs to explain what caused the exception and what needs to be done to avoid the exception. The line number should also be provided along with the message.
Catching exception
System.Exception should be caught only in top level exception handler.
Having an empty catch block is a bad practice. It reflects poor design or understanding of the problem. If it really needs to be done, then it should at least log an error.
Exception handling should never be used to validate a value or protect from invalid casts.
Building a centralized exception reporter makes the exceptions more manageable. It provides consistency in exception handling and better user experience.
Next week
We will look at how to run applications without installation and keep them synchronized across multiple systems in “Going portable on multiple systems”.
Sources
Design Guidelines for Exceptions
Code complete (Sections 8.3 and 8.4)