Error Handling
It is extremely rare for a class to be written perfectly the first time. In most, if not all, situations, things will go wrong. Any developer who does not plan for problems is courting danger.
Assuming that your code has the capability to detect and trap an error condition, you can handle the error in several ways: On page 223 of their book Java Primer Plus, Tyma, Torok, and Downing (9781571690623) 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 (978-1571691347), Gilbert and McCarty expand on this theme by adding the choice of throwing an exception:
- Ignore the problem—not 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? It is obvious that you should not ignore any known 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, and that can be much worse than a program crash.
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 a problem exists. In this case, the application 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 and forcing a system restart.
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 conditional 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 the variable a to 1, at least the system will not crash. However, setting a to 1 might not be a proper solution because the result would be incorrect. The better solution would be to prompt the user to reenter the proper input value.
Although the error-checking techniques mentioned previously are preferable to doing nothing, they still have a few 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, and often the operating system itself can alert you to problems that it detects.
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#, C++, Objective-C, and Visual Basic, exceptions are handled by the keywords catch and throw. This might sound like a baseball game, but the key concept 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 the structure for a Java try/catch block:
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 handles 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—that is, an application crash.)
- 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.
Suffice it 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.");
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 (crashed). 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. The logical flow of this process is illustrated in 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.