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
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 202).
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