Error recovery and avoidance

Một phần của tài liệu Objects first with java a practical introduction using bluej 5th edition (Trang 471 - 474)

12.7.4 Assertions and the BlueJ unit-testing framework

In Chapter 7, we introduced the support that BlueJ provides for the JUnit unit-testing framework. That support is based on the assertion facility we have been discussing in this section. Methods from the framework, such as assertEquals, are built around an asser- tion statement that contains a boolean expression made up from their parameters. If JUnit test classes are used to test classes containing their own assertion statements, then assertion errors from these statements will be reported in the test-results window along with test- class assertion failures. The address-book-junit project contains a test class to illustrate this combination. The testAddDetailsError method of AddressBookTest will trigger an assertion error, because addDetails should not be used to change existing details (see Exercise 12.33).

12.8 Error recovery and avoidance

So far, the main focus of this chapter has been on the problem of identifying errors in a server object and ensuring that any problem is reported back to the client if appropriate. There are two complementary issues that go with error reporting: error recovery and error avoidance.

12.8.1 Error recovery

The first requirement of successful error recovery is that clients take note of any error notifica- tion that they receive. This may sound obvious, but it is not uncommon for a programmer to assume that a method call will not fail and so not bother to check the return value. While ignor- ing errors is harder to do when exceptions are used, we have often seen the equivalent of the following approach to exception handling:

AddressDetails details = null;

try {

details = contacts.getDetails(. . .);

}

Exercise 12.38 Suppose that we decide to allow the address book to be indexed by address as well as name and phone number. If we simply add the following statement to the addDetails method

book.put(details.getAddress(), details);

do you anticipate that any assertions will now fail? Try it. Make any further necessary changes toAddressBook to ensure that all of the assertions are now successful.

Exercise 12.39 ContactDetails are immutable objects—that is, they have no muta- tor methods. How important is this fact to the internal consistency of an AddressBook?

Suppose the ContactDetails class had a setPhone method, for instance? Can you devise some tests to illustrate the problems this could cause?

catch(Exception e) {

System.out.println("Error: " + e);

}

String phone = details.getPhone();

The exception has been caught and reported, but no account has been taken of the fact that it is probably incorrect just to carry on regardless.

Java’s try statement is the key to supplying an error-recovery mechanism when an exception is thrown. Recovery from an error will usually involve taking some form of corrective action within the catch block and then trying again. Repeated attempts can be made by placing the try statement in a loop. An example of this approach is shown in Code 12.18, which is an expanded version of Code 12.9. The efforts to compose an alternative filename could involve trying a list of possible folders, for instance, or prompting an interactive user for different names.

// Try to save the address book.

boolean successful = false; int attempts = 0;

do { try {

addressbook.saveToFile(filename);

successful = true; }

catch(IOException e) {

System.out.println("Unable to save to " + filename);

attempts++;

if(attempts < MAX_ATTEMPTS) { filename = an alternative file name;

} }

} while(!successful && attempts < MAX_ATTEMPTS);

if(!successful) {

Report the problem and give up;

} Code 12.18

An attempt at error recovery

Although this example illustrates recovery for a specific situation, the principles it illustrates are more general:

■ Anticipating an error, and recovering from it, will usually require a more complex flow of control than if an error cannot occur.

■ The statements in the catch block are key to setting up the recovery attempt.

■ Recovery will often involve having to try again.

■ Successful recovery cannot be guaranteed.

■ There should be some escape route from endlessly attempting hopeless recovery.

12.8 Error recovery and avoidance | 445

There won’t always be a human user around to prompt for alternative input. It might be the client object’s responsibility to log the error so that it can be investigated.

12.8.2 Error avoidance

It should be clear that arriving at a situation where an exception is thrown will be, at worst, fatal to the execution of a program and, at best, messy to recover from in the client. It can be simpler to try to avoid the error in the first place, but this often requires collaboration between server and client.

Many of the cases where an AddressBook object is forced to throw an exception involve null parameter values passed to its methods. These represent logical programming errors in the cli- ent that could clearly be avoided by simple prior tests in the client. Null parameter values are usually the result of making invalid assumptions in the client. For instance, consider the follow- ing example:

String key = database.search(zipCode);

ContactDetails university = contacts.getDetails(key);

If the database search fails, then the key it returns may well be either blank or null. Passing that result directly to the getDetails method will produce a runtime exception. However, using a simple test of the search result, the exception can be avoided and the real problem of a failed zip code search can be addressed instead:

String key = database.search(zipCode);

if(key != null && key.length() > 0) {

ContactDetails university = contacts.getDetails(key);

. . .. ..

} else {

Deal with the zipcode error . . . }

In this case, the client could establish for itself that it would be inappropriate to call the serv- er’s method. This is not always possible, and sometimes the client must enlist the help of the server.

Exercise 12.33 established the principle that the addDetails method should not accept a new set of details if one of the key values is already in use for another set. In order to avoid an inappropriate call, the client could make use of the address book’s keyInUse method, as follows:

// Add what should be a new set of details to the address book.

if(contacts.keyInUse(details.getName()) {

contacts.changeDetails(details.getName(), details);

}

else if(contacts.keyInUse(details.getPhone()) {

contacts.changeDetails(details.getPhone(), details);

} else {

Add the details . . . }

Using this approach, it is clearly possible to completely avoid a DuplicateKeyException being thrown from addDetails, which suggests that it could be downgraded from a checked to an unchecked exception.

This particular example illustrates some important general principles:

■ If a server’s validity-check and state-test methods are visible to a client, the client will often be able to avoid causing the server to throw an exception.

■ If an exception can be avoided in this way, then the exception being thrown really represents a logical programming error in the client. This suggests use of an unchecked exception for such situations.

■ Using unchecked exceptions means that the client does not have to use a try statement when it has already established that the exception will not be thrown. This is a significant gain, because having to write try statements for “cannot happen” situations is annoying for a pro- grammer and makes it less likely that providing proper recovery for genuine error situations will be taken seriously.

The effects are not all positive, however. Here are some reasons why this approach is not always practical:

■ Making a server class’s validity-check and state-test methods publicly visible to its clients might represent a significant loss of encapsulation and result in a higher degree of coupling between server and client than is desirable.

■ It will probably not be safe for a server class to assume that its clients will make the neces- sary checks that avoid an exception. As a result, those checks will often be duplicated in both client and server. If the checks are computationally “expensive” to make, then duplication may be undesirable or prohibitive. However, our view would be that it is better to sacrifice efficiency for the sake of safer programming, where the choice is available.

Một phần của tài liệu Objects first with java a practical introduction using bluej 5th edition (Trang 471 - 474)

Tải bản đầy đủ (PDF)

(578 trang)