Lecture : Handling Errors Gracefully
In the previous lecture:
In this lecture:
What happens when a program enters an unexpected state?
The machine freezes? The machine displays the "blue screen of death"? The application quits and the user loses all of their work?
The blue screen of death. |
The sad Mac. |
The Mac Bomb |
We would like our programs to behave nicely, even when things go wrong.
What is an exception?
When an exceptional circumstance such as an error occurs, a well written Java program will:
An exception is a signal that indicates that a problem has occurred.
Throw an exception
Java code that identifies a problem uses the throw statement to signal the problem.
throw expression; |
The expression must evaluate to an exception object describing the problem.
Exception handlers are associated with blocks of code (see below).
Finding an appropriate exception handler: The interpretter first looks to see if the exception is handled by the current block, if not, it looks at the enclosing block, if not, it keeps looking at all of the enclosing blocks of code from the throw, outwards. If it still hasn't found a handler, the interpretter will then check the block that called the current method for an appropriate exception handler, and so on outwards until the interpretter gets back to the main() method.
If it finds an exception handler for the exception: this is executed, and then the program resumes running normally from the location immediately after the exception handler code.
If it does not find an exception handler for the exception: the Java interpretter will print out an exception error message to the console and the program execution will be aborted.
Exception Types
The java.lang.Throwable class has two main subclasses: java.lang.Error and java.lang.Exception.
Exceptions derived from class Error indicate an unrecoverable problem (such as a corrupt file, memory problems etc.).
Exceptions derived from class Exception indicate conditions that may be caught and handled (for instance java.io.EOFException indicates the end of a file, java.lang.ArrayIndexOutOfBoundsException indicates an attempt to access an array cell using an invalid index.
All subclasses of Throwable include a string that holds an error message describing the problem that has occurred. This string is set when the exception object is created.
The message may be read using the exception object's getMessage() method.
Try / catch / finally
To set up code to catch an exception use syntax like this:
try { // Here goes code that you have written that will mostly work well. // Sometimes a problem may occur though and so... if (aBigProblemOccurs() == true) { throw new MyBigException("a terrible exceptional error has occurred"); } else if (aSmallProblemOccurs() == true) { throw new MySmallException("a little problem has occurred"); } } catch (MyBigException bigExceptionObjectName) { // Here goes code that handles the exception object of type // MyBigException (or a subclass). // The exception object is called bigExceptionObjectName in this block of code. } catch (MySmallException smallExceptionObjectName) { // Here goes code that handles the exception object of type // MySmallException (or a subclass). // The exception object is called smallExceptionObjectName in this block of code. } finally { // Here goes "clean up code" that will *always* be executed // after the try clause exits. This code will be executed if : // no exceptions are thrown // a break, continue or return statement in the try block is executed // an exception is thrown and handled above // an exception is thrown and not handled above // The only way this finally clause will not be executed is if the program calls System.exit(). } |
try
The code encapsulated between the { brackets } of a try block has associated with it the catch and finally statements that follow.
By itself, try { } doesn't do anything much.
catch
After the try block comes an optional set of catch blocks that handle the exceptions.
Each catch statement has a single argument of the exception type (the type must be derived from java.lang.Throwable) that the catch handles and a name for the exception object.
If an exception is thrown from a try block, the interpretter jumps to the first catch block it finds with an exception type that matches (or is a super-class of) the exception thrown.
The code in the catch block handles the exception. For example, it may request the user to perform some action (such as to re-enter a misspelled file name).
If no catch block is associated with an exception that was thrown in the try block, the interpretter keeps looking for a handler as discussed in the section above.
finally
Code in the finally block will always be executed after the try block unless the program calls System.exit() before the finally block is reached. This code will be executed regardless of whether or not an exception was thrown or caught.
The finally block can be used to clean up resources (close a file, release a lock on a resource, close a network connection etc.).
If no exception handler associated with the try block catches an exception that was thrown, control passes to the finally clause of the block before the interpretter looks at the enclosing blocks and methods for an appropriate handler.
If the finally clause throws its own exception, the interpretter abandons its attempts to handle any pre-exisiting unhandled exceptions and attempts to finad a handler for the new exception.
If the finally clause includes a return statement, the method enclosing the clause will return normally, regardless of whether or not any pre-exisiting exceptions have been handled or not.
An example...
import java.math.*; public class SquareRootIt { public static double safeSquareRoot(int x) { if (x<0) throw new IllegalArgumentException("x must be a non-negative number"); return Math.sqrt(x); } public static void main(String[] args) { try { // read the command-line argument and calculate its square root int x = Integer.parseInt(args[0]); System.out.println(x + " doubled = " + safeSquareRoot(x)); } // catch the exception where no command-line argument was specified // args[0] was undefined catch (ArrayIndexOutOfBoundsException e) { System.out.println("Usage: java SquareRootIt <number>"); System.out.println("Please specify an argument on the command line."); } |
Make SqareRootIt.java and try the program above with various inputs on the command line.
(Note how command line arguments are read in and processed by the program. This is a useful thing to know!)
Some common errors made using interactive software (and how they can be managed).
Users may enter an incorrect password due only to an inadvertent tap of the capslock key... notify them that the key has been hit in a location near where they are looking, without interrupting the flow of thought or the password entry process. |
Users may easily misstype file names. Provide a dialog box that constrains them to select a file that they locate. If the file isn't there, they can't click on it, so they can't make a mistake. Grey out and make files that can't be opened in the current application unselectable. This will prevent users from opening files that might cause the application to crash and give them visual feedback about the file's unsuitability. Provide a cancel button to allow the user to switch out of file opening mode. Users sometimes change their minds! *Interactive selection of file names can be slow for experienced users and impossible to script / automate. Command-line selection by explicit naming can be much faster, for instance, allow users to execute the program and give a command line argument for the file they wish to open in a text editor:
|
The Rubbish Bin (Trash Can in American) provides a temporary location for deleted files. If a user mistakenly deletes something they can recover it from this location. Undo and Step Backward are essential features for error recovery. Redo is also useful in case the user discovers that the previous state was not acceptable and they would like to maintain the current program state after all. Revert To Saved also allows quick return to a previous program state. Provide (and display) mnemonic keyboard shortcuts for users in your menus and on tool tips. This can speed up the process for those with a high level of skill and allow users with a low level of skill to become more proficient. This makes it easier for users to make errors (since they aren't selecting from a menu with clearly labelled items) but that is an acceptable price to pay in cases where experienced users would become frustrated with a slow cursor-based selection interface. |
Modal dialogue boxes prevent users from clicking on or activating anything before they acknowledge the dialogue box by clicking its "OK" button. This can be exceedingly annoying. It can be better to allow users to leave the dialogue box open, switch to another process such as the shell/operating system's file manipulation utilities etc. before forcing them to quite the application. That is, make the dialogue box non-modal. If at all possible, before quitting the program, even if you can't continue properly or safely, at least give the user a chance to save their work or have the software automatically save a back-up (emergency) copy of the current file, whatever state it is in, to a file name that doesn't over-write an existing file. Tell the user you are doing this to put them at ease. Obviously, this isn't always possible. Still, do the best you can for your users. Make your error messages useful to users, or don't display anything other than an apology! |