Section 10.5. Error Handling and Robust Program Design


[Page 488 (continued)]

10.5. Error Handling and Robust Program Design

An important element of program design is to develop appropriate ways of handling erroneous and exceptional conditions. As we have seen, the JVM will catch any unchecked exceptions that are not caught by the program itself. For your own (practice) programs, the best design may simply be to use Java's default exception handling. The program will terminate when an exception is thrown, and then you can debug the error and recompile the program.

Let Java do it?



[Page 489]

On the other hand, this strategy would be inappropriate for commercial software, which cannot be fixed by its users. A well-designed commercial program should contain exception handlers for those truly exceptional conditions that may arise.

In general there are three ways to handle an exceptional condition that is not already handled by Java (Table 10.3). If the exceptional condition cannot be fixed, the program should be terminated with an appropriate error message. If the exceptional condition can be fixed without invalidating the program, then it should be remedied and the program's normal execution should be resumed. Finally, if the exception cannot be fixed, but the program cannot be terminated, the exceptional condition should be reported or logged in some way, and the program should be resumed.

Table 10.3. Exception-handling strategies

Kind of Exception

Kind of Program

Action to Be Taken

Caught by Java

 

Let Java handle it

Fixable condition

 

Fix the error and resume execution

Unfixable condition

Stoppable

Report the error and terminate the program

Unfixable condition

Not stoppable

Report the error and resume processing


What action should we take?


Effective Design: Handling Exceptions

There are three general ways to handle exceptions: (1) report the exception and terminate the program; (2) fix the exceptional condition and resume normal execution; and (3) report the exception to a log and resume execution.


10.5.1. Print a Message and Terminate

Our illegal-argument example is a clear case of an exception that is best handled by terminating the program. The particular error is best left to Java's default exception handling, which will terminate the program when the exception is thrown. There is simply no way to satisfy the postcondition of the avgFirstN() method when N is less than or equal to 0. This type of error often calls attention to a design flaw in the program's logic that should be caught during program development. The throwing of the exception helps identify the design flaw.

Program development


Effective Design: Exceptions and Program Development

Java's built-in exception handling helps identify design flaws during program development. Your own use of exceptions should follow this approach.


Similar problems can (and often do) arise in connection with errors that are not caught by Java. For example, suppose that your program receives an erroneous input value whose use would invalidate the calculation it is making. This won't be caught by Java. But it should be caught by your program, and an appropriate alternative here is to report the error and terminate the program. Fixing this type of error may involve adding routines to validate the input data before they are used in the calculation.

Don't spread bad data!



[Page 490]

In short, rather than allow an erroneous result to propagate throughout the program, it is best to terminate the program.

Effective Design: Report and Terminate

If an unfixable exception arises in a program that can be terminated, it is better to report the error and terminate the program. That is better than allowing it to run with an erroneous value.


10.5.2. Log the Error and Resume

Of course, the advice to stop the program assumes that the program can be terminated reasonably. Some programssuch as programs that monitor the space shuttle or programs that control a nuclear magnetic resonance (NMR) machinecannot (and should not) be terminated because of such an error.

Such programs are called failsafe because they are designed to run without termination. For these programs, the exception should be reported in whatever manner is most appropriate, but the program should continue running. If the exceptional condition invalidates the program's computations, then the exception handler should make it clear that the results are tainted.

Failsafe programs


Other programssuch as programs that analyze a large transaction databaseshould be designed to continue processing after catching such errors. For example, suppose a large airline runs a program once a day to analyze daily ticketing transactions. This kind of program might use exceptions to identify erroneous transactions or transactions that involve invalid data of some sort. Because there are bound to be many errors of this kind in the database, it is not reasonable to stop the program. This kind of program shouldn't stop until it has finished processing all of the transactions. An appropriate action for this kind of program is to log the exceptions into a file and continue processing the transactions.

Programs that can't be stopped


Suppose a divide-by-zero error happened in one of these programs. In that case, you would override Java's default exception handling to ensure that the program is not terminated. More generally, it is important that these types of programs be designed to catch and report such exceptions. This type of exception handling should be built right into the program's design.

