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.
Design
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, youll see the rewards (even
if no one can measure them). Not only does it give you a program thats
easier to build and debug, but its also easier to understand and
maintain, and thats where the financial value lies. This point can take
some experience to understand, because it can appear that youre not being
productive while youre making a piece of code elegant. Resist the urge to
hurry; it will only slow you down.
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 dont have meaning, it can be as bad as not
having adequate indirection.
Make classes as atomic as possible. Give each class a single, clear
purposea 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 its doing too much and should be broken up. 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. 4) Other suggestions can be found in
Refactoring: Improving the Design of Existing Code by Martin Fowler
(Addison-Wesley 1999).
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.
Dont 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
methods.
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.
Dont 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 youre 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 dont try to
anticipate all the ways that your class might be used. As the class is
used, youll 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, thats fine; it wont 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 wont disturb any calls to the existing method.
Read your classes aloud to make sure theyre 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
overridden methods.
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 hierarchiespreferably 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
type.
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? Noin 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
class.
In general, follow the Sun coding conventions. These are available
at 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
youve 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
https://jcsc.sourceforge.net.
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 elses coding
style if it doesnt 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
means.