It is rare for a class to be written perfectly the first time. In most, if not all, situations, things will go wrong. Any designer who does not plan for problems is courting danger.
Assuming that your code has the ability to detect and trap an error condition, you can handle the error in several different ways: On page 223 of their book Java Primer Plus, Tyma, Torok, and Downing state that there are three basic solutions to handling problems that are detected in a program: fix it, ignore the problem by squelching it, or exit the runtime in some graceful manner. On page 139 of their book Object-Oriented Design in Java, Gilbert and McCarty expand on this theme, but add the choice of throwing an exception:
Ignore the problemnot a good idea!
Check for potential problems and abort the program when you find a problem.
Check for potential problems, catch the mistake, and attempt to fix the problem.
Throw an exception. (Often this is the preferred way to handle the situation.)
These strategies are discussed in the following sections.
Ignoring the Problem
Simply ignoring a potential problem is a recipe for disaster. And if you are going to ignore the problem, why bother detecting it in the first place? The bottom line is that you should not ignore the problem. The primary directive for all applications is that the application should never crash. If you do not handle your errors, the application will eventually terminate ungracefully or continue in a mode that can be considered an unstable state. In the latter case, you might not even know you are getting incorrect results for some period of time.
Checking for Problems and Aborting the Application
If you choose to check for potential problems and abort the application when a problem is detected, the application can display a message indicating that you have a problem. Then the code gracefully exits and the user is left staring at the computer screen, shaking her head and wondering what just happened. Although this is a far superior option to ignoring the problem, it is by no means optimal. However, this does allow the system to clean up things and put itself in a more stable state, such as closing files.
Checking for Problems and Attempting to Recover
Checking for potential problems, catching the mistake, and attempting to recover is a far superior solution than simply checking for problems and aborting. In this case, the problem is detected by the code, and the application attempts to fix itself. This works well in certain situations. For example, consider the following code:
if (a == 0) a=1; c = b/a;
It is obvious that if the if statement is not included in the code and a zero makes its way to the divide statement, you will get a system exception because you cannot divide by zero. By catching the exception and setting a to 1, at least the system will not crash. However, setting a to 1 might not be a proper solution. You might need to prompt the user for the proper input value.
A Mix of Error Handling Techniques
Despite the fact that this type of error handling is not necessarily object-oriented in nature, I believe that it has a valid place in OO design. Throwing an exception (discussed in the next section) can be expensive in terms of overhead. Thus, although exceptions are a great design choice, you will still want to consider other error handling techniques, depending on your design and performance needs.
Although this means of error checking is preferable to the previous solutions, it still has a few potentially limiting problems. It is not always easy to determine where a problem first appears. And it might take a while for the problem to be detected. In any event, it is beyond the scope of this book to explain error handling in great detail. However, it is important to design error handling into the class right from the start.
Throwing an Exception
Most OO languages provide a feature called exceptions. In the most basic sense, exceptions are unexpected events that occur within a system. Exceptions provide a way to detect problems and then handle them. In Java, C# and C++, exceptions are handled by the keywords catch and throw. This might sound like a baseball game, but the key here is that a specific block of code is written to handle a specific exception. This solves the problem of trying to figure out where the problem started and unwinding the code to the proper point. Here is how the code for a try/catch block looks:
try { // possible nasty code } catch(Exception e) { // code to handle the exception }
If an exception is thrown within the try block, the catch block will handle it. When an exception is thrown while the block is executing, the following occurs:
The execution of the try block is terminated.
The catch clauses are checked to determine whether an appropriate catch block for the offending exception was included. (There might be more than one catch clause per try block.)
If none of the catch clauses handle the offending exception, it is passed to the next higher-level try block. (If the exception is not caught in the code, the system ultimately catches it and the results are unpredictable.)
If a catch clause is matched (the first match encountered), the statements in the catch clause are executed.
Execution then resumes with the statement following the try block.
Again, it is beyond the scope of this book to explain exception handling in great detail. Suffice to say that exceptions are an important advantage for OO programming languages. Here is an example of how an exception is caught in Java:
try { // possible nasty code count = 0; count = 5/count; } catch(ArithmeticException e) { // code to handle the exception System.out.println(e.getMessage()); count = 1; } System.out.println("The exception is handled.");
Exception Granularity
You can catch exceptions at various levels of granularity. You can catch all exceptions or just check for specific exceptions, such as arithmetic exceptions. If your code does not catch an exception, the Java runtime willand it won't be happy about it!
In this example, the division by zero (because count is equal to 0) within the try block will cause an arithmetic exception. If the exception was generated (thrown) outside a try block, the program would most likely have been terminated. However, because the exception was thrown within a try block, the catch block is checked to see whether the specific exception (in this case, an arithmetic exception) was planned for. Because the catch block contains a check for the arithmetic exception, the code within the catch block is executed, thus setting count to 1. After the catch block executes, the try/catch block is exited and the message The exception is handled. appears on the Java console (see Figure 3.5).
Figure 3.5 Catching an exception.If you had not put ArithmeticException in the catch block, the program would likely have crashed. You can catch all exceptions by using the following code:
try { // possible nasty code } catch(Exception e) { // code to handle the exception }
The Exception parameter in the catch block is used to catch any exception that might be generated within a try block.
Bulletproof Code
It's a good idea to use a combination of the methods described here to make your program as bulletproof to your user as possible.