This appendix contains
suggestions to help guide you while performing low-level program design, and
also while writing
code.
Capitalize the first letter of class names. The
first letter of fields, methods, and objects (handles) should be lowercase. All
identifiers should run their words together, and capitalize the first letter of
all intermediate words. For
example: ThisIsAClassName thisIsAMethodOrFieldName Capitalize
all the letters of staticfinal primitive identifiers that
have constant initializers in their definitions. This indicates they are
compile-time constants. Packages 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
1.2.)
When creating a class for
general-purpose use, follow a “canonical form” and include
definitions for equals( ), hashCode( ),
toString( ), clone( ) (implement Cloneable), and
implement Serializable.
For each
class you create, consider including a main( ) that contains code to
test that class. 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 re-run the tests. This
code also provides examples of how to use your
class.
Methods should be kept to brief,
functional units that describe and implement a discrete part of a class
interface. Ideally, methods should be concise; if they are long you might want
to search for a way to break them up into several shorter methods. This will
also foster reuse within your class. (Sometimes methods must be large, but they
should still do just one thing.)
When you
design a class, think about the client programmer’s perspective (the class
should be fairly obvious to use) and the perspective of the person maintaining
the code (anticipate the kind of changes that will be made, to make them
easy).
Try to keep classes small and
focused. Clues to suggest redesign of a class
are: 1) A complicated switch statement:
consider using polymorphism 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
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.
Privacy is especially important when dealing with multithreading – only
private fields can be protected against un-synchronized
use.
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.
If you must do something
ugly, at least localize the ugliness inside a
class.
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 (see
“Improving the code with an inner class”
on page 701).
Use comments liberally, and
use the javadoc comment-documentation syntax to produce your program
documentation.
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.
In terms of
constructors and exceptions, you’ll generally want to re-throw any
exceptions that you catch while in a constructor if it causes failure of the
creation of that object, so the caller doesn’t continue blindly, thinking
that the object was created correctly.
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 cleanup( ) that clearly suggests its purpose. In addition,
place a boolean flag in the class to indicate whether the object has been
cleaned up. 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.runFinalizersOnExit(true) to
ensure this behavior.)
If an object must
be cleaned up (other than by garbage collection) within a particular scope, use
the following approach: 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
collection of objects, transfer them to an array (especially if you’re
returning this collection 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.
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.
Inside constructors, do only what
is necessary to set the object into the proper state. Actively avoid calling
other methods (except for final methods) since those methods can be
overridden by someone else to produce unexpected results during construction.
(See Chapter 7 for details.)
Objects
should not simply hold some data; they should also have well-defined
behaviors.
Choose composition first when
creating new classes from existing classes. You should only used inheritance if
it is required by your design. If you use inheritance where composition will
work, your designs will become needlessly
complicated.
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 inheriting different classes
to represent colors instead of using a “color”
field.
To avoid a highly frustrating
experience, make sure that there’s only one 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.
When using the event
“adapters” in the Java 1.1 AWT, there’s a particularly easy
pitfall you can encounter. If you override one of the adapter methods 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.
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, 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.
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
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, 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 online Java
documentation should convince you.
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 pair of fresh eyes can reveal problems at a
stage where it’s much easier to fix them and more than pays for the time
and money “lost” to the walkthrough
process.
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). And
there’s nothing that matches the feeling that comes from knowing
you’ve got an amazing design. Resist the urge to hurry; it will only slow
you down.
You can find other programming
guidelines on the Web. A good set of links can be found at
http://www.ulb.ac.be/esp/ip-Links/Java/joodcs/mm-WebBiblio.html