|
You may have read the recent JavaWorld article, "Java Tip 134: When Catching Exceptions, Don't Cast Your Net Too Wide" by Dave Schweisguth. That excellent article warned against catching java.lang.Exception or java.lang.Throwable as a bad, catch-all practice. Catching the most specific exception you can is important for code maintainability and future refactoring. However, this rule must be bent under special circumstances. Particularly, if you intend not to crash and retain your data structures' exception safety in the presence of callouts to external, potentially hostile code, you must intercept java.lang.Throwable regardless of the actual exception type being thrown. Last month's Java 101 article introduced you to exceptions. You learned that an exception diverges from normal execution and observed how C, C++, and Java programs handle exceptions. This month, I delve into Java's throw-object/catch-object exception-handling technique by focusing on that technique's exceptions class hierarchy, statements and clauses, and various rules. After completing this article, you will be able to incorporate Java's throw-object/catch-object exception-handling technique into your programs. Why is it thrown away? The stack trace is considered a transient property of an exception. When objects are sent across RMI, they are serialized -- converted into a stream of bytes that you can easily transmit over a network wire. This means that any object to be transmitted over RMI, including exceptions, must be serializable (by implementing the java.io.Serializable interface). Java programmers use the keyword transient to describe any field of a class that is considered temporary, such as a temporary file handle. Unfortunately for you, transient fields like the exception's stack trace are not serialized, and thus not reproduced on the new tier. This article discusses program failure in the exception context. After defining exception, I show how C, C++, and Java handle exceptions. Knowing how to handle exceptions in C and C++ gives you an appreciation for why exception handling works the way it does in Java and lets you compare/contrast different exception-handling techniques. Using exceptions correctly entails looking at error conditions from several perspectives -- from the perspectives of the class that throws the error, the classes that catch the exception, and the poor user who has to deal with the results. To write friendly programs, you must consider error conditions from all these points of view. As the Java virtual machine executes the bytecodes that represent a Java program, it may exit a block of code -- the statements between two matching curly braces -- in one of several ways. For one, the JVM simply could execute past the closing curly brace of the block of code. Or, it could encounter a break, continue, or return statement that causes it to jump out of the block of code from somewhere in the middle of the block. Finally, an exception could be thrown that causes the JVM either to jump to a matching catch clause, or, if there isn't a matching catch clause, to terminate the thread. With these potential exit points existing within a single block of code, it is desirable to have an easy way to express that something happened no matter how a block of code is exited. In Java, such a desire is expressed with a try-finally clause. From a mechanical viewpoint, runtime exceptions and checked exceptions differ in how you declare the method that throws the exception, and how you handle the exception when it's thrown. Consider the following checked exception definition: Compiler-enforced (checked) exceptions are instances of the Exception class or one of its subclasses -- excluding the RuntimeException branch. The compiler expects all checked exceptions to be appropriately handled. Checked exceptions must be declared in the throws clause of the method throwing them -- assuming, of course, they're not being caught within that same method. The calling method must take care of these exceptions by either catching or declaring them in its throws clause. Thus, making an exception checked forces the programmer to pay heed to the possibility of it being thrown. An example of a checked exception is java.io.IOException. As the name suggests, it throws whenever an input/output operation is abnormally terminated. Examine the following code: Exceptions have several benefits. First, they allow you to separate error handling code from normal code. You can surround the code that you expect to execute 99.9% of the time with a try block, and then place error handling code in catch clauses -- code that you don't expect to get executed often, if ever. This arrangement has the nice benefit of making your "normal" code less cluttered. Each item is relevant to a different party. Software entities care about the exception class -- the JVM and the code that calls the throwing method use it to determine how to handle the exception. The other two items are relevant to people -- a developer or support engineer analyzes the stack trace to debug the problem, and a user or developer examines the error message. Each party must receive the information it needs to effectively deal with the error. It has been brought to my attention that some C++ compilers, such as Microsoft's Visual C++ compilers, make it possible to handle flawed code exceptions. If you have a C++ compiler, please consult your compiler's documentation to see if that is true for you." Even if we fix the way an exception should be handled, how do we ensure that the developer actually follows the rule? Being a developer myself, I know it's difficult to follow coding rules. I decided to develop a small exception-handling framework, called Patch, that solves the above problems. Because we made the constructor in the first version declare ConnectionException, code that uses it doesn't have to change to use the second version. Java trades some checking it could do in the throws clause for the sake of long-term stability in all the other classes that call the constructor—not a bad bargain at all. In Java, exceptions are objects. When you throw an exception, you throw an object. You can't throw just any object as an exception, however -- only those objects whose classes descend from Throwable. Throwable serves as the base class for an entire family of classes, declared in java.lang, that your program can instantiate and throw. A small part of this family is shown in Figure 1. Part 1 emphasized that if two different exceptions could potentially have different error-recovery procedures, then they should be of different classes -- although perhaps derived from the same base class. You never want to be in the situation where you will be tempted to use the message text to differentiate between two different exceptions. The message text associated with exceptions is explanatory only and exclusively for the consumption of people, not code. Exceptions in object-oriented applications tend to proliferate, overload the code, and improperly handle issues. In this article, author Jean-Pierre Norguet explains how to design exceptions in order to implement a simple, readable, robust, active, debug-oriented, and user-friendly error-handling system. He proposes the design of a sample exception set, including the source code of a Java implementation. Finally, he explains how to integrate such a design into a Java enterprise application. One of the problems with exception handling is knowing when and how to use it. In this article, I will cover some of the best practices for exception handling. I will also summarize the recent debate about the use of checked exceptions. In this article, we'll discuss some fundamental concepts about the different types of Java exceptions and their intended uses. We'll also cover basic logging concepts, especially as they relate to exception handling. Finally, instead of prescribing what to do, we'll focus on what not to do, and take a look at a dozen common exception-handling antipatterns that you are almost certain to find somewhere in your code base. To illustrate these rules of effective exception handling, this article discusses a fictional personal finance manager called JCheckbook. JCheckbook can be used to record and track bank account activity, such as deposits, withdrawals, and checks written. The initial version of JCheckbook runs as a desktop application, but future plans call for an HTML client and a client/server applet implementation. There is no hard-and-fast rule about how many exception types a method should throw, but fewer is generally better. Each exception type a method might throw will have to either be caught or thrown by any method that calls it, so the more different types of exceptions a method throws, the more difficult that method will be to use. If you throw too many exception types, callers might get lazy and simply catch Exception -- or, worse, throw Exception. These are dangerous practices; callers should instead treat exception types individually. Throwing more than three different exception types generally indicates a problem: the method either performs too many different tasks and should be broken up, or lazily propagates lower-level exceptions that should either be mapped to a single higher-level exception type or caught and handled within the method. However, if you keep taking stack traces this situation does not change, and the GUI appears to have frozen. This indicates that the remove call never returned. By following the code path to the ChoicePeer class, you can see that this is making a native MFC call that does not return. That's where the real problem lies and is a bug in the Java core classes. The user's code was OK. Bill Venners: I recently published an interview with C#'s creator Anders Hejlsberg in which we talked about checked exceptions and why C# doesn't have them. I wanted to ask you about some of his comments. He said: Anders Hejlsberg: The scalability issue is somewhat related to the versionability issue. In the small, checked exceptions are very enticing. With a little example, you can show that you've actually checked that you caught the FileNotFoundException, and isn't that great? Well, that's fine when you're just calling one API. The trouble begins when you start building big systems where you're talking to four or five different subsystems. Each subsystem throws four to ten exceptions. Now, each time you walk up the ladder of aggregation, you have this exponential hierarchy below you of exceptions you have to deal with. You end up having to declare 40 exceptions that you might throw. And once you aggregate that with another subsystem you've got 80 exceptions in your throws clause. It just balloons out of control. Exception Safety: an exception should mean that the object is still usable, unless otherwise indicated in the semantics. I.e., when I ask an object to perform a service and the object throws an exception back at me, I want to be confident that I can subsequently ask the object to do something else for me, or the same thing again later, etc. Gives an opportunity to mention finally clauses, which are executed as exceptions propagate up the stack. 25. Exceptions Five months ago, I began a mini-series of articles about designing objects. In this Design Techniques article, I'll continue that series by looking at design principles that concern error reporting and exceptions. I'll assume in this article that you know what exceptions are and how they work.In Java, exceptions are objects. When you throw an exception, you throw an object. You can't throw just any object as an exception, however, only objects whose class descends from Throwable. Throwable serves as the base class for an entire family of classes, declared in java.lang, that your program can instantiate and throw. A small part of this family is shown in Figure 9-1. This article was first published under the name Under the Hood: Try-finally clauses defined and demonstrated in JavaWorld, a division of Web Publishing, Inc., February 1997. In the same Exceptions/examples/ex7 directory, edit VirtualPerson.java. Increase the multipler of the random number returned from Math.random() from 4.0 to 5.0. Add another case statement to the switch (for case 3) that throws a new TastesLikeDishwaterException. Recompile the application and run it enough times that you get to see what happens when VirtualPerson throws TastesLikeDishwaterException. In our first installment of this column, we discussed the cost of throwing exceptions. This month, we revisit the subject from a different point of view -- how the JVM handles thrown exceptions -- and we ponder whether optimal exception coding should be considered a premature optimization or a best practice. Many of the features new to the Merlin release, like exception handling and logging, aren't as visible or exciting as some others, but they are useful and merit our attention. All Java developers should be familiar with the basic structure for performing exception handling: you place the code that might throw an exception within a try block and then have catch clauses at the end of the block to handle if and when exceptions do get thrown within the block. This basic structure doesn't change with the Merlin release. What's new in 1.4 is that if you rethrow an exception from the catch clause, you can attach the original cause of the exception. A real coup for debugging! And, if you want to log where the exception happened, you don't have to manually parse the stack trace. There's now support for programmatically accessing the stack trace data and a Logging API for logging that data (or anything else). Like C++, the Java language provides for throwing and catching of exceptions. Unlike C++, though, the Java language supports both checked and unchecked exceptions. Java classes must declare any checked exceptions they throw in the method signature, and any method that calls a method that throws a checked exception of type E must either catch E or must also be declared to throw E (or a superclass of E). In this way, the language forces us to document all the anticipated ways in which control might exit a method. In this article, you learned about the various class loading exceptions, from the most basic errors to some more cryptic ones. In the next article in this series, we will look at some other class loading problems that you might encounter when running more complex applications. Designing a strong and effective error handling mechanism is one of the most important considerations when building an application. The object-oriented languages used today provide a solid foundation for exception handling that is built directly into the languages themselves. Despite the fact that this type of error handling is not necessarily object-oriented in nature, I believe that it has a valid place in OO design. Throwing an exception (discussed in the next section) can be expensive in terms of overhead (you will learn about the cost of exception handling in a later article). Thus, although exceptions are a great design choice, you will still want to consider other error handling techniques, depending on your design and performance needs. Now let's look at the sample program in Listing 4, which throws an exception and deals with it. This program illustrates the implementation of exception handling using the try/catch block structure. Exceptions and exception handling are essential to good object-oriented design. They not only separate the error-handling code from the main execution but also provide a layer of abstraction for underlying errors to be intelligible to applications. With the new Exception Chaining facility and some new APIs, the Java Exception framework has provided the programmer with the missing link between lower layer and higher layer exceptions. For more details and API discussions, visit the following links. Richard has participated in numerous consulting projects, and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas. He is the author of Baldwin's Programming Tutorials, which has gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine. In this article, our primary focus is on how the Java exception handling mechanisms can be used to help achieve fault tolerance and where in the journey to robust and reliable software exception handlers should fit. To get started, we need to establish a few ground rules. Because some key terms are commonly used in different ways, Table 1 provides some simple definitions for how these terms are used in this article. |
www_.___j__a__va_2s__.___c_o_m__ | Contact Us |
Copyright 2009 - 12 Demo Source and Support. All rights reserved. |
All other trademarks are property of their respective owners. |