Effective Design: Report and Resume

If an unfixable exception arises in a program that cannot be terminated reasonably, the exception should be reported and the program should continue executing.


10.5.3. Fix the Error and Resume

As an example of a problem that can be addressed as the program runs, consider the task of inputting an integer into a text field. As you have probably experienced, if a program is expecting an integer and you attempt to input something other than an integer, a NumberFormatException is generated and the program will terminate. For example, if you enter "$55" when prompted to input an integer dollar amount, this will generate an exception when the Integer.parseInt() method is invoked. The input string cannot be parsed into a valid int. However, this is the kind of error that can be addressed as the program is running.

Problem statement



[Page 491]

Let's design a special IntField that functions like a normal text field but accepts only integers. If the user enters a value that generates a NumberFormatException, an error message should be printed and the user should be invited to try again. As Figure 10.13 shows, we want this special field to be a subclass of JTextField and to inherit the basic JTextField functionality. It should have the same kind of constructors that a normal JTextField has. This leads to the definition shown in Figure 10.14.

Figure 10.13. An IntField is a JTextField that accepts only integers.


Figure 10.14. A NumberFormatException might be thrown by the Integer.parseInt() method in IntField.getInt().

import javax.swing.*; public class IntField extends JTextField {     public IntField () {         super();     }     public IntField (int size) {         super(size);     }     public int getInt() throws NumberFormatException {         return Integer.parseInt(getText());     } // getInt() } // IntField class 

Note that the constructor methods use super to call the JTextField constructor. For now, these two constructors should suffice. However, later in the chapter we will introduce a third constructor that allows us to associate a bound with the IntField.

What constructors do we need?


Our IntField class needs a method that can return its contents. This method should work like JTextField.getText(), but it should return a valid integer. The getInt() method takes no parameters and will return an int, assuming that a valid integer is typed into the IntField. If the user enters "$55," a NumberFormatException will be thrown by the Integer.parseInt() method. Note that getInt() declares that it throws this exception. This is not necessary because a NumberFormatException is not a checked exception, but it makes the code clearer.

What methods do we need?


Where and how should this exception be handled? The exception cannot easily be handled within the getInt() method. This method has to return an integer value. If the user types in a non-integer, there's no way to return a valid value. Therefore, it's better just to throw the exception to the calling method, where it can be handled more easily.


[Page 492]

In a GUI application or applet, the calling method is likely to be an actionPerformed() method, such as the following:

public void actionPerformed(ActionEvent e) {   try {     userInt = intField.getInt();     message = "You input " + userInt + " Thank you.";   } catch (NumberFormatException ex) {     JOptionPane.showMessageDialog(this,      "The input must be an integer. Please re-enter.");   } finally {       repaint();   } } // actionPerformed() 


The call to getInt() is embedded in a try/catch block. This leads to the design summarized in Figure 10.15. The IntField throws an exception that is caught by the applet, which then displays an error message.

Figure 10.15. If the user types a noninteger into an IntField, it will throw a NumberFormatException. The applet will display an error message in a JOptionPane (a dialog window).


If the user inputs a valid integer, the program will just report a message that displays the value. A more real-world example would make a more significant use of the value. On the other hand, if the user types an erroneous value, the program will pop up the dialog box shown in Figure 10.16. (See the "From the Library" section of this chapter for more on dialog boxes.) When the user clicks the OK button, the program will resume normal execution, so that when an exception is raised, the enter value is not used, and no harm is done by an erroneous value. The user can try again to input a valid integer. Note that the finally clause repaints the GUI. In this case, repainting would display the appropriate message on the applet or the application.

Figure 10.16. This exception handler opens a dialog box to display an error message.
(This item is displayed on page 493 in the print version)


This is an example of what we might call defensive design. Defensive design is when we anticipate a possible input error and take steps to ensure that a bad value is not propagated throughout the program.

Defensive design: anticipating an exception


Effective Design: Defensive Design

Well-designed code should anticipate potential problems, especially potential input problems. Effective use of exceptions can help with this task.



[Page 493]

Admittedly, the sense in which the error here is "fixed" is simply that the user's original input is ignored and reentered. This is a legitimate and simple course of action for the particular situation. It is far preferable to ignoring the exception. If the program does not handle this exception itself, Java will catch it and will print a stack trace and terminate the program. That would not be a very user-friendly interface!

Clearly, this is the type of exceptional condition that should be anticipated during program design. If this happens to be a program designed exclusively for your own use, then this type of exception handling might be unnecessary. But if the program is meant to be used by others, it is important that it be able to handle erroneous user input without crashing.

Anticipating exceptions


Effective Design: Fixing an Exception

If a method can handle an exception effectively, it should handle it locally. This is both clearer and more efficient.


Effective Design: Library Exception Handling

Many of Java's library classes do not handle their own exceptions. The thinking behind this design is that the user of the class is in a better position to handle the exception in a way that is appropriate for the application.


10.5.4. To Fix or Not to Fix

Let's now consider a problem where it is less clear whether an exception can be successfully fixed "on the fly." Suppose you have a program that contains an array of Strings initially created with just two elements.

String list[] = new String[2]; 


If an attempt is made to add more than two elements to the array, an ArrayIndexOutOfBoundsException will be raised. This exception can be handled by extending the size of the array and inserting the element. Then the program's normal execution can be resumed.


[Page 494]

To begin creating such a program, let's first design a method that will insert a string into the array. Suppose that this is intended to be a private method that will only be used within the program. Also, let's suppose that the program maintains a variable, count, that tracks how many values have been stored in the array. Therefore, it will not be necessary to pass the array as a parameter. So we are creating a void method with one parameter, the String to be inserted:

private void insertString(String str) {                                    // Might throw ArrayIndexOutOfBoundsException     list[count] = str;     ++count; } 


Problem statement


The comment notes where an exception might be thrown.

Can we handle this exception? When this exception is raised, we could create a new array with one more element than the current array. We could copy the old array into the new array and then insert the String in the new location. Finally, we could set the variable list, the array reference, so that it points to the new array. Thus, we could use the following try/catch block to handle this exception:

private void insertString(String str) {     try {         list[count] = str;     } catch (ArrayIndexOutOfBoundsException e) {                                               // Create a new array         String newList[] = new String[list.length+1];         for (int k = 0; k < list.length; k++) // Copy array             newList[k] = list[k];         newList[count] = str;                 // Insert into new array         list = newList;                       // Make old point to new     } finally {                               // Since the exception is now fixed         count++;                              // Increase the count     } } // insertString() 


Algorithm design


The effect of the catch clause is to create a new array, still referred to as list, but that contains one more element than the original array.

Note the use of the finally clause here. For this problem it's important that we increment count in the finally clause. This is the only way to guarantee that count is incremented exactly once whenever an element is assigned to the array.

The design of the FixArrayBound class is shown in Figure 10.17. It provides a simple GUI interface that enables you to test the insertString() method. This program has a standard Swing interface, using a JFrame as the top-level window. The program's components are contained within a JPanel that's added to the JFrame in the main() method.


[Page 495]

Figure 10.17. The FixArrayBound class uses exception handling to extend the size of an array each time a new element is inserted.


Each time the user types a string into the text field, the actionPerformed() method calls the insertString() method to add the string to the array. On each user action, the JPanel is repainted. The paintComponent() method simply clears the panel and then displays the array's elements (Fig. 10.18).

Figure 10.18. The strings displayed are stored in an array that is extended each time a new string is entered.


Debugging Tip: Clearing the JPanel

Swing components, such as JPanel, do not automatically clear their backgrounds. This must be done explicitly in the paintComponent() method.


The complete implementation of FixArrayBound is given in Figure 10.19. This example illustrates how an exception can be handled successfully and the program's normal flow of control resumed. However, the question is whether such an exception should be handled this way.

Figure 10.19. FixArrayBound increases the size of the array when a ArrayIndexOutOfBoundsException is raised.

import java.awt.*; import java.awt.event.*; import javax.swing.*; public class FixArrayBound extends JPanel implements ActionListener {   public static final int WIDTH = 350, HEIGHT = 100;   private JTextField inField = new JTextField(10); 
[Page 496]
private JLabel prompt = new JLabel("Input a word and type <ENTER>: "); // Initially list has 2 elements private String list[] = new String[2]; private int count = 0; public FixArrayBound() { inField.addActionListener(this); add(prompt); add(inField); setSize(WIDTH, HEIGHT); } // FixArrayBound() public void paintComponent(Graphics g) { g.setColor(getBackground()); // Clear the background g.fillRect(0, 0, WIDTH, HEIGHT); g.setColor(getForeground()); String tempS = ""; for (int k = 0; k < list.length; k++) tempS = tempS + list[k] + " "; g.drawString(tempS, 10, 50); } // paintComponent() private void insertString(String str) { try { list[count] = str; } catch (ArrayIndexOutOfBoundsException e) { String newList[] = new String[list.length+1]; // New array for (int k = 0; k < list.length; k++) // Copy old to new newList[k] = list[k]; newList[count] = str; // Insert item into new list = newList; // Make old point to new } finally { // The exception is now fixed count++; // so increase the count } } // insertString() public void actionPerformed(ActionEvent evt) { insertString(inField.getText()); inField.setText(""); repaint(); } // actionPerformed() public static void main( String args[] ) { JFrame f = new JFrame("Array Fixer"); FixArrayBound panel = new FixArrayBound(); f.getContentPane().add(panel); f.setSize(panel.WIDTH, panel.HEIGHT); f.setVisible(true); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); // Quit the application } }); } // main() } // FixArrayBound class


[Page 497]

Unfortunately, this is not a well-designed program. The array's initial size is much too small for the program's intended use. Therefore, the fact that these exceptions arise at all is the result of poor design. In general, exceptions should not be used as a remedy for poor design.

Poor program design


Effective Design: Truly Exceptional Conditions

A well-designed program should use exception handling to deal with truly exceptional conditions, not to process conditions that arise under normal or expected circumstances.


For a program that uses an array, the size of the array should be chosen so that it can store all the objects required by the program. If the program is a failsafe program, and therefore cannot afford to crash, then something like the previous approach might be justified, provided this type of exception occurs very rarely. Even in that case it would be better to generate a message that alerts the program's user that this condition has occurred. The alert will indicate a need to modify the program's memory requirements and restart the program.

Proper array usage


If it is not known in advance how many objects will be stored in an array, a better design would be to make use of the java.util.Vector class (see "From the Java Library" in Chapter 9). Vectors are designed to grow as new objects are inserted. In some ways the exception-handling code in our example mimics the behavior of a vector. However, the Vector class makes use of efficient algorithms for extending its size. By contrast, exception-handling code is very inefficient. Because exceptions force the system into an abnormal mode of execution, it takes considerably longer to handle an exception than it would to use a Vector for this type of application.

Choosing the correct data structure


Effective Design: Appropriate Data Structure

A major component of problem solving is choosing the best way to represent the data. A vector should be used as an array structure whenever the size of the array will grow and shrink dynamically during the program's execution.


Self-Study Exercise

Exercise 10.13

For each of the following exceptions, determine whether it can be handled in such a way that the program can be resumed or whether the program should be terminated:

  1. A computer game program detects a problem with one of its GUI elements and throws a NullPointerException.

  2. A factory assembly-line control program determines that an important control value has become negative and generates an ArithmeticException.

  3. A company's Web-based order form detects that its user has entered an invalid String and throws a SecurityException.




Java, Java, Java(c) Object-Orienting Problem Solving
Java, Java, Java, Object-Oriented Problem Solving (3rd Edition)
ISBN: 0131474340
EAN: 2147483647
Year: 2005
Pages: 275

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net