| < Day Day Up > |
10.2 Handling Multiple Types of Exceptions
Our first exception example was overly simplistic. What happens if our method generates more than one kind of error? Are they all sent to the same
catch
block? Well, that's up to the developer; they
Suppose we wanted a finer-grained set of error messages in our setWidth( ) method: one for general invalid data, one for too small a width, and one for too large a width. Our revised setWidth( ) method might look like this:
public function setWidth (w:Number):Void {
if (isNaN(w) w == null) {
throw new Error("Illegal Box dimension specified.");
} else if (w <= 0) {
throw new Error("Box dimensions must be greater than 0.");
} else if (w > Number.MAX_VALUE) {
throw new Error("Box dimensions must be less than Number.MAX_VALUE.");
}
width = w;
}
To handle the three possible error messages in our new
setWidth( )
message, we might be tempted to code our
try/catch/finally
statement as
try {
b.setWidth(someWidth);
// If we get this far, no exception occurred; proceed as planned.
trace("Width set successfully.");
} catch (e:Error) {
switch (e.message) {
case "Illegal Box dimension specified.":
trace("An error occurred: " + e.message);
trace("Please specify a valid dimension.");
break;
case "Box dimensions must be greater than 0.":
trace("An error occurred: " + e.message);
trace("Please specify a larger value.");
break;
case "Box dimensions must be less than Number.MAX_VALUE.":
trace("An error occurred: " + e.message);
trace("Please specify a smaller value.");
break;
}
}
Admittedly, that code does work, but it's fraught with problems. First, and most serious, the errors are distinguished from one another only by the text in a string that is hidden within the
Box
class. Each time we want to check the type of an error, we have to look inside the
Box
class and find the message string. Using the string for error identification across multiple
Fortunately, there's a formal (and elegant) way to handle multiple exception types. Each
try
block can have any number of supporting
catch
blocks. When an exception is thrown in a
try
block that has multiple
catch
blocks, the interpreter executes the
catch
block whose parameter's datatype matches the datatype of the value originally thrown. The datatypes of the
catch
parameter and thrown value are
Here's the general syntax of a try statement with multiple catch blocks:
try {
// Code that might generate an exception.
} catch (e:
ErrorType1
) {
// Error-handling code for
ErrorType1
.
} catch (e:
ErrorType2
) {
// Error-handling code for
ErrorType2
.
} catch (e:
ErrorTypen
) {
// Error-handling code for
ErrorTypen
.
}
If a
throw
statement in the
throw new ErrorType1( ); If a throw statement were to pass an expression of type ErrorType2 , then the second catch clause would be executed, and so on. As we learned earlier, in ActionScript the throw statement expression can belong to any datatype. However, as an OOP best practice, we should throw only instances of the Error class or one of its subclasses (this best practice follows Java, where throw can be used only with Throwable and its subclasses). If we want to throw multiple kinds of exceptions in an application, we should define an Error subclass for each kind of exception. It is up to you as the developer to decide what level of granularity you require (i.e., to what degree you need to differentiate among different error conditions). However, don't confuse the following discussion of how to implement granularity in error handling as an insistence that you must implement such granularity. 10.2.1 Determining Exception Type Granularity
Should you define an
Error
subclass for each error condition? Typically, no, you won't need that level of granularity because in many cases multiple error conditions can be treated in the same way. If you don't need to differentiate among multiple error conditions, you can
That said, you should define a separate
Error
subclass for each error condition that you want to distinguish from other possible conditions. To help you understand when you should create a new subclass for a given error condition and to
Earlier we generated three exceptions from the
Box.setWidth( )
method: one for general invalid data, one for too small a width, and one for too large a width. All three
Box
-
public function setWidth (w:Number):Void {
if (isNaN(w) w == null) {
throw new Error("Illegal Box dimension specified.");
} else if (w <= 0) {
throw new Error("Box dimensions must be greater than 0.");
} else if (w > Number.MAX_VALUE) {
throw new Error("Box dimensions must be less than Number.MAX_VALUE.");
}
width = w;
}
In the preceding code, to differentiate
Box
exceptions from all other exceptions in our application, we use the
Error
class's
message
property, which, as we just learned, made our exceptions
// Code in BoxDimensionException.as:
class BoxDimensionException extends Error {
public var message:String = "Illegal Box dimension specified.";
}
With our BoxDimensionException class in place, our Box.setWidth( ) method can throw its very own type of error, as follows:
public function setWidth (w:Number):Void {
if (isNaN(w) w == null) {
// Throw a
BoxDimensionException
instead of an
Error
.
throw new BoxDimensionException( );
} else if (w <= 0) {
throw new BoxDimensionException( );
} else if (w > Number.MAX_VALUE) {
throw new BoxDimensionException( );
}
width = w;
}
Notice that the preceding method definition throws the same error type (
BoxDimensionException
) for all three
Box
-related error conditions. As developers of the
Box
class, we now face the crux of the error granularity issue. We must decide not only how
Let's consider the preceding options in
10.2.1.1 Using a single custom exception typeOur first option is to accept the preceding setWidth( ) definition, which throws the same error type ( BoxDimensionException ) for all three Box -related error conditions. Because the method uses BoxDimensionException and not Error to throw exceptions, Box exceptions are already distinguishable from other generic exceptions. Users of the setWidth( ) method can use code such as the following to discriminate between Box -related errors and other generic errors:
var b:Box = new Box( );
var someWidth:Number = -10;
try {
// This call to
setWidth( )
will generate a
BoxDimensionException
.
b.setWidth(someWidth);
// Other statements in this
try
block might generate other generic errors.
// For demonstration purposes, we'll throw a generic error directly.
throw new Error("A generic error.");
} catch (e:BoxDimensionException) {
// Handle
Box
dimension errors here.
trace("An error occurred: " + e.message);
trace("Please specify a valid dimension.");
} catch (e:Error) {
// Handle all other errors here.
trace("An error occurred: " + e.message);
}
For many applications, the level of error granularity provided by BoxDimensionException is enough. In such a case, we should at least refactor the setWidth( ) method so that it doesn't contain redundant code (throwing the BoxDimensionException three times). Here's the refactored code (which was option 2 in our earlier list):
public function setWidth (w:Number):Void {
if ((isNaN(w) w == null) (w <= 0) (w > Number.MAX_VALUE)) {
throw new BoxDimensionException( );
}
width = w;
}
10.2.1.2 Using configurable debugging messages
Now let's turn to option 3 (adding configurable debugging messages to the
BoxDimensionException
class). Options 1 and 2 let us distinguish a
Box
exception from other exceptions in the application but didn't help us distinguish, say, an overflow exception from a
class BoxDimensionException extends Error {
// The default error message still stands.
public var message:String = "Illegal Box dimension specified.";
// Provide a constructor that allows a custom message to be supplied.
public function BoxDimensionException (boxErrMsg:String) {
super(boxErrMsg);
}
}
To make use of our adjusted BoxDimensionException class in setWidth( ) , we revert to our setWidth( ) code used in option 1 and add debugging error messages, as follows:
public function setWidth (w:Number):Void {
if (isNaN(w) w == null) {
// The default error message is fine in this case,
// so don't bother specifying a custom error message.
throw new BoxDimensionException( );
} else if (w <= 0) {
// Here's the custom "too small" error message.
throw new BoxDimensionException("Box dimensions must "
+ "be greater than 0.");
} else if (w > Number.MAX_VALUE) {
// Here's the custom "too big" error message.
throw new BoxDimensionException("Box dimensions must be less "
+ "than Number.MAX_VALUE.");
}
width = w;
}
Now that
setWidth( )
var b:Box = new Box( );
var someWidth:Number = -10;
try {
b.setWidth(someWidth);
} catch (e:BoxDimensionException) {
// Handle
Box
dimension errors here.
// In this case, the helpful debugging output is:
// An error occurred: Box dimensions must be greater than 0.
trace("An error occurred: " + e.message);
} catch (e:Error) {
// Handle all other errors here.
trace("An error occurred: " + e.message);
}
10.2.1.3 Multiple custom BoxDimensionException subclassesOption 3 (adding configurable debugging messages to the BoxDimensionException class) helped us investigate a problem in our code during development, but it doesn't help the program take independent action to recover from individual Box errors. To allow the program to execute independent code branches based on the type of Box error thrown, we need custom BoxDimensionException subclasses (option 4).
To allow our program to independently differentiate among the Box class's three error conditions, we create three custom exception types by creating three Error subclasses: BoxDimensionException , BoxUnderZeroException , and BoxOverflowException . In our case, the BoxDimensionException class extends Error directly. The BoxUnderZeroException and BoxOverflowException classes both extend BoxDimensionException because we want to differentiate these specific error types from a more general invalid dimension exception. Hence, the datatype hierarchy is shown in Figure 10-1. Figure 10-1. The custom exception class hierarchy
Here's the source code for our three Box Error subclasses:
// Code in BoxDimensionException.as:
class BoxDimensionException extends Error {
public var message:String = "Illegal Box dimension specified.";
}
// Code in BoxUnderZeroException.as:
class BoxUnderZeroException extends BoxDimensionException {
public var message:String = "Box dimensions must be greater than 0.";
}
// Code in BoxOverflowException.as:
class BoxOverflowException extends BoxDimensionException {
public var message:String = "Box dimensions must be less "
+ "than Number.MAX_VALUE.";
}
Each class specifies the value of its message property directly and does not allow it to be customized on a per-use basis. Truth be told, now that we're dealing with class-based errors instead of string-based errors, the message property is completely secondary. What matters is the datatype of the exception generated by the throw statement. When catching any of the preceding Box exceptions, our program will use the exception's datatype (not the message property) to distinguish between the three kinds of exceptions. Now that we have three exception types, let's update our setWidth( ) method to throw those types. Here's the code:
public function setWidth (w:Number):Void {
if (isNaN(w) w == null) {
throw new BoxDimensionException( );
} else if (w <= 0) {
throw new BoxUnderZeroException( );
} else if (w > Number.MAX_VALUE) {
throw new BoxOverflowException( );
}
width = w;
}
Notice that we do not pass any error description to the various Box exception constructors. Once again, the description of each exception is set by each custom Error subclass using its message property.
With each
Box
exception represented by its own class, the errors that can be generated by the
setWidth( )
method are well-known to programmers working with
Box
instances. The exception types are visible outside the
Box
class, exposed appropriately to programmers working on the application. Just by
Now let's see how to add branching logic to our code based on the types of exceptions that can be generated by Box.setWidth( ) . Pay close attention to the datatype of each catch block parameter:
var b:Box = new Box( );
var someWidth:Number = -10;
try {
b.setWidth(someWidth);
} catch
(e:BoxOverflowException)
{
// Handle overflow.
trace("An error occurred: " + e.message);
trace("Please specify a smaller value.");
} catch
(e:BoxUnderZeroException)
{
// Handle under zero.
trace("An error occurred: " + e.message);
trace("Please specify a larger value.");
} catch
(e:BoxDimensionException)
{
// Handle general dimension errors.
trace("An error occurred: " + e.message);
trace("Please specify a valid dimension.");
}
In the preceding code, if the setWidth( ) method generates a BoxOverflowException , the first catch block executes. If setWidth( ) generates a BoxUnderZeroException , the second catch block executes. And if setWidth( ) generates a BoxDimensionException , the third catch block executes. Notice that the error datatypes in the catch blocks progress from specific to general. When an exception is thrown, the catch block executed is the first one that matches the datatype of the exception. Hence, if we changed the datatype of the first catch block parameter to BoxDimensionException , the first catch block would execute for all three kinds of exceptions! (Remember, BoxDimensionException is the superclass of both BoxUnderZeroException and BoxOverflowException , so they are considered matches for the BoxDimensionException datatype.) In fact, we could prevent all of the catch blocks from executing simply by adding a new first catch block with a parameter datatype of Error :
try {
b.setWidth(someWidth);
} catch (e:Error) {
// Handle
all
errors. No other
catch
blocks will ever execute.
trace("An error occurred:" + e.message);
trace("The first catch block handled the error.");
} catch (e:BoxOverflowException) {
// Handle overflow.
trace("An error occurred: " + e.message);
trace("Please specify a smaller value.");
} catch (e:BoxUnderZeroException) {
// Handle under zero.
trace("An error occurred: " + e.message);
trace("Please specify a larger value.");
} catch (e:BoxDimensionException) {
// Handle general dimension errors.
trace("An error occurred: " + e.message);
trace("Please specify a valid dimension.");
}
Obviously, the addition of the first
catch
clause in the preceding code is self-defeating, but it does
try {
b.setWidth(someWidth);
} catch (e:BoxOverflowException) {
// Handle overflow.
trace("An error occurred: " + e.message);
trace("Please specify a smaller value.");
} catch (e:BoxUnderZeroException) {
// Handle under zero.
trace("An error occurred: " + e.message);
trace("Please specify a larger value.");
} catch (e:BoxDimensionException) {
// Handle general dimension errors.
trace("An error occurred: " + e.message);
trace("Please specify a valid dimension.");
} catch (e:Error) {
// Handle any errors that don't qualify as
BoxDimensionException
errors.
}
Remember, error granularity is a choice. In option 4 we created a custom
Error
subclass for each variety of exception generated by the
Box
class. This approach gives our program the greatest ability to respond independently to different types of errors. But such flexibility is not
{% if main.adsdop %}{% include 'adsenceinline.tpl' %}{% endif %} To work around the problem, we can include the full package name when specifying a custom error class as a catch block parameter. For example:
import somePackage.SomeCustomError;
try {
throw new SomeCustomError( );
} catch(e:somePackage.SomeCustomError) {
// Now that the package name is included, this code runs.
trace("Caught: " + e);
}
This bug is fixed in the Flash MX 2004
http://macromedia.com/support/flash/downloads.html |
| < Day Day Up > |