Download Rules for Developing Robust Programs with Java Exceptions

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts
no text concepts found
Transcript
Rules for Developing Robust Programs with
Java Exceptions
TDT4735 Software Engineering, Depth Study
Hoa Dang Nguyen and Magnar Sveen
November 28, 2003
Teaching supervisor: Tor Stålhane
Norwegian University of Science and Technology, NTNU
Department of Computer and Information Science, IDI
i
Abstract
Modern object oriented programming languages like Java and C# offer advanced exception
handling mechanisms. As systems grow more complex and customer demand for robustness
increases, understanding and knowing how to use exception handling techniques effectively is
increasingly important. In the past, developers considered exceptions as an afterthought or an
as-you-go addition, resulting in difficult debugging and unstable programs. Working on this
project, we searched books, articles and the web for guidelines and pitfalls regarding the use
of Java exceptions. We tested integrated development environments and other software
designed to help analyze Java programs and the use of exceptions. Based on what we have
learned, this paper formulates a set of rules to help system developers create robust Java
programs with the use of exceptions. To build a robust system, the developers need to take
exception handling into account throughout the development process. Exception handling is
an integral part of a robust system, and can not be added as an afterthought. Java’s exception
handling mechanisms are powerful, easy to use, and easy to misuse. This paper proposes
some rules to help avoid or correct the misuses of exceptions.
i
Preface
This document is the result of our work in the course TDT4735 Systemutvikling, Fordypning.
The work has been performed at the Department of Computer and Information Science,
Faculty of Information Technology, Mathematics and Electrical Engineering at the
Norwegian University of Science and Technology (NTNU) during the autumn semester of
2003.
Acknowledgements
We are very thankful for the support, advice, feedback and comments from our teaching
supervisor, Tor Stålhane. In addition we would wish to thank Catherine Pierce for invaluable
help with the English language, and Alf Inge Wang and Mario Aparicio for letting us use the
code from DIAS 2 and IpManager for testing purposes.
Trondheim, 28. November 2003.
____________
Hoa Dang Nguyen
____________
Magnar Sveen
ii
Contents
ABSTRACT
PREFACE
ACKNOWLEDGEMENTS
CONTENTS
II
III
III
IV
1 INTRODUCTION
1
1.1
1.2
1.3
1.4
1
2
2
3
2
2.1
2.2
2.3
3
3.1
3.2
3.3
3.4
3.5
3.6
3.7
4
4.1
4.2
5
5.1
5.2
5.3
5.4
5.5
6
MOTIVATION AND BACKGROUND
PROBLEM DESCRIPTION
RELATED WORK
STRUCTURE OF REPORT
EXCEPTION HANDLING
WHAT THEY ARE FOR
HOW THEY WORK
HISTORY
JAVA EXCEPTIONS
EXCEPTIONS AS OBJECTS
THE THROW STATEMENT
THE TRY-CATCH CONSTRUCT
CLEANING UP WITH FINALLY
CATCH OR DECLARE
RUNTIME EXCEPTIONS
CHAINED EXCEPTIONS
CURRENT TRENDS
DESIGNING FOR ROBUST JAVA PROGRAMS WITH EXCEPTIONS
BEST PRACTICES FOR USING EXCEPTIONS
ANALYSIS TOOLS
JEX
JIKES BYTECODE TOOLKIT
TEAMSTUDIO ANALYZER FOR JAVA
J2EE CODE VALIDATION FOR WEBSPHERE STUDIO
CONCLUSION
RULES
6.1 SYSTEM REQUIREMENTS PHASE
6.2 ARCHITECTURAL DESIGN
6.3 DETAILED DESIGN
4
4
4
5
6
6
7
8
8
9
9
10
12
12
15
21
21
24
25
26
28
29
29
30
30
iii
6.4 IMPLEMENTATION
6.5 TESTING
7
7.1
7.2
THE RULES IN PRACTICE
IP MANAGER
DIAS 2
30
33
34
34
37
8
CONCLUSIONS AND FURTHER WORK
40
9
REFERENCES
41
iv
1 Introduction
Exception handling is something most people have heard about, but do not always know how
to use properly. From our own experience and others, exception handling was the last
mechanism we learnt and the last to be used. We will with this report show the importance
and necessity of exception handling.
1.1 Motivation and Background
We got the idea for this report after a meeting with our teaching supervisor, Tor Stålhane. He
made us aware of the lack of guidance, guidelines, rules and tips within exception handling in
Java. Java is a programming language that we have been familiar with since the freshman
year. We have been programming with Java ever since, but did not realize how important
exception handling was until recently.
When we reflect on our work with exception handling, we can see the lack of understanding
and use of exception handling. The literature which introduced us to Java only gave us a little
knowledge about exception handling. After some research, we noticed how few guidelines
that was available for exception handling. We saw at once the potential for writing a complete
guideline or a set of rules for exception handling.
With powerful development tools and increasing hardware performance, systems tend to
become more and more complex in order to take advantage of these opportunities. Complex
systems are harder to code, harder to debug and harder to maintain. The more complex the
systems get the more unstable they will become. With the increasing demand for robust
system from the customers, developers are forced to focus more on reliability and robustness.
Exception code design and analysis is complex and can be hard to handle. The different
components have to co-operate and interact with each other in faultless way. The process of
exception handling is not straight forward but rather complex. When one component decides
to throw an exception it can not handle, that exception can be caught anywhere in the
program. Code violation can occur anywhere in the code and have to be handled. Developers
have to plan in advance how exceptions are handled before they occur. Logging for failure
that occurred also has to be considered thoroughly.
It is not easy to known where or when to include exception handling in your development
process. Exception handling is usually not addressed at the appropriate phase of system
development. People tend to forget about exception handling and only take them in
consideration when they encounter problems with their program. This will only cause the
program to be more unstable and harder to debug.
Even though the developers do plan exception handling ahead, they do not always know how
to use them properly. Methodologies supporting proper use of exception handling are few and
far between. The methodologies that are available today are not complete, they are rather
scattered. You may find many books that include many good guides for using exceptions, but
you may find other guides elsewhere not mentioned in the books.
1
1.2 Problem Description
Many developers and programmers do not know how to use exceptions efficiently. Some see
Java exceptions as a hassle, and only think about them when forced to do so by the Java
compiler. The exceptions are inserted into the system too late, and are often caught and just
ignored to stop the compiler from complaining. Other developers see the need for increasing
the robustness of their program after it has been implemented, and try to plug exception
handling into the system near the end of the process, usually much too late.
We want to show that Java’s exception handling mechanism is a powerful and essential tool
for creating robust programs. We will present a rule set to help developers use Java
exceptions for more than just showing error messages and stack traces. We will show the
importance of using these rules throughout the development process. Starting with the system
requirements phase, we will suggest rules to help developers use exceptions in a better way
through the different phases of architectural design, detailed design, implementation and
testing and delivery of the finished product.
1.3 Related Work
Although there is an overabundance of books about Java, and many of these include a chapter
about how Java’s exception handling mechanisms work, there are only a few books that delve
deeper into the subject of good use of exceptions. [PRJ2000] is a collection of practical
suggestions, advice, examples and discussions about programming in the Java language. It has
a section for exception handling that contains eleven good practices when using exceptions
and the reasoning behind them. [EFJ2001] is a guide to effective use of the Java programming
language. Its chapter about exceptions contains nine rules for using exceptions effectively, to
improve a program’s readability, reliability, and maintainability.
Of the publicly available articles about exception handling, many had a narrow focus on some
particular aspect of exception handling, and thus did not help us much in our search for
general guidelines and best practices. One article we think is a good read is [EAE2003]. It
presents a simple component–based strategy addressing the various ways for using exceptions
better in Java. The paper is based on the experience of the development of many real large
software projects. Another article which we also find useful and interesting in relation to our
work is [AEU2003]. This article discusses common trends in the use of exception handling in
large Java applications, and proposes some solutions to the more common pitfalls. [EXS2003]
compares Java exceptions with C# exceptions, and also presents a dozen strategies for using
exceptions in Java.
The World Wide Web is a great resource for information, and there are plenty of forums
where you can get hints and tips and read about exception handling in Java. Many of the sites
and articles on the net have more guidelines than the books you may find in your local store.
2
1.4 Structure of Report
We start this paper by giving the reader a basic introduction to exception handling. Chapter 2
describes what exception handling is and why programming languages have this mechanism,
in addition to a short history of exception handling. Chapter 3 describes Java’s exception
handling mechanisms in detail. Chapter 4 looks at current trends in exception handling and
summarizes information that is currently available. Several tips and guidelines are gathered
from various books, articles and forums. We have tested several programs and toolkits
concerning exception handling in Java in Chapter 5. Our main contribution is in Chapter 6
where we present a set of rules to help developers create more robust programs. We then look
at the rules in the context of real life examples in Chapter 7, discussing what is good, what is
bad and what could have been done better. Chapter 8 concludes the report, and looks at
possible further work.
3
2 Exception Handling
Following is a short introduction to exception handling. Sections 2.1 and 2.2 explain why
programming languages have exceptions and how exception handling works. We will then
provide the user with a short history of exception handling in section 2.3.
2.1 What they are for
The main idea behind exception handling is to make programs more reliable and robust. Many
programming languages provide exception handling mechanisms to allow software
developers to define exceptional conditions. This helps the developers to structure the
exceptional activities of the system component.
It is important to have a well thought-out and consistent exception handling strategy for the
sake of efficiency and good programming practice. Exception handling should not be
considered as an add-on but as an integral part of the development process. The power of
exceptions provides a framework on which to develop applications that are robust and
dependable by design, rather than by accident.
2.2 How they work
There exist two types of activities taking place during the execution of a program; normal and
abnormal activities. In the normal activities, software components may receive service
requests and produce responses. If the component does not satisfy a service request, it returns
an exception. There are several exceptions that can occur, as represented in Figure 2.1.We can
classify exceptions into three categories: interface, internal and failure.
-
Interface exceptions: When a request does not conform to the component’s interface
the response will be an interface exception.
Internal exceptions: This is the type of exception that is called by the component itself
in order to invoke its own internal exceptional handler.
Failure exceptions: This occurs when the component itself is unable to handle
exceptions
If the component is able to handle the exception it will return to normal state and the program
will continue to run normally. When the component is unable to handle the exception it will
try to throw it back to the caller. If the caller is unable to handle the exception it will try to
throw it to the next caller. This will go on until the exception is handled or else the program
will halt.
4
Service
requests
Normal
responses
Interface
exceptions
Failure
exceptions
Internal
exceptions
Abnormal Activity
Normal Activity
(fault tolerance by exception
handling)
Return to normal
operation
Service
requests
Normal
responses
Interface
Failure
exceptions exceptions
Figure 2.1 Idealized fault-tolerant components taken from [EHM1999].
2.3 History
Already in the mid 1950’s we find something similar to exception handling, you may call it a
predecessor to exception handling. It was the Lisp 1.5 developed by John McCarthy who
introduced a function called “errset” which allowed the Lisp interpreter and compiler to exit
when an error occurred. Later, in the mid 1960’s, a programming language, developed at IBM
called PL/I, included facilities for dealing with control flow. The earlier languages had few
facilities that dealt with exceptional conditions during execution. There were several problems
with this mechanism, but the philosophy of programming language design for reliability
demanded that this facility be included in the programming language definition. By the mid
1970’s, software engineering and programming languages communities had a strong concern
about software reliability.
In the Late 1970’s both Ada and CLU, inspired by John B. Goodenough’s paper [EXH1975],
introduced another way to deal with exceptions. The exceptions were handled by the caller
not where it was raised. Another feature was that the exception handling mechanisms were
static instead of dynamic as PL/I was.
Treatment of the exceptions in programming languages in the 1980’s influenced the software
engineering research on the design of programming development environments. In the 1990’s
when object-oriented programming was beginning to enter the world of software
development, exceptions were considered as objected. The try-catch method is most
commonly used in object-oriented programming today. For a more detailed reading see
[IDE2003].
5
3 Java Exceptions
In this chapter we will look at how exceptions are implemented and used in the Java language.
In Java, exceptions are first class citizens. This means that exceptions are directly supported
by the language. Using Java Exceptions for what they are worth can greatly improve the
robustness of a program. It helps distinguishing normal and abnormal behavior, and separates
error-detection (throw) from error-handling (try-catch). In order to take advantage of Java’s
flexible error-handling system, we need to understand how it works and some of the design
decisions Sun did when making the exception handling mechanisms.
3.1 Exceptions as objects
“With exceptions as objects, you have the power to encapsulate an unlimited variety of
functionality specific to the problem.”
- http://www.churchillobjects.com/c/11012k.html
Java exceptions are objects. This is a relatively new way to represent error handling. C++ and
Java are the only well known languages today that have exceptions as objects. Older
languages use labels or have no real support for exceptional behavior. One example is Virtual
Basic’s awkward ON ERROR GOTO functionality. Java exceptions are sometimes called
throwables because raising an exception in Java is done with the throw keyword.
Throwable
(interface)
Exception
Error
IOException
ThreadDeath
SQLException
VirtualMachineError
RuntimeException
OutOfMemoryError
NullpointerException
ArrayIndexOutOfBoundsException
Figure 3.1: Java API Exception Hierarchy
6
Figure 3.1 shows the fundamentals of Java’s Common APIs exception hierarchy. Inheriting
from the Throwable interface, the Exception and Error classes serve quite different purposes.
While both signal an exceptional behaviour, the Error class and its subclasses are used mainly
by the Java Virtual Machine to indicate severe (and often unrecoverable) errors. The
Exception branch is more commonly used. Most notable is the RuntimeException class with
subclasses
like
NullPointerException,
ArrayIndexOutOfBoundsException
and
ArithmeticException. These exceptions are usually the result of bad programming, and are not
checked at compile time. See section 3.6 for more information about Runtime Exceptions.
Exceptions that are not Runtime Exceptions are called Checked Exceptions (see section 3.5).
When you use a method that throws a Checked Exception, you are required by the compiler to
declare how the program is to respond, should the exception occur. Most checked exceptions
are thrown by the programmer, and not by the virtual machine. These usually signal an error
stemming from outside the program, for instance bad data by the user or failure to
communicate with a database or another machine.
3.2 The throw statement
When a situation occurs that the current class or method can not or will not handle, an
exception is thrown. If the exception isn’t caught within the scope of the method, it will be
thrown to the calling method. If this method doesn’t catch it either, it will be thrown to the
next calling method – and so on, until the exception is caught or the program terminates with
an error message. See Figure 3.2 for an illustration.
Main Method
Call
Catch
Method 1
Call
Re-thrown
Method 2
Exception
Figure 3.2: Two method calls, an exception and a catch statement
This system helps separate error detection from error handling. One method discovers the
error and signals this by throwing an exception. This ends its responsibility regarding the
situation. Any method in the chain of calls that is able to handle the exception can do so, or
the user will be presented with an error message and a stack trace.
7
3.3 The try-catch construct
The try-catch statement is Java's way of separating normal code from error-handling code.
When a programmer suspects that a block of code might generate an exception, it is placed
inside a try block. This is followed by one or more catch statements, each containing the code
to be executed if the matching exception is thrown. See Example 3.1.
Example 3.1 – The try-catch construct
try {
// code that might throw an exception
} catch (OneException e1) {
// code for handling OneException
} catch (AnotherException e2) {
// code for handling AnotherException
}
The catch statements are checked against the thrown exceptions in the order they are declared.
3.4 Cleaning up with finally
Once an exception is thrown, the rest of the code in the try block is skipped. See Example 3.2.
If an IOException is thrown in the third line, the file will not be closed in line four.
Example 3.2 – Writing to a file without finally
1
2
3
4
5
6
7
try {
file.open();
file.write("something");
file.close();
} catch (IOException ioe) {
GUI.alertUser("Could not write to the file." );
}
A better way to handle this is with the finally statement. The code contained within the finally
block will always run, whether any exceptions have been generated or not. See Example 3.3.
The file will be closed in line 7 independent of what happens inside the try block.
Example 3.3 – Writing to a file with finally
1
2
3
4
5
6
7
8
try {
file.open();
file.write("something");
} catch (IOException ioe) {
GUI.alertUser("Could not write to the file." );
} finally {
file.close();
}
8
The only thing that can prevent a finally block from executing is a call to System.exit, since
this will shut down the virtual machine.
3.5 Catch or declare
Unless a thrown exception is a subclass of RuntimeException, the Java compiler requires that
all exceptions are caught or specifically thrown by the method. This means that if you call a
method that throws an exception, you are required to declare how your program will respond
if that exception occurs. Either you throw the exception further up the call stack, or you catch
it and deal with it.
Example 3.4 – Catch or specify
/* Reads the contents of a textfile. If the file does not exist,
* creates an empty file and returns an empty string.
*/
public String readFile(String filename) throws IOException {
String fileContents = "";
FileReader filereader = null;
try {
filereader = new FileReader(filename) ;
} catch (FileNotFoundException notfound) {
new File(filename).createNewFile();
}
if (filereader != null) {
BufferedReader reader = new BufferedReader(filereader);
while (reader.ready()) {
fileContents += reader.readLine();
}
}
return fileContents;
}
Take a look at Example 3.4. The FileReader constructor can throw two checked exceptions,
IOException and FileNotFoundException. One is caught and handled by the method. The
other, IOException, is specifically declared to be thrown by the method. Had this not been
specified, it would result in a compiler error. Likewise, another method calling this one will
have to catch the IOException or declare that it throws it further.
3.6 Runtime exceptions
As opposed to the exceptions in section 3.5, the ones inheriting from RuntimeException are
not checked by the compiler. Runtime exceptions are problems detected by the runtime
system. Common exceptions of this type are NullPointerException (trying to access an object
through a null pointer reference), ArithmeticException (e.g. division by zero) and
ArrayIndexOutOfBounds (trying to access an element in a list with an index that is either too
large or too small).
These exceptions can be numerous, possibly occurring anywhere in the code. For one, this
makes it prohibitive to check the exceptions at compile time – and would require that the
9
programmer catch these exceptions everywhere they could occur. It also would make
programmers spend all their time producing extremely cluttered code that did nothing.
3.7 Chained exceptions
A new feature in Java Development Kit (JDK) 1.4.0 is known as chained exceptions. In short,
exception chaining stores all exceptions from the initial cause up to the last. Throwables can
now contain the throwable that caused them. One cause can have another cause, thus giving a
causal chain. You can iterate through the chain, all the way back to the initial exception.
Initial cause
EXCEPTION
EXCEPTION
getCause()
EXCEPTION
getCause()
Figure 3.3: A Chain of Exceptions
According to Sun, "it is common for Java code to catch one exception and throw another"
[SUN1]. The problem with this is that the second exception will lose information contained in
the first. This makes both bug finding and recovery attempts harder. When the initial cause is
lost, the programmer must spend time trying to dig up what really happened. Chained
exceptions will let you know not only the initial cause, but also every exception that was
triggered on the way up.
This knowledge can also be used effectively inside the program. Imagine a method doing this:
Example 3.5 – Chaining exceptions
try {
// code
} catch (FileNotFoundException cause) {
// do something
throw new FileNotAvailableException(cause);
} catch (AccessDeniedException cause) {
// do something
throw new FileNotAvailableException(cause);
}
It is now possible to find out why the file was not available, without forcing the calling
method to catch two separate exceptions. It might be used like this:
Example 3.6 – Using chained exceptions
try {
// code calling the method in Example 3.5.
} catch (FileNotAvailableException exception) {
// do something regardless of cause
if (exception.getCause() instanceof AccessDeniedException) {
// do something more
10
}
}
It has always been possible to chain exceptions, either by making your own, or with a wrapper
class. Many developers have seen the usefulness of chained exceptions, and made their own
non-standard approaches. With JDK 1.4 this is no longer necessary.
11
4 Current Trends
As systems have grown more complex and customers’ demand for robustness has increased,
more people have turned their attention to exception handling techniques. While exception
handling in crude forms has existed since the mid 1960’s, the advanced exception handling
techniques of today is a new field of research. There is no generally accepted agreement on
how exceptions are to be used. However, more is written about exceptions every day, along
with debates about what exceptions really are for [EJE2003] and if checked exceptions are a
good idea or not [DJN2003]. In this chapter we have gathered the recent information to make
an overview of the current trends in exception handling today.
4.1 Designing for robust Java programs with exceptions
According to [EHO2003], the development of modern object oriented systems tends toward
higher complexity and an increasing number of exceptional situations. Exception handling
techniques are employed to deal with these problems, but there are some serious issues when
applying them in practice that have yet to be solved. Exception handling is often not
addressed at the appropriate phases of system development. Exception code design and
analysis is complex, and methodologies supporting the proper use of exception handling are
few and far between. We will take a look at these challenges in this section.
Addressing exception handling early
Exception handling is an important part of a large system. Handling exceptions in a good way
helps debugging during development and increases the robustness of the finished product. For
most projects these advantages are too important to haphazardly leave the exception handling
up to each individual programmer. It is the system architect’s job to define how the system
should respond to undesired events. [EAE2003] writes: Inexperienced programmers tend to
invent and implement ad-hoc repair measures. If this happens at a large scale, the system is
doomed to failure.
Still, for many projects the exception handling is pushed too late into the development
process. Decisions that normally belong to the architectural or detailed design phase are done
during implementation. Java’s creator James Gosling says this is a culture thing, and blames
some of these problems on the way Java and exceptions are taught [FAE2003]. Exception
handling is the last mechanism to learn, and the last mechanism to use.
A lack of notations and patterns
According to [EHO2003] there is a lack of methodologies supporting the proper use of
exception handling. This helps to explain why exception handling is not always considered
during the design phases of development. With no tools available, it is easy to forget the task.
With no standard notation, you need to create and agree upon a notation to include exception
handling in your diagrams. With no support for exception handling in the standard notations
used, you are breaking the standard to include them.
12
The Unified Modelling Language (UML) is the industry-wide standard for modelling
software architectures, and up until recently you could not describe the logic flow of
exceptions with UML. This has been mended in UML 2.0 [UML2003]. An exception
handling notation has been introduced, and hopefully this will help developers account for
exceptions when designing their software. A short introduction to this notation will be
presented later in this chapter.
Along the same lines, there are few available patterns for the use of exceptions. Patterns are
well-known techniques, abstractions of common design occurrences, which document specific
reoccurring problems and solutions. For thorough information about patterns, see
[GAM1995]. Searching the large collection of patterns for Java [PIJ2001] we found not a
single pattern for using exceptions, although there were a few for increasing program
robustness. A pattern called safety facades was introduced by [EAE2003]. In the next section
we will give a brief explanation of it.
Safety facades
A special case of the facade pattern [GAM1995], called safety facades, is presented in
[EAE2003]. At the time of writing, this is to the best of our knowledge the only pattern that
deals with exceptions at an architectural level. We give a short introduction to the pattern
here.
A call between two components can be safe or unsafe. An unsafe call is done directly, with no
exception handling in between. These two components form a risk community. If one fails, so
does the other. A risk community consists of all components that are linked by unsafe calls.
Safe access to risk communities is provided by safety facades. The safety facade is
responsible for exception handling. Exceptions within the risk community fly over all
involved components and are finally caught by the safety facade. This means that all
components within a risk community share the same exception handling mechanism. It is
important to design the risk communities carefully.
A system will have several layers of safety facades. Exceptions are handled by the nearest
safety facade. The exceptions are resolved there or propagated to the next safety facade. The
outermost safety facade will shut down the program if unable to deal with the exception.
Defining safety facades and risk communities at the architectural design phase has several
advantages. The system architect gains control over the big picture of exception handling in
the system. Programmers know which components are responsible for taking care of which
exceptions.
Modelling exceptions in UML 2.0
Presented in UML 2.0 is a mechanism for describing how exceptions are handled, especially
for describing the logic flow. With this notation we can use UML 2.0 to model exceptions
with the use of class and interaction diagrams. The new version of UML also includes new
developments in the activity diagram that deal with exception handling. An exception handler
13
in the activity diagram specifies something to execute when a given exception occurs during
processing.
Figure 4.1: Model of exception handling notation in UML 2.0
Figure 4.1 shows what a model of exception handling notation looks like in UML 2.0.
Protected Node in the figure represents the try block in an activity diagram. The catch block
represents the handler body. If an exception occurs, the set of handlers is examined for a
possible match. If a match is found, the handler body is invoked and the handler catches the
exception. If the exception is not caught, the exception is propagated to the enclosing
protected node if one exists. For an example of using activity diagram for modeling exception
handling in UML 2.0 see figure 4.2. Read [UML2003] for additional explanations.
Figure 4.2: The URL viewer example, from [UML2003]
With the new mechanism for exception handling in UML 2.0, the impact of using activity
diagrams to communicate proper use of exception handling in the design of a system is huge.
Most projects should consider the benefits of using activity diagrams in system modeling.
Even though modeling with activity diagrams promotes functional programming, this is a
good way for describing the design of exception handling. Activity diagrams have roots in
flow charting and are often associated with functional decomposition. Since it was believed
that they promoted functional programming, activity diagrams were not favored in the
software system community. It is important to balance any modeling activity (like use case
modeling and activity diagrams) that promotes functional programming with activities that
promote good object-oriented design.
14
Interface versus implementation
Ideally a method can be seen as an abstract contract, the interface, and the code as one way to
implement that contract. However, many programmers forget about the interface and just
think in terms of the code. It is then easy to forget that the throws clause is part of the
interface. An implementation change may result in a method being called that throws a new
checked exception. That does not mean that the exception should appear in throws clauses up
the call stack. If the exception is just added to the throws clause without any further ado, the
implementation is driving the interface. A method’s throws clause should be considered at the
abstract contract level.
Consider a method loadUserPreferences(int userid). The purpose of the method is to find a
set of user preferences based on a given user identification number. Depending on how this
method was implemented, it could throw such exceptions as SQLException,
FileNotFoundException, MalformedURLException or SocketException. Throwing all these
exceptions isn’t sensible, and throwing only one of them binds the implementation
prematurely. This is where exception translation [EFJ2001] comes in handy. The low level
exception, for instance SQLException, is caught by the method. The method chains the old
exception to a new high level exception, for instance UserPreferencesNotAvailableException,
and throws it. If the implementation changes at a later date, the method interface stays the
same, and the caller of the loadUserPreferences method is presented with an exception that is
appropriate to the abstraction level.
4.2 Best practices for using exceptions
We have done some research about tips and guidelines available today and will present them
in this chapter. The following tips and guidelines are taken from various articles, forums on
the internet and several books: [EFJ2001], [PRJ1999], [DRJ2000], [EJA2001] and [EIJ2000].
The tips and guidelines will be divided into three parts where the first part will show you
when you should use exceptions. Next we will advise you on the distinction in the use of
checked and unchecked exceptions. Finally, the gathered tips and guidelines will show you
how best to use exceptions.
When should I use exceptions?
Programmers do not always know when to use exceptions and when not to use them. When
exceptions are misused, the programs will perform poorly, confuse users and will be harder to
maintain.
First of all, you should only use exceptions, as the name implies, for exceptional conditions. If
your method encounters an abnormal condition that it cannot handle, it should throw an
exception. Avoid using exceptions to indicate conditions that can reasonably be expected as
part of the typical functioning of the method. Never use exceptions for ordinary control flow.
15
Bad idea
Example 4.1 – Flow control
Good idea
try {
int i = 0;
while (true)
a[i++].f();
}
catch(ArrayIndexOutOfBoundsException
e) {
}
for (int i = +; i < a.length; i++){
a[i].f();
}
Consider Example 4.1 where exceptions are being abused in a horrible way. Instead of using a
for-loop to go through all the elements available in the array and then exit, the code to the left
uses a while-loop. The while-loop terminates by throwing, catching and ignoring an
ArrayIndexOutOfBoundsException when it attempts to access the first array element outside
the bounds of array.
Once you have used exceptions you should never hide them. If you hide an exception then it
would be nearly impossible to trace back to where the exception has occurred and find the
original cause of the method’s failure. Furthermore you should never ignore an exception and
hope it will go away, because it won’t. A thread will terminate if exception is not caught and
there will be no record to show that exception has occurred. Take a look at Example 4.2
where the following output is: “In main, caught: Third Exception”. First and Second
Exception will be ignored since //1 is hidden by exception thrown at //2. Exception at //2 is
again hidden by exception at //3. One solution to this is to save a list of all exceptions
generated and throw an exception that holds a reference to this list.
Example 4.2 – Hidden exception
class Hidden{
public static void main (String args[]){
Hidden h = new Hidden();
try {
h.method();
}catch (Exception e){
System.out.println(“In main, caught exception: “ +
e.getMessage());
}
}
public void method() throws Exception {
try {
throw new Exception(“First Exception”);
}catch Exception e){
throw new Exception(“Second Exception”);
}finally{
throw new Exception(“Third Exception”);
}
}
}
//1
//2
//3
Even though using exceptions can help you make your code easier to read by separating
functional code from error-handling code, inappropriate use can make your code harder to
read. This means that you do not have to use exceptions for every error condition. You should
consider when it is intuitive to use exceptions and when not to use exceptions.
16
Bad idea
Example 4.3 – Intuitive use of exception
Good idea
int data;
MyInputStream
in
=
new
MyInputStream(“filename.ext”);
while(true){
try{
data = in.getData();
}
catch(NoMoreDataException e){
break;
}
}
int data;
MyInputStream
in
=
MyInputStream(“filename.ext”);
data = in.getData();
while(data!=0){
//do something with data
data = in.getData();
}
new
Take a look at Example 4.3 where it is more intuitive, easier and faster to return zero rather
than using an exception. Besides, using exceptions are more expensive in terms of the
resources needed. The more exceptions you create the more handling you need to do and
exception handling mechanism demands a lot of resources.
Which exceptions class should I use?
There are a lot of standard exceptions provided by Java. In this section we will concentrate on
the distinction in the use of checked and unchecked exception, also known as runtime
exception. The main rule for using checked exception is to use them for conditions from
which the caller can reasonably be expected to recover. If you are throwing an exception for
an abnormal condition that you feel client programmers should consciously decide how to
handle, throw a checked exception. When using a checked exception is inappropriate, it is
better to use unchecked exception. One technique for turning checked exception into
unchecked exception is to break the method that throws the exception into two methods, the
first of which returns a boolean indicating whether the exception should be thrown. Even
though it is not always appropriate to do such transformation, it will make the API more
pleasant to use where it is appropriate.
Example 4.4
//Transform the calling sequence from :
try{
obj.actio(args);
} catch(TheCheckedException e) {
//Handle exceptional condition
……………
}
// to this :
if (obj.actionPermitted(args)) {
obj.action(args);
}else {
//Handle exceptional condition
……………
}
You should use runtime exception to indicate programming errors. When using unchecked
exception the compiler does not force the client programmers to either catch the exception or
declare in a throws clause. All unchecked throwables you implemented should be subclass
from RuntimeException either directly or indirectly.
17
How do I best use exception?
Favor the use of standard exceptions
As you may have noticed, Java provides a lot of standard exceptions and you should favor the
use of these exceptions instead of creating new ones. There are many benefits for using
preexisting exceptions. The main argument for using them is that it will make your code
easier to read and use, since programmers will be dealing with exceptions that they are
familiar with. Besides, using preexisting classes and fewer classes mean a smaller set of
classes to deal with and less time spent loading them. If you find it inappropriate to use
preexisting exceptions or the exceptions you make are more convenient for your code, then
you are better off creating new ones.
Catching exceptions
When catching an exception you should try to gather as much information about that
exception as possible. The more specific exception you throw the easier it is to handle. This
means that you should try to avoid catching superclasses as Exception and Throwable if you
can help it. Catch a superclass only if you are certain that all the exceptions you will catch by
doing so, have the same meaning to your code. Always catch and handle RuntimeException
or Error separately from other Exception or Throwable subclasses. Do not add exception
handling at the end of the development cycle. Never create objects from the Exception class
because an exception handler can not distinguish between the exceptions those objects
represent. Remember to create exception objects from appropriate Exception classes.
Throwing exceptions
You should consider the type of exceptions to be thrown by a method from the perspective of
the method’s caller rather than the class’s own perspective, when writing throws clauses. If
you throw a bad type, the caller will not be able to handle the exception sensibly and might
have to pass it on to its caller or the user. After finding out which type of exception to throw,
you have to consider the object’s state to be thrown. The object has to return to a valid state
before throwing an exception. The purpose of catching an exception is to try to recover from
the problem that occurred and keep the system running. If you leave the object in a bad state
the code will very likely fail anyway, even when the exception has been handled. Consider
Example 4.5 where at //1 the method increases a counter for the number of objects in the list.
If an exception is thrown after //1 then the object will be in an invalid state because the
counter, numElements, is incorrect. The way to solve this problem is to move the counter at
the end of the method after //2.
Example 4.5
class Foo{
private int numElements;
private MyList myList;
public void add(Object o) throws SomeException{
//…
numElements++;
if(myList.maxElements() < numElements) {
//Reallocate myList
//Copy elements as necessary
//Could throw exceptions
}
myList.addToList(o); //Could throw exception
}
}
//1
//2
18
If you have to deal with failure that occurs inside a constructor, throw an exception object
from that constructor. Do not attempt to throw objects from any class apart from Throwable
or a Throwable subclass, otherwise the compiler will report an error. The compiler will also
report an error when a method attempts to throw a checked exception object but does not list
that object’s name in the method’s throws clause. You should throw an exception and never
throw errors.
Try/Catch/Finally
The catch keyword must appear immediately after a try block’s closing brace character.
Placing try/catch blocks inside of loops can slow down execution of code as you can see in
Example 4.6. Method2, which puts the try block outside the loop, has proved to be more
efficient during execution than method1.
Bad idea
Example 4.6
Good idea
public void method1(int size){
public void method2(int size){
int[] ia = new int[size];
for(int=0;i<size;i++){
try{
ia[i]=I;
}
}catch (Exception e){
//Exception ignored on purpose
}
int[] ia = new int[size];
try{
for(int=0;i<size;i++){
ia[i]=I;
}
}catch (Exception e){
//Exception ignored on purpose
}
}
}
Do not issue a return, break or continue statement inside a try block. If you cannot avoid this,
be sure the existence of a finally does not change the return value of your method. If a finally
block exists, it is always executed. The main benefit of using finally is to avoid resource leaks
as you can see from Example 4.7. If an exception occurred in a try block, the close call will
never be reached unless you have finally.
Bad idea
Example 4.7
Good idea
class WithoutFinally{
public void foo() throws IOException
//Create a socket on any free port
ServerSocket ss = new ServerSocket(0);
try{
Socket socket = ss.accept();
//Other code here…
}catch(IOException e){
ss.close();
throw e;
}
close();
}}
Create useful error messages
class WithFinally{
public void bar() throws IOException
//Create a socket on any free port
ServerSocket ss = new ServerSocket(0);
try{
Socket socket = ss.accept();
//Other code here…
}finally{
close();
}
}
19
It is clearly an easy task to write throw or catch clauses, but writing good error-recovery
information is more difficult. To write a good throw or catch clause is another problem. We
will see here the benefit of writing good error-recovery information. Take a quick look at
Example 4.8 where you can see a hard-code text string for throwing an exception. As you can
see, this is a very commonly and undesirable way to build an error message. Most developers
commit this type of mistake of placing explanatory messages with source code.
Example 4.8
if (!file.exists()){
throw new ResourceException(“Cannot find file” + file.getname());
}
The source code is not a place for explanatory messages, which are really more related to
documentation. As long as the error messages are hard-coded among source codes, they will
be created and owned by developers, often to the detriment of usability. Developers should
not be writing error messages even more than they should not be writing documentation. How
often have you seen error messages that are utterly incomprehensible except to the person
who wrote the code?
20
5 Analysis Tools
In section 5.1 we take a look at JeX, a static toolkit for analyzing exception flow in Java.
Section 5.2 describes Jikes Bytecode Toolkit, a class library written entirely in Java that
enables Java programs to create, read, and write binary Java class files. Finally in section 5.3
we try out Teamstudio Analyzer for Java, a best-practices audit tool that uncovers errors in
Java code.
5.1 JeX
JeX is a static toolkit for analyzing exception flow in Java. It was developed by Martin P.
Robillard and Gail C. Murphy in 1998 at the Department of Computer Science at the
University of British Columbia.
About JeX
JeX consists of four components: the parser, the Abstract Syntax Tree (AST), the type system
and the JeX loader. The AST is used to identify the structures of classes and exceptions within
a method or constructor. It also evaluates the expressions and invocations that may cause an
exception to be thrown. The type system provides the AST with a list of all types that override
a particular method. The parser component uses this for analyzing the exceptions in the code
which JeX analyze. JeX loader loads the various components and connects them. See
[AEF1999] for additional information.
In order to use JeX, the user must specify a list of packages, a path to search for JeX files and
a Java source code file in a configuration text file. JeX uses the following template (see Table
5.1).
JeX template:
<jexpath> The root directory where JeX is installed.
<package> Package name to include for class-hierarchy analysis. Class hierarchy analysis is
used to conservatively determine which method bodies can be executed for each
method call. As many package descriptors as desired can be used.
<genstub> Package name to generate stubs for at the beginning of an analysis.
<dir>
Directory where the Java source file/files are to be found.
<file>
The Java source file which JeX is supposed to analyze.
Table 5.1: Template for JeX configuration file
Additional information about JeX may be found at [JEX1].
JeX at work
We’ve tested JeX with some small programs, one large program and an example program that
is available at JeX’s homepage. Before we were able to test JeX with any of the programs, we
21
had to configure JeX and generate Java packages for JeX class-hierarchy. First of all we
tested the example program that already had the configured text file that JeX uses. JeX
worked fine in this example and there were no difficulties. The JeX-report that was generated
by JeX only showed that there was one warning, which was expected, see figure 5.1.
Figure 5.1: The content of JeX-report.
Then we tested JeX on a program which was developed in 2000 by a group of students at the
Norwegian University of Science and Technology. The reason for choosing this program was
that it included a lot of exceptions. It was also programmed in Java and one of the authors of
this report developed program. Here we encountered several problems. One of the problems
was that JeX was developed with Java version 1.3 while the test-program was developed in
Java version 1.4. Many packages included in Java 1.4 were not included in Java 1.3. Another
problem was to set up the configuration file for JeX. The documentation found at JeX
homepage was not clear enough to allow us to understand whether the set up was right or not.
In the end, we gave up trying JeX with that program since we could not get JeX to work with
it.
Then we chose a smaller program to try JeX on. This time all the packages needed for
generating stubs in order to test out JeX, worked on this program. Surprisingly JeX took an
extremely long time to go through a small program with only three Java classes. The report
showed us a lot of warnings which shouldn’t appear. The warnings were about methods it
didn’t find even though they were there, see figure 5.2. We were not able to understand the
reasons why. Beyond this it seemed that the program was fine and JeX reported no other
errors. We expected JeX to give advice on how to optimize our exceptions in the code. It did
not.
22
Figure 5.2: JeX-report for our small program.
Conclusion
JeX is an old toolkit that will probably not work with most programs developed by Java 1.4
and above. It may work if the packages used during development were included in Java 1.3.
JeX has not been updated since 8 March 2002 which means it’s quite out of date. The
documentation should have been better and easier to read. The report which JeX generates is
not easy to understand and did not show as much useful information as we had hoped. JeX is
slow even when the program is small. For a huge program it will probably take JeX hours to
finish. One thing that is positive about JeX is that the JeX-files which JeX generates from
Java source file are very interesting. Here we can find all of the exceptions that that method or
constructor in the Java source file may encounter. This will help the developer to catch the
right exception when it is thrown.
JeX is best used during development since the user can insert the necessary package much
easier than when the program is finished. We have found that it took us a long time to go
through the source codes and insert packages for every file. JeX also makes it easier to change
the code during development than changing finished products.
23
5.2 Jikes Bytecode Toolkit
Jikes Bytecode Toolkit is developed by Chris Laffra, Doug Lorch, Dave Streeter, Frank Tip
and John Field at alphaWorks, IBM. It is a work in progress, but released versions have
already been used in several projects.
About Jikes
Jikes Bytecode Toolkit is a class library, written entirely in Java, which enables Java
programs to create, read, and write binary Java class files. It also lets the programmer query
and alter a high-level representation of the collection of the class files, and relations among
them.
Jikes provides a logical representation of the class file. Classes have methods, methods have
code and code contains bytecode instructions. In Jikes these are represented by objects that
can be read and manipulated.
From http://www.alphaworks.ibm.com/tech/jikesbt: Jikes Bytecode Toolkits’ model is quite
rich and provides a number of relationships, such as an easy way of finding each instruction
where a given class is allocated, where a given method is invoked, and where a given field is
accessed. It records which methods override which others, which will be inherited from other
classes, and even which method implements a method declared in a given interface (even if
via inheritance).
Jikes and Exceptions
Using Jikes, a programmer can browse a class' hierarchy to find which methods throw which
exceptions, and which methods catch them. This isn't unique to Jikes. What makes Jikes
special is the added possibility of inserting code in the class files without changing, or even
needing, the source code.
While the inserted code certainly can change the behavior of the program, this is not our
intention here. Every throw, throws, try, catch and finally statement could have a log writer
added. This would allow the program's execution and exceptional behavior to be logged and
analyzed, without cluttering up the code or intruding on the program in any other way.
Needless to say, this is work of much larger magnitude than this project. It might be an
interesting work for a diploma. Since the analyzer would be independent of the source code, it
could be applied to a many different programs. To best discover the behavior, the modified
program would have to be inserted into a real situation - since that is where the exceptions are
going to show up.
Conclusion
Jikes is an extension to the functionality of the Java programming language, and not an
executable program. As such, it is not what we were looking for. However, Jikes may prove
useful for creating such a program. It removes the need to write a code parser. Class files can
24
be searched for exception-handling. It even lets you add functionality without altering the
workings of the program. An application's execution can be observed and exception handling
details written to a database.
5.3 Teamstudio Analyzer for Java
Teamstudio Analyzer for Java is a commercial product developed by Teamstudio, Inc. This
section is based on a seven day free trial of the product and the contents of their homepage
www.teamstudio.com.
About Teamstudio Analyzer
Teamstudio Analyzer is a best-practices audit tool that uncovers errors in Java code. It is not a
standalone program, but is incorporated into different integrated development environments
(IDEs). Among the supported IDEs are JBuilder, Eclipse, JDeveloper and Sun ONE.
Once the program is installed it will check your code against a set of best practice rules. In
addition to about two hundred rules that come with the package, programmers can write their
own rules and insert them into the rule set. These rules are written as Java Classes.
When using the IDE after installing Teamstudio Analyzer, an additional window will open.
This window contains a list of detected errors, a description and a link to the line of code in
question. The rules range from brackets that aren’t closed to unnecessary imports to missing
or incomplete Javadoc.
Teamstudio Analyzer and Exceptions
Some breaches of best-practices for using exceptions are simple to detect. These are pretty
much covered by Teamstudio Analyzer. More complex scenarios are a lot harder to find, and
are not part of the Analyzer default rule set. Figure 5.3 shows Teamstudio Analyzer at work.
Errors discovered and reported by the Analyzer are:
?
?
?
?
?
Empty catch and finally blocks.
Throwable, Error or generic Exception caught.
Class names ending with Exception that doesn't inherit from Exception, or the other
way around.
Method declares that it throws an exception that isn't thrown in the code.
Unnecessary catch block, the code tries to catch an exception that isn't thrown.
25
Figure 5.3: Teamstudio Analyzer detects code violations.
Conclusion
While Teamstudio Analyzer doesn't have much support explicitly for exceptions, it supports
writing your own rules (as Java classes) and integrating them into the analysis and reporting
engine. However, since Analyzer is a commercial product, this probably isn't of particular
interest to anyone that doesn't already use Teamstudio's suite of tools.
5.4 J2EE Code Validation for WebSphere Studio
IBM WebSphere Studio is a suite of tools for development needs, including Web
development, enterprise-scale application development and development for wireless devices
[IWS2003]. This software includes several validation tools, but the most important one of all
and most relevant to this report is the J2EE Code Validation Preview.
About J2EE Code Validation Preview
J2EE Code Validation Preview for WebSphere Studio (J2EE Code Validation) is a tool that
automatically detects common error patterns and coding violations of best practices in Web
applications [JCV2003]. IBM has categorized error patterns and coding violations into
several, broad rule categories. J2EE Code Validation uses specialized analysis code for each
rule category, and in this technology preview, detects over four hundred violations.
These violations are very hard to detect by conventional means and they cause serious
performance degradation and system outages in real-world production environments. J2EE
Code Validation integrates with WebSphere Studio Application Developer and helps
developers identify and correct code defects early in the development cycle and before
applications reach production.
26
J2EE Code Validation does not replace traditional code analysis tools. It augments them by
conducting deep, static analysis of applications and by constructing all possible paths and all
possible object manipulations. This type of analysis is more thorough and is capable of
looking at a different set of problems. For example, using data flow analysis, J2EE Code
Validation can find many common cases of rare conditions which source scanning tools
cannot.
J2EE Code Validation and Exception
J2EE Code Validation does static program analysis (control and data flow) to check for many
bad coding patterns. The analyzer points out where in the program the exception handling has
been implemented poorly. Bad coding patterns include multiple exceptions handled by a
single catch block, catch block not catching the exact exception thrown (but its supertype) or
exceptions that have been swallowed etc.
Once the analysis is completed and if there are any coding violations, a list of the result with
explanations will pop up in the task bar. You can also see the analyzed steps that have been
taken during the analysis in the status bar. The user can click on the specific item in the task
bar for a detailed explanation of the coding violation. In the explanation box there will be
some highlighted text which you can click on. When you click on the text, the source view
will show you which part of the code contains the coding violation.
You can also choose to see the paths that lead to the coding violation in the task bar. A tree
structure will show you the paths in the application. When you click on it you can see the
source code associated with the paths, figure 5.4 shows how it looks like.
Figure 5.4: J2EE Code Validation at work
27
Conclusion
J2EE Code Validation is a just a plug-in for WebSphere Studio, which means that you have to
acquire the commercial product from IBM. There is a possibility for downloading the trial
software which is over one GB. Even though J2EE Code Validation is a neat tool for
analyzing coding violation including wrong exception handling, it is more suitable for people
who are already using WebSphere Studio. It takes some time to really get to know the
software with its tons of functions. WebSphere Studio is Java software and it is very slow
when starting up and requires at least 512MB of ram for the code violation to run.
5.5 Conclusion
Although these tools are very helpful, none of them fulfill the criteria for handling exceptions
in a better way. First, JeX is out of date and only creates lists of inconsistent methods. It does
not suggest how you could fix them or optimize them. Both J2EE Code Validation and
Teamstudio Analyzer detect bad or wrong use of exceptions, but they also do not advise you
how to fix or make them better. Jikes is not a tool or a program, but is a collection of class
libraries, which can help develop a Java exception analyzing program like JeX. Table 5.2
gives a summary of disadvantages and advantages of the tools we have investigated.
Name
JeX
-
-
-
Disadvantages
Setup up own
configuration file
Overlook several
problem with
exceptions
slow
bad documentation
Not a toolkit or
program
Free
Includes all classes of
exception
- Able to insert code in
class files
- Works with several
- Require license
Teamstudio Analyzer
programs
- Just a tool to integrate
- Detects several severe
with other
exception violations
development program
- Add new rules
- Points out error at
- Require license
J2EE Code Validation
source code
- Require WebSphere
- Show paths lead to code
Studio
violation
- User must be familiar
- Check precondition of
with WebSphere
methods
Studio
Table 5.2 Advantages and disadvantages with the programs we have tested.
Jikes
-
Advantages
It’s free
Detect error in method
Include almost all
exception class
28
6 Rules
In this chapter we introduce a set of rules that we have assembled throughout our work with
this project. With the waterfall model as the starting point (see Figure 6.1), we have made
rules for using exceptions in each phase of the model. We hope with these rules, we could
help system developers to be able to make more robust and reliable systems. Even though the
main bulk of the rules belong to the implementation phase, rules in preceding and subsequent
phases are also important.
System
Requirements
(1)
Architectural
Design
(2)
Detailed Design
(2)
Implementation
(16)
Testing
(1)
Figure 6.1 Waterfall model, with the number of rules at each phase.
6.1 System Requirements Phase
SR1: Clarify time spent on system robustness.
Creating a robust system takes time and resources. It is important that the developers and the
customer are clear from the start how important system robustness is to the project, and thus
which resources should be allotted to this end. The developers can err on both sides of this, in
one case creating a program the customer is not happy with – in the other, spending too much
time and money on something the customer is not asking for.
29
6.2 Architectural Design
AD1: Define risk communities and safety facades.
It is the job of the system architect to define how the system as a whole should react to an
exceptional situation. In a project where exception handling is not considered during the
architectural design phase, these important issues will be up to each individual programmer.
Setting up risk communities and safety facades makes it clear for everyone which modules are
responsible for which kinds of exceptions. See section 4.2 for details.
AD2: Create a system wide user alert system.
Centralize the handling of user alerts; otherwise you will end up with error messages spread
all over the code. A good user interface demands that the error messages presented to the user
are coherent and informative. To achieve this you need an easy way to inspect and revise
them all, as opposed to searching through thousands of lines of code looking for that one
strange error message your users have been getting. (See Code Excerpt 7.1.3)
6.3 Detailed Design
DD1: For each module, identify possible emergencies.
Look at which external sources the module depends on, and what would happen should they
stop working. [EAE2003] defines an emergency as a situation where the programmer of the
component doesn’t know what to do – there is no local help available: the component where
the emergency happened is unable to solve the problem. Emergencies range from
programming errors to unreachable databases and crashed neighbor systems.
DD2: Define how each module should respond to an undesired event.
There are certain problems that the module will encounter, some the module may be able to
solve and others that are impossible to handle. You have to separate these and define which of
the problems that may be solved and which the module has to pass on to the safety façade (see
rule AD1). It is important to limit the module to problems which it can handle.
6.4 Implementation
IM1: Do not use exceptions for control flow.
It is possible to use the exception handling mechanism for control flow also during normal
program flow. This makes the code harder to read, harder to analyze, significantly slower and
opens up for some horrible situations if an actual exception were to occur. Exception should,
as the name states, only be used in exceptional situations.
IM2: Use exceptions in exceptional situations.
In languages with less powerful exception handling mechanism than Java, programmers have
to signal an exceptional state through other means. Some common examples are returning a
null pointer, the value ‘-1’ for normally unsigned integers or the empty string. This is not
necessary in Java and should be avoided. If the caller of the method is unaware of these (often
undocumented) peculiars, the program will most likely start behaving in unforeseen manners.
Exceptions are part of the method interface and callers are forced to take them into account.
(See Code Excerpt 7.2.2)
30
IM3: Do not catch the generic exceptions.
A method might throw several different exceptions, and some programmers would be tempted
to simply catch the generic Exception class – especially if all the thrown exceptions are
handled in the same way. Resist the temptation before you create a monster that swallows all
exceptions thrown at it. All warnings that something has gone wrong will disappear or be
masked as something else entirely. (See Code Excerpt 7.1.2 and 7.2.4)
IM4: Do not throw the generic exceptions.
By throwing the generic Exception class, you remove all power from the user of the method.
The programmer has no way of knowing what has gone wrong, why it went wrong or what
can be done about it. In addition the user of the method is forced to violate rule IM2, allowing
for even more problems. If there is no exception matching yours in the standard exception
library, create your own exception. Never throw the Throwable, Exception or
RuntimeExcecption classes.
IM5: Ensure the object is in a stable state after throwing an exception.
Clean up with finally after a try clause so you’re always in a consistent state. Protect yourself
on the way up the call stack by deallocating any used resources. When the object is unstable
after the exception has been handled, it will most likely fail and throw exceptions again.
Another case is when other objects depend on that object and use it in an unstable state. This
might cause a myriad of exceptions. (See Code Excerpt 7.2.1)
IM6: Create abstract superclasses for related sets of exceptions
Regularly review existing exceptions usage and introduce abstract superclasses where the
same corrective action might be used for a set of different exception classes. Callers can then
name a single superclass in a catch block instead of all the individual exception classes for
which a corrective action is appropriate. Making the superclasses abstract enforces the
throwing of specific concrete exceptions
IM7: Do not leave a catch clause empty.
Only catch an exception if you can and will handle it. If you are unable to handle the
exception then pass it on, but never ignore it. Leaving the catch clause empty will trick the
user and the program into thinking that the exception has been handled while it did nothing.
Ignoring exceptions means that you will lose vital information and you might not even
register that an error has occurred. (See Code Excerpt 7.2.4)
IM8: Use runtime exceptions to indicate programming errors.
When an abnormal condition occurs, in situations where the client is unable to handle
exception, you should use runtime exceptions. This is an error and does not force the client to
catch nor to declare it in a throws clause. There is no point in handling runtime exceptions
since you will not be able to recover from them.
IM9: Use checked exceptions for recoverable conditions.
If the user can reasonably be expected to recover from the exceptions that occur, then use
checked exceptions. This forces the programmer to deal with the exceptional condition.
Checked exception is also used whenever a method is unable to fulfill its contract. The
contract includes preconditions that the client must fulfill and post conditions that the method
itself must fulfill.
31
IM10: Be careful when returning from a try clause with finally.
Always remember that the finally clause will execute regardless of what happens in a try
clause. So be careful when returning from try, since finally will produce a return value which
will overwrite the return value from try. To avoid this pitfall, do not issue a return, break or
continue statement inside the try clause, or just make sure that finally does not overwrite the
return value of the method.
IM11: Use separate try blocks for statements that throw the same exceptions.
When several statements throw the same exceptions, there are no ways of telling which of the
statements threw the exception. If you put each statement into separately try blocks, the
debugging will be much easier. When an exception occurs you will know exactly where and
which statement produced the exception. (See Code Excerpt 7.1.4)
IM12: Use standard exceptions provided by Java instead of creating new ones.
Using standard exceptions will make it easier for others to read since they will be dealing with
exceptions that they are familiar with. Besides standard exceptions are already well
documented which means that you do not need to document them again. If there are no
suitable standard exceptions to use then you are of course encouraged to make your own.
IM13: Do not propagate implementation specific exceptions.
Throw exceptions that make sense in the context of the method. Consider a method that can
throw a set of exceptions. Throwing all isn’t sensible, but throwing one of them binds the
implementation prematurely. Higher layers should catch lower-level exceptions and throw
exceptions that are more explainable in terms of higher-level abstraction. This is called
exception translation [EFJ2001]. (See Code Excerpt 7.1.1)
IM14: Use exception chaining when translating an exception.
Once you have decided to translate an exception, you should always chain the old thrown
exception to the new exception. In this way all exceptions are chained back to the lowest-level
where the first exception was thrown. This makes it easier to trace back to the source of the
problem. Debugging is also easier, since the entire stack trace is available.
(See Code Excerpt 7.1.1)
IM15: Use exceptions when a constructor fails.
When the initialization of an object fails, your only way to signal this is through an exception.
Swallowing exceptions in a constructor will result in live objects that have not been initialized
properly. Needless to say the behaviors of these objects are near impossible to know in
advance. (See Code Excerpt 7.2.3)
IM16: Document the exceptions well.
Use Javadoc @throws tag to document each exception. Also document precisely the condition
for the exception to occur. A good documentation of exceptions will make it easier to handle
the exceptions that occur. It is usually only the developer who has written the exceptions that
understands them, but with good documentation others will too.
32
6.5 Testing
TE1: Use a global flag to toggle debug information.
Many catch blocks will contain debug information, often in the form of ex.printStackTrace().
This should not be part of the final build. Use a simple global flag to separate debugging code
from normal code. It helps developers to clearly separate debug and normal code. After
testing, turn the debug information off. You need not go through the code and risk removing
too much or too little. Should it be necessary you can easily turn the debugging back on.
(See Code Excerpt 7.1.1)
33
7 The rules in practice
In this chapter we review the code of two large real life projects, IpManager and DIAS 2. We
look at how they have used exceptions in light of the rules in Chapter 6, pointing out why it is
good, why it is bad, and what can be done to improve the code.
When starting this project, we intended do a real test of the rules we made. We had plans to
create a test, apply our rules to a large project, and then test both the original program and the
modified one to see if there was any improvement. In this way we would check if the rules
improved the robustness of programs, or at least that particular program. We began working
on our rule set and learned more about designing robust programs with Java exceptions. After
many weeks we came to realize that our testing plan was not plausible.
Our single most important rule is that in order to create a robust program, exception handling
has to be accounted for throughout the process. It can not be added as an after-thought. Most
of our rules aren’t simple fixes – they are meant to force developers and programmers to think
differently about exceptions. The application of our rules can change how the entire system
works in an undesired state.
We opted for the second best alternative, examining and commenting on the program code to
find examples where the rules are in use or ought to be used. We hope this will help clarify
some of the rules, and give a better understanding of what can go wrong when using Java’s
exception handling mechanisms.
7.1 IpManager
IpManager is an Open Source project founded by Mario Aparicio and Salten Kraftsamband
AS in 2003. It is an application for managing networks, subnets and nodes within a company
network. It helps the administrator keep track of names and descriptions of nodes and
networks, and shows the number of nodes in a subnet with a certain network mask.
The Code Excerpts
Code Excerpt 7.1.1
public void saveDocument( Document doc, String filename )
throws SaveException {
try {
// method body
} catch( javax.xml.transform.TransformerException e ) {
if ( DEBUG ) {
System.err.println( e.getMessage() );
e.printStackTrace();
}
throw new SaveException( e.getMessage() );
}
}
34
Code Excerpt 7.1.1 shows in a good way how the rule IM13: Do not propagate
implementation
specific
exceptions
works.
The
implementation
bound
javax.xml.transform.TransformerException is translated to a SaveException that is meaningful
in the context of the saveDocument method. This allows for the saveDocument method to
change data representation later, without changing its interface. The implementation is
separated from the interface, and the user of the saveDocument method is presented with an
exception that makes sense.
The example also shows how the rule TE1: Use a global flag to toggle debug information
can be used. The stack trace would only be interesting to the programmer. Using this flag the
programmer can easily switch off debug information once the program is released. In addition
it makes the code easier to read for others, since it is clear what is debugging code and what is
normal code.
To further improve this particular piece of code we have two suggestions. One is described in
the rule IM14: Use exception chaining when translating exceptions; the old
TransformerException should be chained to the new SaveException. This way the entire stack
trace can be found for exceptions further out the call stack, and programmers interested in
what caused the SaveException have a way to find out. Second, following the naming
convention for exceptions suggests that SaveException should be renamed to
SaveFailedException.
Code Excerpt 7.1.2
try {
// method body
} catch ( Exception e ) {
System.err.println( e.getMessage() );
e.printStackTrace();
throw new XMLParseException(
"Error when reading file:" + e.getMessage() );
}
Code Excerpt 7.1.2 shows a violation of the rule IM3: Do not catch the generic exceptions.
This code excerpt will swallow and translate all kinds of exceptions to an
XMLParseException. Should an ArrayIndexOutOfBounds, NullPointerException or similar be
thrown in the method body it would not be treated in any other way than the exceptions the
programmer had intended to catch. Luckily the error message is echoed to the error stream;
otherwise the bug fixer would have a colossal headache ahead of him.
To fix this problem, the programmer should map out what exceptions are thrown in the
method body and catch those exceptions specifically. The program would function like the
programmer intended, and unexpected exceptions would not be swallowed.
Code Excerpt 7.1.3
} else if( foundOne == true ) {
throw new IllegalNetmaskException(
"The netmask is illegal. Legal values should be: \n" +
"255.255.255.X\n 255.255.X.0\n 255.X.0.0 or\n X.0.0.0" +
"Where X is:\n 0, 128, 192, 224, 240, 248, 252, 254 or 255" );
}
35
Code Excerpt 7.1.3 shows the violation of rule AD2: Create a system wide user alert
system. The error message is hard-coded into the exception. This makes it inconvenient
should you want to inspect, alter or reuse some or more of the error messages presented to the
end user.
Gathering all error messages in one place has several advantages. Adding support for different
languages based on user location, changing the user dialog for all messages or making sure all
the messages are coherent and formatted in the same way – these changes and several others
are made a lot simpler by having a centralized user alert system.
Code Excerpt 7.1.4
private TreeNode buildTreeFromXMLDocument( Document doc )
throws XMLParseException {
//some code
try {
for( int i = 0; i < networks.getLengt h(); i++ ) {
currentNetwork = (Element)networks.item( i );
networkN = new NetworkNode();
networkN.networkName = currentNetwork.getAttribute ( "Name" );
networkN.networkAddress = (java.net.Inet4Address)
java.net.InetAddress.getByAddress(
U tilityClass.extractIpAddress(
currentNetwork.getAttribute( "Address" ) ) );
networkN.netmask = UtilityClass.extractIpAddress(
currentNetwork.getAttribute( "Netmask1" ) );
networkN.onesNetmask =
Integer.parseInt(currentNetwork.getAttribute(
"Netmask2" ) );
networkN.description = currentNetwork.getAttribute(
"Description" );
networkTreeNode = new DefaultMutableTreeNode( networkN );
rootTreeNode.add( networkTreeNode );
addresses = currentNetwork.getChild Nodes();
// a lot more code like this
}
} catch ( Exception e ) {
System.err.println( e.getMessage() );
e.printStackTrace();
throw new XMLParseException(
"Error when reading file:" +e.getMessage() );
}
return rootTreeNode;
}
Code Excerpt 7.1.4 violates the rule IM3: Do not catch the generic exceptions. That was
also the case for Code Excerpt 7.1.2, so the same comments apply here. However, this one
also shows an example of IM11: Use separate try blocks for statements that throw the
same exceptions. Note that the code within the try block in the excerpt is shortened
considerably from the original source code. If an exception is raised anywhere in this massive
block of code, the programmer trying to debug the code will not know where in the try block
the error occurred. A common practice to work around this problem is to insert debugging
lines between every line of code in the try block, to see where the execution of the code stops.
36
A better and less crude way to get this information is to use several smaller try blocks around
the critical code.
7.2 DIAS 2
Distributed Intelligent Agent System 2 (DIAS 2) is part of a diploma thesis by Bård Smidsrød
Moen and Anders Aas at NTNU during autumn 1999. The diploma involves design and
implementation of a mobile agent architecture supporting interoperability. Central issues in
DIAS are inter-agent communication, dispatching, disposing of agents and how other agent
systems and clients interacts.
The Code Excerpts
Code Excerpt 7.2.1
try {
AccessController.beginPrivileged();
DEFAULT_USERNAME = System.getProperty( "user.name");
username = System.getProperty( "username", DEFAULT_USERNAME);
password = System.getProperty( "password", "");
} finally {
AccessController.endPrivileged();
}
Code Excerpt 7.2.1 shows the rule IM5: Ensure the object is in a stable state after
throwing an exception in practice. The finally block ensures that the resource with privileged
access is unlocked regardless of the outcome of the method calls in the try block. Note that
the try block is not followed by a catch block. This is not necessary, and in this case the
programmer has no intention of mending an error at this point in the code. The focus is on
making sure that the resource is unlocked.
Code Excerpt 7.2.2
public URL getAdpURL() {
try {
URL ret = new URL(adpName);
return ret;
} catch (MalformedURLException mue) {
System.out.println("Could'nt retrive the ADP's URL" +mue);
return null;
}
}
Code Excerpt 7.2.2 breaks the rule IM2: Use exceptions for exceptional situations. The
code translates a MalformedURLException to a returned null pointer. The caller of this
method has to check if the returned value is a null pointer, but this is not part of the methods
interface, and has to be documented separately. If the caller fails to check for a null pointer,
the program will crash with a NullPointerException once a malformed URL is used. If the
37
caller checks for a null pointer, the decision to throw a new exception or fix the situation has
to be made.
The same functionality is attained by simply propagating the MalformedURLException.
Exceptions are part of the method interface, so the possibly undocumented feature of a
returned null pointer is avoided. If the caller of getAdpURL can not mend the situation, that
method throws it further. The other solution would result in the first exception being thrown,
caught immediately afterwards, translated to a returned null pointer, just to see a new
exception thrown one step up in the call stack. This is not only awkward and hard-to-read
code, it is very slow code.
Code Excerpt 7.2.3
public AMPInfo(String strAmpInfo) {
XmlDocument xmlInfo = new XmlDocument();
try {
// large constructor body
} catch (SAXException se) {
System.out.println("AgentInfo::AgentInfo:" +
"The agentID could'nt be parsed \n"+se);
} catch (IOException ioe) {
System.out.println("AgentInfo::AgentInfo:" +
"An IO exception has been thrown \n"+ioe);
}
}
Code Excerpt 7.2.3 shows an example of breaking the rule IM15: Use exceptions when a
constructor fails. If a SAXEception or an IOException is thrown in the constructor body, the
AMPInfo object will not be properly initialized. A warning is echoed to System.out, informing
the user of the problem if the user is monitoring that channel. The program has no way of
knowing something went wrong during the initialization of the object, and will continue as if
nothing was wrong. Putting it a bit flippantly, a veritable exception generator has been let
loose in the system. One or more NullPointerExceptions is likely to occur. Worse still is if the
incomplete object does not trigger an exception, but is used by other subsystems in good faith,
feeding them incoherent and false data.
Code Excerpt 7.2.4
public void parseMessage(String str) throws ParseException {
try {
KQMLStreamTokenizer parser = new KQMLStreamTokenizer( new
BufferedReader(new StringReader(str)));
parseMessage(parser);
} catch (Exception e) {
e.printStackTrace();
}
}
Code Excerpt 7.2.4 violates the rule IM7: Do not leave a catch clause empty. A method that
only dumps the stack trace to System.out is empty for all practical considerations. No matter
38
what happens further down the call stack, this method will print the stack trace and end the
crisis; fixed or not. Imagine a program feeding tens of thousands of messages into this method
while an underlying system is down. The System.out channel will be flooded with messages,
and afterwards the program continues as if everything worked out as planned.
It also shows yet another example of the rule IM3: Do not catch the generic exceptions (see
Code Excerpt 7.1.2 for a discussion), but this one has an interesting twist. The method
declares that it throws a ParseException, but thanks to the catch clause that accepts all kinds
of exceptions, this method will never throw that exception. In fact, it will never throw any
kind of exception. This is obviously an oversight by the programmer, but it would never have
been missed if the programmer was not tempted to catch the generic exception, because then
the compiler would have complained.
There might seem to be an easy fix to this problem. Simply throw a new ParseException in
the catch block. See Code Excerpt 7.2.5 for why this is not such a good idea.
Code Excerpt 7.2.5
public void parseMessage(KQMLStreamTokenizer parser)
throws ParseException {
try {
// method body
} catch (Exception e) {
e.printStackTrace();
throw new ParseException();
}
}
The method in Code Excerpt 7.2.5 is the one being called in line five in Code Excerpt 7.2.4.
Notice that this catch block does in fact throw a ParseException. Any and all exceptions that
are thrown in the method body will be translated to a ParseException, and all information
contained in the old exception is lost. At the very least the rule IM14: Use exception
chaining when translating exceptions should be followed, chaining the old exception to the
new one.
Now take a look at Code Excerpt 7.2.4 again. What happens if the easy fix mentioned there
(making it throw a new ParseException) is applied? The program will throw the first
exception, catch it along with all other general exceptions, and then throw a new, nearly
identical exception. This is slow, it is cumbersome, it makes bug finding harder, and it is not
necessary.
39
8 Conclusions and Further Work
For a long time, developers have ignored and misused the powerful error handling mechanism
that Java offers. Being a third generation programming language, Java is one of the few
programming languages that provide very good exception handling. Developers should take
advantage of this and make better use of them and using them more often. Many developers
tend to avoid using exception or use them as little as possible because they lack the knowhow. Other developers consider exceptions as afterthought or an as-you-go addition, which
often results in difficult debugging and unstable programs.
Exception handling is an integral part of a robust system. It should not and cannot be added as
an afterthought. The biggest mistake for most developers is that they deal with exceptions far
too late in the development. Not until they have reached the implementation phase do they
become aware of how important a role exception handling plays in the development process.
To build a robust and reliable system, developers need to take exception handling into account
throughout the entire development process.
It is important that developers understand and know how to use the exception handling
mechanisms. This information is not always obvious and there is great potential for
improvement in this field. Not only do they need to know how to use this powerful
mechanism, they also have to understand how important a role it plays in the development
process. Wrong use of exceptions can lead to severe problems, like bug ridden, unstable
programs.
We present in this paper a set of rules to help developers reach their goals and improve their
programs. We believe that developers following these rules will develop more robust and
reliable systems. Developers are forced to take exceptions into account already at the system
requirements phase. The sooner exceptions are taken into consideration, the easier they are to
implement and to manage. The rules at each stage, from system requirements to testing, are
equally important to follow.
We inspected two real life projects in Chapter 7, and saw that there was definite room for
improvement in the exception handling of both projects. If the projects were to abide fully by
the rules in this paper, large parts of the code would have to be completely rewritten. This
made it not only prohibitive for us to test our rules formally, but it emphasizes the importance
of designing with exceptions in mind from the start.
Further Work
We were not able to formally test our rules by modifying an existing program (see Chapter 7).
This makes it very interesting to see how the rules fare in a case study. An idea is to follow
two groups of developers during their process of developing a program. One group gets a
presentation of this paper and the rules, and is encouraged to comply with the rules. The other
group is not asked to do this, but is simply watched. Following the two groups through all the
phases of development, and looking at the robustness of the final products, might give some
interesting insights into what part exception handling plays in the stability of a program and if
the rules actually help developers create better programs.
40
9 References
[SUN2001]
Java Community Process Maintenance Review for J2SE 1.4.
http://java.sun.com/j2se/1.4/jcp/j2se-1_4-mr_docs-spec/core_libraries.html,
2001.
[JEX2002]
Jex, A Tool for Analyzing Exception Flow in Java Programs,
http://www.cs.ubc.ca/~mrobilla/jex/, 2002
[EHO2003] A. Romanovsky, C. Dony, J. L. Knudsen, A. Tripathi: Exception Handling in
Object Oriented Systems: Towards Emerging Application Areas and New
Programming Paradigms. ECOOP, 2003.
[FAE2003] B. Venners: Failure and Exceptions, A Conversation with James Gosling.
http://www.artima.com/intv/solid.html, 2003.
[GAM1995] E. Gamma, Helm, Johnson, Vlissides: Design Patterns. Addison-Wesley, 1995.
[EAE2003] J. Siedersleben: Errors and Exceptions – Rights and Responsibilities. ECOOP,
2003.
[EFJ2001]
J. Bloch: Effective Java Programming Language Guide. Addison-Wesley,
2001.
[IWS2003] IBM WebSphere Studio, http://www106.ibm.com/developerworks/websphere/zones/studio/bigpicture.html
[JCV2003] J2EE Code Validation Preview for WebSphere Studio,
http://www106.ibm.com/developerworks/websphere/downloads/j2ee_code_validation.html
[EIJ2000]
G. Pal, S. Bansal: Exceptions in Java: Nothing exceptional about them.
http://www.javaworld.com/javaworld/jw-08-2000/jw-0818-exceptions.html,
2000
[DJN2003] B. Eckel: Does Java need Checked Exceptions?,
http://www.mindview.net/Etc/Discussions/CheckedExceptions, 2003.
[EJE2003]
J. R. Kiniry: Exceptions in Java and Eiffel: Two Extremes in Exception Design
and Application. ECOOP, 2003.
[PRJ1999]
P. Haggar: Practical Java Programming Language Guide. Addison-Wesley,
1999.
[DRJ2000] M. P. Robillard, G. C. Murphy: Designing Robust Java Programs with
Exceptions. ACM, 2000.
[EJA2001]
C. Ratliff: Exceptions in Java.
http://www.lily.org/users/cratliff/mejug/Exceptions%20in%20Java.ppt, 2001.
[AEU2003] D. Reimer, H. Srinivasan: Analyzing Exception Usage in Large java
Applications. ECOOP, 2003.
[UML2003] R. Miller: What's New in UML 2? Model Exceptions.
http://community.borland.com/article/0,1410,30169,00.html, 2003.
[EXH1975] John B. Goodenough: Exception Handling: Issues and a Propsed Notation.
ACM, 1975.
[AEF1999] M. P. Robillard, G. C. Murphy: Analyzing Exception Flow in Java Programs.
UBC, 1999.
[IDE2003]
B. G. Ryder, M. L. Soffa: Influences on Design of Exception Handling. ACM,
2003.
[EHM1999] A. F. Garcia, D. M. Beder, C. M. F. Rubira: An Exception Handling
Mechanism for Developing Dependable Object-Oriented Software Based on a
Meta-Level Approach. UNICAMP.1999
[EXS2003] J. D Luca: The Coad Letter: Modeling and Design Edition, Issue 90,
41
[PIJ2001]
Exceptional Strategies.
http://bdn.borland.com/article/0,1410,29664,00.html#s8, 2003.
M. Grand: Overview of Design Patterns,
http://www.clickblocks.org/patterns1/pattern_synopses.htm, 2001.
42