Java Programming Guidelines
This appendix contains suggestions to help guide you in performing low-level program design and in writing code.
Naturally, these are guidelines and not rules. The idea is to use them as inspirations and to remember that there are occasional situations where they should be bent or broken.
- Elegance always pays off. In the short term it might seem like it
takes much longer to come up with a truly graceful solution to a problem, but
when it works the first time and easily adapts to new situations instead of
requiring hours, days, or months of struggle, you’ll see the rewards (even
if no one can measure them). Not only does it give you a program that’s
easier to build and debug, but it’s also easier to understand and
maintain, and that’s where the financial value lies. This point can take
some experience to understand, because it can appear that you’re not being
productive while you’re making a piece of code elegant. Resist the urge to
hurry; it will only slow you down.
- First make it work, then make it fast. This is true even if you are
certain that a piece of code is really important and that it will be a principal
bottleneck in your system. Don’t do it. Get the system going first with as
simple a design as possible. Then if it isn’t going fast enough, profile
it. You’ll almost always discover that “your” bottleneck
isn’t the problem. Save your time for the really important stuff.
Remember the “divide and conquer” principle. If the
problem you’re looking at is too confusing, try to imagine what the basic
operation of the program would be, given the existence of a magic
“piece” that handles the hard parts. That “piece” is an
object—write the code that uses the object, then look at the object and
encapsulate its hard parts into other objects, etc.
- Separate the class creator from the class user (client
programmer). The class user is the “customer” and
doesn’t need or want to know what’s going on behind the scenes of
the class. The class creator must be the expert in class design and write the
class so that it can be used by the most novice programmer possible, yet still
work robustly in the application. Think of the class as a service provider
for other classes. Library use will be easy only if it’s transparent.
- When you create a class, attempt to make your names so clear that
comments are unnecessary. Your goal should be to make the client
programmer’s interface conceptually simple. To this end, use method
overloading when appropriate to create an intuitive, easy-to-use interface.
- Your analysis and design must produce, at minimum, the classes in your
system, their public interfaces, and their relationships to other classes,
especially base classes. If your design methodology produces more than that,
ask yourself if all the pieces produced by that methodology have value over the
lifetime of the program. If they do not, maintaining them will cost you. Members
of development teams tend not to maintain anything that does not contribute to
their productivity; this is a fact of life that many design methods don’t
- Automate everything. Write the test code first (before you
write the class), and keep it with the class. Automate the running of your tests
through a build tool—you’ll probably want to use Ant, the defacto
standard Java build tool. This way, any changes can be automatically verified by
running the test code, and you’ll immediately discover errors. Because you
know that you have the safety net of your test framework, you will be bolder
about making sweeping changes when you discover the need. Remember that the
greatest improvements in languages come from the built-in testing provided by
type checking, exception handling, etc., but those features take you only so
far. You must go the rest of the way in creating a robust system by filling in
the tests that verify features that are specific to your class or program.
Write the test code first (before you write the class) in order to verify
that your class design is complete. If you can’t write test code, you
don’t know what your class looks like. In addition, the act of writing the
test code will often flush out additional features or constraints that you need
in the class—these features or constraints don’t always appear
during analysis and design. Tests also provide example code showing how your
class can be used.
- All software design problems can be simplified by introducing an extra
level of conceptual indirection. This fundamental rule of software
is the basis of abstraction, the primary feature of object-oriented programming.
In OOP, we could also say this as: “If your code is too complicated, make
- An indirection should have a meaning (in concert with guideline 9).
This meaning can be something as simple as “putting commonly used code in
a single method.” If you add levels of indirection (abstraction,
encapsulation, etc.) that don’t have meaning, it can be as bad as not
having adequate indirection.
- Make classes as atomic as possible. Give each class a single, clear
purpose—a cohesive service that it provides to other classes. If your
classes or your system design grows too complicated, break complex classes into
simpler ones. The most obvious indicator of this is sheer size; if a class is
big, chances are it’s doing too much and should be broken up.
suggest redesign of a class are:
1) A complicated switch statement: consider
2) A large number of methods that cover broadly
different types of operations: consider using several classes.
3) A large
number of member variables that concern broadly different characteristics:
consider using several classes.
4) Other suggestions can be found in
Refactoring: Improving the Design of Existing Code by Martin Fowler
- Watch for long argument lists. Method calls then become difficult to
write, read, and maintain. Instead, try to move the method to a class where it
is (more) appropriate, and/or pass objects in as arguments.
- Don’t repeat yourself. If a piece of code is recurring in many
methods in derived classes, put that code into a single method in the base class
and call it from the derived-class methods. Not only do you save code space, but
you provide for easy propagation of changes. Sometimes the discovery of this
common code will add valuable functionality to your interface. A simpler version
of this guideline also occurs without inheritance: If a class has methods that
repeat code, factor that code into a common method and call it from the other
- Watch for switch statements or chained if-else clauses.
This is typically an indicator of type-check coding, which means that you
are choosing what code to execute based on some kind of type information (the
exact type may not be obvious at first). You can usually replace this kind of
code with inheritance and polymorphism; a polymorphic method call will perform
the type checking for you and allow for more reliable and easier extensibility.
- From a design standpoint, look for and separate things that change from
things that stay the same. That is, search for the elements in a system that
you might want to change without forcing a redesign, then encapsulate those
elements in classes. You can learn much more about this concept in Thinking
in Patterns (with Java) at www.BruceEckel.com.
- Don’t extend fundamental functionality by subclassing. If an
interface element is essential to a class it should be in the base class, not
added during derivation. If you’re adding methods by inheriting, perhaps
you should rethink the design.
- Less is more. Start with a minimal interface to a class, as small and
simple as you need to solve the problem at hand, but don’t try to
anticipate all the ways that your class might be used. As the class is
used, you’ll discover ways you must expand the interface. However, once a
class is in use, you cannot shrink the interface without breaking client code.
If you need to add more methods, that’s fine; it won’t break code.
But even if new methods replace the functionality of old ones, leave the
existing interface alone (you can combine the functionality in the underlying
implementation if you want). If you need to expand the interface of an existing
method by adding more arguments, create an overloaded method with the new
arguments; this way, you won’t disturb any calls to the existing method.
- Read your classes aloud to make sure they’re logical. Refer to
the relationship between a base class and derived class as “is-a”
and member objects as “has-a.”
- When deciding between inheritance and composition, ask if you need to
upcast to the base type. If not, prefer composition (member objects) to
inheritance. This can eliminate the perceived need for multiple base types. If
you inherit, users will think they are supposed to upcast.
- Use fields for variation in value, and method overriding for variation in
behavior. That is, if you find a class that uses state variables along with
methods that switch behavior based on those variables, you should probably
redesign it to express the differences in behavior within subclasses and
- Watch for overloading. A method should not conditionally execute code
based on the value of an argument. In this case, you should create two or more
overloaded methods instead.
- Use exception hierarchies—preferably derived from specific
appropriate classes in the standard Java exception hierarchy. The person
catching the exceptions can then write handlers for the specific types of
exceptions, followed by handlers for the base type. If you add new derived
exceptions, existing client code will still catch the exception through the base
- Sometimes simple aggregation does the job. A “passenger comfort
system” on an airline consists of disconnected elements: seat, air
conditioning, video, etc., and yet you need to create many of these in a plane.
Do you make private members and build a whole new interface? No—in this
case, the components are also part of the public interface, so you should create
public member objects. Those objects have their own private implementations,
which are still safe. Be aware that simple aggregation is not a solution to be
used often, but it does happen.
- Consider the perspective of the client programmer and the person
maintaining the code. Design your class to be as obvious as possible to use.
Anticipate the kind of changes that will be made, and design your class so that
those changes will be easy.
- Watch out for “giant object syndrome.” This is often an
affliction of procedural programmers who are new to OOP and who end up writing a
procedural program and sticking it inside one or two giant objects. With the
exception of application frameworks, objects represent concepts in your
application, not the application itself.
- If you must do something ugly, at least localize the ugliness inside a
- If you must do something nonportable, make an abstraction for that
service and localize it within a class. This extra level of indirection
prevents the nonportability from being distributed throughout your program.
(This idiom is embodied in the Bridge Pattern, among others).
Objects should not simply hold some data. They should also have
well-defined behaviors. (Occasionally, “data objects” are
appropriate, but only when used expressly to package and transport a group of
items when a generalized container is innappropriate.)
- Choose composition first when creating new classes from existing
classes. You should only use inheritance if it is required by your design.
If you use inheritance where composition will work, your designs will become
- Use inheritance and method overriding to express differences in behavior,
and fields to express variations in state. An extreme example of what not to
do is to inherit different classes to represent colors instead of using a
- Watch out for variance. Two semantically different objects may
have identical actions, or responsibilities, and there is a natural temptation
to try to make one a subclass of the other just to benefit from inheritance.
This is called variance, but there’s no real justification to force a
superclass/subclass relationship where it doesn’t exist. A better solution
is to create a general base class that produces an interface for both as derived
classes; it requires a bit more space, but you still benefit from inheritance
and will probably make an important discovery about the design.
Watch out for limitation during inheritance. The clearest
designs add new capabilities to inherited ones. A suspicious design removes old
capabilities during inheritance without adding new ones. But rules are made to
be broken, and if you are working from an old class library, it may be more
efficient to restrict an existing class in its subclass than it would be to
restructure the hierarchy so your new class fits in where it should, above the
- Use design patterns to eliminate “naked functionality.”
That is, if only one object of your class should be created, don’t bolt
ahead to the application and write a comment “Make only one of
these.” Wrap it in a singleton. If you have a lot of messy code in your
main program that creates your objects, look for a creational pattern like a
factory method in which you can encapsulate that creation. Eliminating
“naked functionality” will not only make your code much easier to
understand and maintain, but it will also make it more bulletproof against the
well-intentioned maintainers that come after you.
- Watch out for “analysis paralysis.” Remember that you
must usually move forward in a project before you know everything, and that
often the best and fastest way to learn about some of your unknown factors is to
go to the next step rather than trying to figure it out in your head. You
can’t know the solution until you have the solution. Java has
built-in firewalls; let them work for you. Your mistakes in a class or set of
classes won’t destroy the integrity of the whole system.
When you think you’ve got a good analysis, design, or
implementation, do a walkthrough. Bring someone in from outside your
group—this doesn’t have to be a consultant, but can be someone from
another group within your company. Reviewing your work with a fresh pair of eyes
can reveal problems at a stage when it’s much easier to fix them, and more
than pays for the time and money “lost” to the walkthrough process.
- In general, follow the Sun coding conventions. These are available
java.sun.com/docs/codeconv/index.html (the code in this book
follows these conventions as much as I was able). These are used for what
constitutes arguably the largest body of code that the largest number of Java
programmers will be exposed to. If you doggedly stick to the coding style
you’ve always used, you will make it harder for your reader. Whatever
coding conventions you decide on, ensure that they are consistent throughout the
project. There is a free tool to automatically reformat Java code at
https://jalopy.sourceforge.net. You can find a free style checker at
- Whatever coding style you use, it really does make a difference if your
team (and even better, your company) standardizes on it. This means to the
point that everyone considers it fair game to fix someone else’s coding
style if it doesn’t conform. The value of standardization is that it takes
less brain cycles to parse the code, so that you can focus more on what the code
- Follow standard capitalization rules. Capitalize the first letter of
class names. The first letter of fields, methods, and objects (references)
should be lowercase. All identifiers should run their words together, and
capitalize the first letter of all intermediate words. For
all the letters (and use underscore word separators) of static
final primitive identifiers that have constant initializers in their
definitions. This indicates that they are compile-time constants.
are a special case—they are all lowercase letters, even for
intermediate words. The domain extension (com, org, net, edu, etc.) should also
be lowercase. (This was a change between Java 1.1 and Java 2.)
Don’t create your own “decorated” private field
names. This is usually seen in the form of prepended underscores and
characters. Hungarian notation is the worst example of this, where you attach
extra characters that indicate data type, use, location, etc., as if you were
writing assembly language and the compiler provided no extra assistance at all.
These notations are confusing, difficult to read, and unpleasant to enforce and
maintain. Let classes and packages do the name scoping for you. If you feel that
you must decorate your names to prevent confusion, your code is probably too
confusing anyway and should be simplified.
- Follow a “canonical form” when creating a class for
general-purpose use. Include definitions for equals( ),
hashCode( ), toString( ), clone( )
(implement Cloneable, or choose some other object copying approach, like
serialization), and implement Comparable and Serializable.
Use the JavaBeans “get,” “set,” and
“is” naming conventions for methods that read and change
private fields, even if you don’t think you’re making a
JavaBean at the time. Not only does it make it easy to use your class as a Bean,
but it’s a standard way to name these kinds of methods, so it will be more
easily understood by the reader.
- For each class you create, include JUnit tests for that class (see
www.junit.org, and examples in Chapter 15). You don’t need to
remove the test code to use the class in a project, and if you make any changes,
you can easily rerun the tests. This code also provides examples of how to use
- Sometimes you need to inherit in order to access protected members
of the base class. This can lead to a perceived need for multiple base
types. If you don’t need to upcast, first derive a new class to perform
the protected access. Then make that new class a member object inside any class
that needs to use it, rather than inheriting.
- Avoid the use of final methods for efficiency purposes. Use
final only when the program is running, but not fast enough, and your
profiler has shown you that a method invocation is the bottleneck.
If two classes are associated with each other in some functional way
(such as containers and iterators), try to make one an inner class of the
other. This not only emphasizes the association between the classes, but it
allows the class name to be reused within a single package by nesting it within
another class. The Java containers library does this by defining an inner
Iterator class inside each container class, thereby providing the
containers with a common interface. The other reason you’ll want to use an
inner class is as part of the private implementation. Here, the inner
class is beneficial for implementation hiding rather than the class association
and prevention of namespace pollution noted above.
- Anytime you notice classes that appear to have high coupling with each
other, consider the coding and maintenance improvements you might get by using
inner classes. The use of inner classes will not uncouple the classes, but
rather make the coupling explicit and more convenient.
- Don’t fall prey to premature optimization. This way lies
madness. In particular, don’t worry about writing (or avoiding) native
methods, making some methods final, or tweaking code to be efficient when
you are first constructing the system. Your primary goal should be to prove the
design. Even if the design requires a certain efficiency, first make it work,
then make it fast.
- Keep scopes as small as possible so the visibility and lifetime of your
objects are as small as possible. This reduces the chance of using an object
in the wrong context and hiding a difficult-to-find bug. For example, suppose
you have a container and a piece of code that iterates through it. If you copy
that code to use with a new container, you may accidentally end up using the
size of the old container as the upper bound of the new one. If, however, the
old container is out of scope, the error will be caught at compile time.
Use the containers in the standard Java library. Become proficient
with their use and you’ll greatly increase your productivity. Prefer
ArrayList for sequences, HashSet for sets, HashMap for
associative arrays, and LinkedList for stacks (rather than Stack,
although you may want to create an adapter to give a stack interface) and queues
(which may also warrant an adapter, as shown in this book). When you use the
first three, you should upcast to List, Set, and Map,
respectively, so that you can easily change to a different implementation if
- For a program to be robust, each component must be robust. Use all
the tools provided by Java—access control, exceptions, type checking,
synchronization, and so on—in each class you create. That way you can
safely move to the next level of abstraction when building your system.
Prefer compile-time errors to run-time errors. Try to handle an error
as close to the point of its occurrence as possible. Catch any exceptions in the
nearest handler that has enough information to deal with them. Do what you can
with the exception at the current level; if that doesn’t solve the
problem, rethrow the exception.
- Watch for long method definitions. Methods should be brief,
functional units that describe and implement a discrete part of a class
interface. A method that is long and complicated is difficult and expensive to
maintain, and is probably trying to do too much all by itself. If you see such a
method, it indicates that, at the least, it should be broken up into multiple
methods. It may also suggest the creation of a new class. Small methods will
also foster reuse within your class. (Sometimes methods must be large, but they
should still do just one thing.)
- Keep things as “private as possible.” Once
you publicize an aspect of your library (a method, a class, a field), you can
never take it out. If you do, you’ll wreck somebody’s existing code,
forcing them to rewrite and redesign. If you publicize only what you must, you
can change everything else with impunity, and since designs tend to evolve, this
is an important freedom. In this way, implementation changes will have minimal
impact on derived classes. Privacy is especially important when dealing with
multithreading—only private fields can be protected against
private fields, but it usually makes sense to give the methods of package
access rather than making them public.
- Use comments liberally, and use the javadoc comment-documentation
syntax to produce your program documentation. However, the comments should
add geniune meaning to the code; comments that only reiterate what the code is
clearly expressing are annoying. Note that the typical verbose detail of Java
class and method names reduce the need for some comments.
- Avoid using “magic numbers”—which are numbers
hard-wired into code. These are a nightmare if you need to change them, since
you never know if “100” means “the array size” or
“something else entirely.” Instead, create a constant with a
descriptive name and use the constant identifier throughout your program. This
makes the program easier to understand and much easier to maintain.
When creating constructors, consider exceptions. In the best case,
the constructor won’t do anything that throws an exception. In the
next-best scenario, the class will be composed and inherited from robust classes
only, so they will need no cleanup if an exception is thrown. Otherwise, you
must clean up composed classes inside a finally clause. If a constructor
must fail, the appropriate action is to throw an exception, so the caller
doesn’t continue blindly, thinking that the object was created correctly.
- Inside constructors, do only what is necessary to set the object into the
proper state. Actively avoid calling other methods (except for final
methods), because those methods can be overridden by someone else to produce
unexpected results during construction. (See Chapter 7 for details.) Smaller,
simpler constructors are less likely to throw exceptions or cause problems.
- If your class requires any cleanup when the client programmer is finished
with the object, place the cleanup code in a single, well-defined method,
with a name like dispose( ) that clearly suggests its purpose. In
addition, place a boolean flag in the class to indicate whether
dispose( ) has been called so that finalize( ) can check
for “the termination condition” (see Chapter 4).
- The responsibility of finalize( ) can only be to verify
“the termination condition” of an object for debugging. (See
Chapter 4.) In special cases, it might be needed to release memory that would
not otherwise be released by the garbage collector. Since the garbage collector
might not get called for your object, you cannot use finalize( ) to
perform necessary cleanup. For that you must create your own
dispose( ) method. In the finalize( ) method for the
class, check to make sure that the object has been cleaned up and throw a class
derived from RuntimeException if it hasn’t, to indicate a
programming error. Before relying on such a scheme, ensure that
finalize( ) works on your system. (You might need to call
System.gc( ) to ensure this behavior.)
- If an object must be cleaned up (other than by garbage collection) within
a particular scope, use the following idiom: initialize the object and, if
successful, immediately enter a try block with a finally clause
that performs the cleanup.
- When overriding finalize( ) during inheritance, remember to
call super.finalize( ). (This is not necessary if Object
is your immediate superclass.) You should call super.finalize( ) as
the final act of your overridden finalize( ) rather than the
first, to ensure that base-class components are still valid if you need them.
- When you are creating a fixed-size container of objects, transfer them to
an array, especially if you’re returning this container from a method.
This way you get the benefit of the array’s compile-time type checking,
and the recipient of the array might not need to cast the objects in the array
in order to use them. Note that the base-class of the containers library,
java.util.Collection, has two toArray( ) methods to
- Choose interfaces over abstract classes. If you know
something is going to be a base class, your first choice should be to make it an
interface, and only if you’re forced to have method definitions or
member variables should you change it to an abstract class. An
interface talks about what the client wants to do, while a class tends to
focus on (or allow) implementation details.
- To avoid a highly frustrating experience, make sure that there is only
one unpackaged class of each name anywhere in your classpath. Otherwise, the
compiler can find the identically-named other class first, and report error
messages that make no sense. If you suspect that you are having a classpath
problem, try looking for .class files with the same names at each of the
starting points in your classpath. Ideally, put all your classes within
- Watch out for accidental overloading. If you attempt to override a
base-class method and you don’t quite get the spelling right, you’ll
end up adding a new method rather than overriding an existing method. However,
this is perfectly legal, so you won’t get any error message from the
compiler or run-time system; your code simply won’t work correctly.
Watch out for premature optimization. First make it work, then make
it fast—but only if you must, and only if it’s proven that there is
a performance bottleneck in a particular section of your code. Unless you have
used a profiler to discover a bottleneck, you will probably be wasting your
time. The hidden extra cost of performance tweaks is that your code becomes less
understandable and maintainable.
- Remember that code is read much more than it is written. Clean
designs make for easy-to-understand programs, but comments, detailed
explanations, tests, and examples are invaluable. They will help both you and
everyone who comes after you. If nothing else, the frustration of trying to
ferret out useful information from the JDK documentation should convince you.