Guidelines
This chapter contains guidelines that progress from very general OO guidelines to more Java specific development guidelines. Many of these guidelines are extracted from or based on other sources and the source to the guideline is noted immediately below the guideline or in footnotes.
Introduction
Development guidelines provide two high-level benefits: they reinforce techniques to design and code better and they provide consistency even when there is no clear best choice. This chapter provides guideline recommendations that focus on the first of these benefits: all of the guidelines in here are meant to help build better software. A separate (Standards) document can then chose which of these guidelines a team would like to use or the consistency can be more informally maintained by team interaction and code reviews.
These guidelines are simply recommendations. A particular guideline may be extremely beneficial for some teams and counter-productive for others. Try the guidelines out to see how well they work for your teams particular needs. Sometimes the guidelines conflict because they have advantages in different contexts. A standards document would be expected to define which guidelines a team chooses, or which are acceptable depending on the circumstance. Or the standards could be determined by the teams experiences and culture.
Sources of Guidelines
The guidelines documented here are summaries and extractions from more general guidelines, design principles, methodologies, and development experiences. The following sources document these general "software engineering" practices.
Software design and object oriented design are enormous subjects and there are numerous books and other resources covering them. See the References chapter of this document for a good collection of sources and the Definitions chapter for some discussions of what they focus on. The following list contains a few good introductions to OO related design:
|
Object-Oriented Analysis and Design with Applications
|
Booch 94 |
|
Designing Object-Oriented Software
|
Wirfs-Brock+WW 90 |
|
Object-Oriented Modeling and Design
|
Rumbaugh+BPEL 91 |
|
Design Patterns
|
Gamma+HJV 95 |
|
Information Modeling
|
Kilov+R 94 | For Java development guidelines, we recommend the following resources from the Java, Smalltalk, and Eiffel literature. We chose these other languages because Java is best thought of as simplified Smalltalk with interfaces and bare-bones compile-time typing added to it. The syntax looks like C or C++, but the semantics are much closer to a cross between a very diluted Eiffel and Smalltalk. The literature of good design techniques for Eiffel and Smalltalk is also more mature than Java literature.
|
Java Design: Building Better Apps & Applets
|
Coad+M 96 |
|
Doug Lea's Java coding standards
|
Lea-1 |
|
Smalltalk Best Practice Patterns: Coding
|
Beck 96 |
|
Code Complete
|
McConnell 93 |
|
Object-Oriented Software Construction, 2nd Edition
|
Meyer 97 |
|
Eiffel, The Language
|
Meyer 92 | A major part of all standards and guidelines are good definitions for terms. See the chapter "Definitions" later in this document and the following for suggestions:
|
Directory of Object Technology
|
Firesmith+E 95 |
|
Analysis Patterns: Reusable Object Models.
|
Fowler 97 |
Overall Concepts
The following are three different perspectives on overall principles to good design and implementation for OO. None of these are a panacea: OO is a simple concept, but good design takes lots of learning and experience. These are meant to bring out some simple concepts that sometimes get lost.
Objects: The Core Concept of OO
The core concept of OO is that systems are built out of Objects with a clearly defined exterior and a completely opaque implementation. Objects are just like Cells, Computer Components, and People. Objects can only be interacted with by sending them messages, and a system performs its operations through the behavior associated with those messages. Sometimes just remembering this core metaphor can radically improve a design. Techniques like CRC sessions and anthropomorphizing can especially help.
Two Principles
Two important principles to consider for high-quality software development are to:
- Think from the clients point of view
- Think from the maintainers point of view
Understanding and considering these two customers needs during development makes most of the difference between poorly designed and very nicely designed systems. Object-oriented techniques can help support both of these customers needs, but the principles must always be on your mind. As the developer yourself, you will rarely forget your own needs.
Kent Beck
The following maxims are from Kent Becks Smalltalk Best Practice Patterns [Beck 96]. These describe properties that should be in good OO designs and good OO code:
- Once and only once In a program written with good style, everything is said once and only once.
- Lots of little pieces Good code invariably has small methods and small objects.
- Replaceable objects Good style leads to easily replaceable objects.
- Movable objects
objects can be easily moved to new contexts.
- Isolated rates of change
dont put two rates of change together.
Guidelines Summary
The following table summarizes the guidelines for easier reference
- General OO Guidelines
- Types
- Name Types Well
- Only Expose Responsibilities
- Operations
- Choose Intention Revealing Operation Names
- Have Uniquely Named Signatures
- Standardize Naming Patterns
- Categorize your Operations
- Classes
- Methods
- Compose Your Methods
- Make Execution Structure Obvious
- Java-Oriented Guidelines
- Types, Interfaces, and Classes: Design
- Interface with Interfaces
- Create Different Interfaces for Different Types of Clients
- Use Interfaces over Abstract Classes
- Rarely declare a class final
- Consider writing template files
- Operation and Method Design
- Weave Parameters Positions into the Operation Name
- Define return types as void
- Avoid overloading methods on argument type
- Write methods that only do "one thing"
- Packages and Modularity
- Have a "Pack" Class for all Packages
- Use Factories for Creating Objects
- Minimize * forms of import
- When sensible, consider writing a main for the principal class
- The class with main should be separate from those containing normal classes.
- Method Implementation
- Declare a local variable where you know its initial value
- Use a new local variable
- Assign null to any reference variable that is no longer being used
- Avoid assignments (``='') inside if and while conditions
- Ensure that there is ultimately a catch for all unchecked exceptions
- Embed casts in conditionals
- Documentation
- Make code self documenting before commenting it
- Provide comments that augment, not repeat, program code
- Use Interfaces for Public Documentation
- Specify a standard keyword order
- Augment javadoc keywords
- Class Implementation
- Never declare instance variables as public
- Minimize statics
- Prefer protected to private
- Minimize reliance on implicit initializers
- Prefer abstract methods to those with default no-op implementations
- Avoid giving a variable the same name as one in a superclass
- Use final and/or comment conventions for instance variables
- Avoid unnecessary instance variable access and update methods
- Minimize direct internal access to instance variables inside methods
- Ensure that non-private statics have sensible values
- Consider whether any class should implement Cloneable and/or Serializable.
- Whenever reasonable, define a default (no-argument) constructor
- Overriding
- If you override Object.equals, also override Object.hashCode
- Override readObject and WriteObject if a Serializable class relies on process state
- Explicitly define clone()
- Miscellaneous
- Generally prefer long to int, and double to float
- Use method equals instead of operator == when comparing objects
- Prefer declaring arrays as Type[] arrayName rather than Type arrayName[].
- Concurrent Programming
- Declare all public methods as synchronized
- Prefer synchronized methods to synchronized blocks.
- Always embed wait statements in while loops that re-wait
- Use notifyAll instead of notify or resume.
- Always document the fact that a method invokes wait
- Methodology, Notation, and CASE Guidelines
- Use UML
- Go beyond UML
- Use a smart drawing tool
- Link your design diagrams to javadoc
- Dont draw models for everything
- Use a CASE tool
- Avoid taking liberties with a CASE tool
- Recognize the limits of CASE tools
- Drive CASE tools from the appropriate direction
- Final Guidelines
- Try it out
- Prove Performance
- Take out the trash
- Do not require 0% conformance to rules of thumb!
General OO Guidelines
The following guidelines are statements of very general OO principles that provide overall guidance. These principles should help produce more specific guidelines and can be used as the restarting point if a particular guideline does not seem to work very well for a team.
Types
Types categorize and describe the exteriors of objects. In Java, a Type is represented as either an interface (a pure Type) or a class (a Type combined with an implementation). This document will use the term Type when considering the exterior properties of a class or an interface.
Name Types Well
Spend the time to name a Type correctly and concisely. The name should match the range of usage for the Type: if it is very general, choose a very simple name; and if it is only applicable in a specific context, qualify it to describe that limitation. Sometimes naming a Type can be very easy because it exists as part of the business concepts (an Employee) or part of the solution (a Window). But always make sure the name really matches the concept. And really care about the name: "Be a poet for a moment."
Rationale
Type names provide the main glossary and conceptual skeleton for any OO system.
Details and Examples
You should also try names out and fix them if they dont work well in actual use or if they do not fit well with names in the system. This is especially valuable in the earlier stages of a project before the team has mentally and programmatically committed to a name.
ChiMu 97e, Beck 96
Only Expose Responsibilities
Only expose the features that you want to be responsible for. A Type provides a contract to all its customers that it will need to maintain "for life", so never publicly expose something you do not want to be responsible for maintaining. Make sure all instance variables and implementation specific methods are hidden from clients and do not become part of your responsibilities.
Rationale
This is one of the foundations of OO and software engineering.
ChiMu 97e
Operations
Operations formally define the exterior of an Object: what messages it can understand and the contract it agrees to if you send it that message. Operations must be understandable in the context of the current Type (Interface or Class) and should be consistent across multiple Types. This requires continuous OO thinking, strong efforts toward standardization, and vigilant semantic verification. There are many more operations than their are Types, so it is a much more difficult task to name and organize them well.
Choose Intention Revealing Operation Names
Choose "intention revealing operation names" is the primary rule for naming operations. Always create a name that suggests what the operation "provides for the caller", not how a method could accomplish this service.
Rationale
The clearer you describe the behavior of the operation within its name, the easier it is for the clients (who repeatedly use those operations) to understand your class or interface. This is one of the many incarnations of thinking from the clients perspective.
ChiMu 97e, Beck 96
Have Uniquely Named Signatures
Ideally, make each operation have a unique name if it has different behavior or a different number or parameters. This will allow a client to know what this particular operation does and how many parameters it requires simply from the operation name. Especially avoid creating operations with the same name and the same number of arguments but different argument types.
Rationale
Otherwise, the human reader has to act like a compiler and figure out which operation (of several identically named ones) is the proper one to call given all the variable types involved. Because this is all done at compile-time, usually the result is not what anyone would want. See "Avoid overloading methods on argument type" for a good example in Java.
ChiMu 97e
Standardize Naming Patterns
Standardize the vocabulary used in operation names. As much as possible, words should be used consistently and uniquely when part of an operation.
Rationale
This makes understanding operations easier and supports precisely describing new functionality. These benefits become especially important as a system grows.
Details and Examples
The following is an example subset of the standard meanings for operation name parts and operation categories (see the Source code format section below).
The following are common operation prefixes
|
Prefixes
|
Category
|
Description |
|
is, can, has, will
|
Testing
|
Return a Boolean and test the state of the object |
|
new
|
Creating
|
Create and return a new object from a factory that creates only a single type of object |
|
init, setup
|
Initializing
|
These methods are called before you can use an object. Only a single init function should be called which can then be followed by whatever setup methods you need to change the default configuration of the object. | A few Type specific prefixes are:
|
Prefixes
|
Category
|
Description |
|
find
|
Searching
|
Retrieve a single object or null if unsuccessful |
|
select
|
Searching
|
Retrieve multiple objects or an empty collection |
|
add
|
|
Add an object to a collection |
Non-prefix operation name patterns
|
Prefixes
|
Description |
|
any
|
Return any object that satisfies the request (findAny) |
|
all
|
Return all objects that satisfy the request (selectAll) | Within each Type or domain area (Collections, Functors, SQL, Mapping, Domain models) there will be both reused vocabulary and new vocabulary. Try to manage these forces well.
ChiMu 97e
Categorize your Operations
Group operations into Categories and reflect those categories in your Interfaces. If a language or tool does not support operation categories, use whatever documentation is available (comment dividers, notes, etc.).
Rationale
By grouping operations into meaningful categories it will be easier to understand the operations and to read the implementations. This organizational assistance is something akin to "subtyping" of operations. Some example categories are:
|
Constructing
|
A section and category. The constructors for the class. |
|
Initializing
|
An additional method that should be applied directly after constructing the object. |
|
Setup
|
Methods that can optionally be applied to an object but must be done immediately after construction and initialization and before using the object normally. |
|
Validating
|
Check whether the current object is in an acceptable state (could also be under asking if this is possible after construction is finishing). |
|
Asking
|
Asking the state of the current object without causing any (visible) side effects. A pure function. ISE Eiffel Query. |
|
Testing
|
An asking method that returns a Boolean value | These categories should align with and reinforce operation naming patterns.
ChiMu 97e
Classes
Classes provide an implementation for a Type, so they should focus on the needs of implementers and maintainers
Methods
Methods implement operations within a given class. As such, the rules for operations determine the name and other externally visible properties of a method. The rest of the
Compose Your Methods
After you have defined the public operations that a class has to perform, you will need to implement those operations with methods. Focus on communication and maintainability when implementing methods. "Divide your program into methods that perform one identifiable task. Keep all of the operations in a method at the same level of abstraction. This will naturally result in programs with many small methods, each a few lines long."
ChiMu 97e, [Beck 96]
Make Execution Structure Obvious
Try to make the execution structure of your method visible when looked at quickly. Standardize on a few good structures so a reader will quickly be able to survey the functionality and identify where to look for interesting features.
ChiMu 97e
Java-Oriented Guidelines
The following guidelines are more Java specific but also try to expose the general principles as much as possible.
Types, Interfaces, and Classes: Design
Interface with Interfaces
Use interfaces as the glue throughout your code instead of classes: define interfaces to describe the exterior of objects (i.e. their Type) and type all variables, parameters, and return values to interfaces.
Rationale
The most important reason to do this is that interfaces focus on the clients needs: interfaces define what functionality a client will receive from an Object without coupling the client to the Objects implementation. This is one of the core concepts to OO.
Details and Examples
There are many benefits to using interfaces as the glue throughout your systems, the following are just two of the most important benefits. First, clients will not be coupled to the specific implementation, so you can have much more flexibility in evolving the implementation plus you can provide alternative implementations to support proxies, tracing, and performance variations. Second, you can use multiple inheritance among interfaces and between interfaces and classes, which can help with OO modeling and can support different access views of the same class (see below).
Interfaces should be given no suffixes or prefixes: they have the "normal" name space. Classes are given a suffix of "Class" if they are meant to be instantiated or are given a suffix of "AbsClass" if they are an abstract class that provides inheritable implementation but is not complete and instantiable by itself. Java classes then become implementations of Java interfaces and should provide no public behavior beyond the interface itself (other than how to create and initialize an object of that class). Avoid exposing classes except when you want to provide the ability for a client to subclass.
ChiMu 97e
Create Different Interfaces for Different Types of Clients
Provide different interfaces to support different types of clients and to prevent exposing responsibilities to clients who should not see it.
Rationale
Provides a more understandable system for a particular clients perspective and makes maintenance impacts more visible.
ChiMu 97e
Use Interfaces over Abstract Classes
If you can conceive of someone else implementing a class's functionality differently, define an interface, not an abstract class. Generally, use abstract classes only when they are ``partially abstract''; i.e., they implement some functionality that must be shared across all subclasses.
Rationale
Interfaces are more flexible than abstract classes. They support multiple inheritance and can be used as `mixins' in otherwise unrelated classes.
Lea-1
Rarely declare a class final
Declare a class as final only if it is a subclass or implementation of a class or interface declaring all of its non-implementation-specific methods. (And similarly for final methods).
Rationale
Making a class final means that no one ever has a chance to reimplement functionality. Defining it instead to be a subclass of a base that is not final means that someone at least gets a chance to subclass the base with an alternate implementation. Which will essentially always happen in the long run.
Lea-1
Consider writing template files
Consider writing template files for the most common kinds of class files you create: Applets, library classes, application classes.
Rationale
Simplifies conformance to coding standards.
Lea-1
Operation and Method Design
The terms "operation" and "method" are used interchangeably (when referring to external specification) depending on who produced the guideline.
Weave Parameters Positions into the Operation Name
Put underscores "_" into operation names as placeholders for where a particular parameter is woven into the message send. Leave off any trailing underscores.
Examples and Details
For example:
at_put(key,value)
would read as at_(first parameter)put(second parameter) or "at (key) put (value)". A second example would be:
setIndex_to_asType(index,value,type)
or "set index (index) to (value) as type (type)". This does a good job of specifying the meaning of the message, the number of parameters, and the specific positions of all the parameters. If you have a large number of parameters that you do not want to specifically mention/weave into the operation name, they can be added at the end. For example:
at_putStuff(key,value1,value2,value3)
Rationale
This improves operation/method names and helps clients know what order to put parameters into the parenthesis.
ChiMu 97e
Define return types as void
Define return types as void unless they return results that are not (easily) accessible otherwise (i.e., hardly ever write "return this").
Rationale
While convenient, the resulting method cascades (a.meth1().meth2().meth3()) can be the sources of synchronization problems and other failed expectations about the states of target objects.
Lea-1
Avoid overloading methods on argument type
Avoid overloading methods on argument type. (Overriding on arity is OK, as in having a one-argument version versus a two-argument version). If you need to specialize behavior according to the class of an argument, consider instead choosing a general type for the nominal argument type (often Object) and using conditionals checking instanceof. Alternatives include techniques such as double-dispatching, or often best, reformulating methods (and/or those of their arguments) to remove dependence on exact argument type.
Rationale
Java method resolution is static; based on the listed types, not the actual types of argument. This is compounded in the case of non-Object types with coercion charts. In both cases, most programmers have not committed the matching rules to memory. The results can be counterintuitive; thus the source of subtle errors. For example, try to predict the output of this. Then compile and run.
class Classifier {
String identify(Object x) { return "object"; }
String identify(Integer x) { return "integer"; }
}
class Relay {
String relay(Object obj) {
return (new Classifier()).identify(obj);
}
}
public class App {
public static void main(String[] args) {
Relay relayer = new Relay();
Integer i = new Integer(17);
System.out.println(relayer.relay(i));
}
}
Lea-1
Write methods that only do "one thing"
Write methods that only do "one thing". In particular, separate out methods that change object state from those that just rely upon it. For a classic example in a Stack, prefer having two methods Object top() and void removeTop() versus the single method Object pop() that does both.
Rationale
This simplifies (sometimes, makes even possible) concurrency control and subclass-based extensions.
Lea-1
Packages and Modularity
Have a "Pack" Class for all Packages
Create a Class named "<packageName>Pack" for each Java package. Put documentation about the Package and any functionality that applies to the package as a whole into the Pack.
Rationale
Packages are not represented in Java in any tangible manner: they are more a hierarchical naming convention. By having a real Class for each package you have a standard place to put package documentation and functionality. This makes understanding a package easier and can also support better encapsulation of the packages functionality.
ChiMu 97e
Use Factories for Creating Objects
Use Factories and Factory methods for "public" object construction: have an object be responsible for construction instead of having clients directly call "new AClass()".
Rationale
The reason to use factory creation methods instead of straight constructors is because they:
- Allow more flexibility in "creating" a new object: the implementation can just reuse an existing object if the semantics make sense.
- Can have better names: "newTimeNow()" and "newTimeFromSeconds(...)" instead of "new Time()" and "new Time(...)"
- Provide better separation between interface and implementation: we can document the factory method in an interface
- Naturally flow into more sophisticated factory designs (See [Gamma+HJV 95])
- The implementation can take advantage of inheritance since it is a "normal" object method.
The Factory can either be an existing appropriate object (e.g. a database object is the factory for database Tables), or a specific Factory type. For classes that have no other appropriate factory object we use the Pack object as the factory.
ChiMu 97e
Minimize * forms of import
Minimize * forms of import. Be precise about what you are importing. Check that all declared imports are actually used.
Rationale
Otherwise readers of your code will have a hard time understanding its context and dependencies. Some people even prefer not using import at all (thus requiring that every class reference be fully dot-qualified), which avoids all possible ambiguity at the expense of requiring more source code changes if package names change.
Lea-1
When sensible, consider writing a main for the principal class
When sensible, consider writing a main for the principal class in each program file. The main should provide a simple unit test or demo.
Rationale
Forms a basis for testing. Also provides usage examples.
Lea-1
The class with main should be separate from those containing normal classes.
For self-standing application programs, the class with main should be separate from those containing normal classes.
Rationale
Hard-wiring an application program in one of its component class files hinders reuse.
Lea-1
Method Implementation
Declare a local variable where you know its initial value
Declare a local variable only at that point in the code where you know what its initial value should be.
Rationale
Minimizes bad assumptions about values of variables.
Lea-1
Use a new local variable
Declare and initialize a new local variable rather than reusing (reassigning) an existing one whose value happens to no longer be used at that program point.
Rationale
Minimizes bad assumptions about values of variables.
Lea-1
Assign null to any reference variable that is no longer being used
Assign null to any reference variable that is no longer being used. (This includes, especially, elements of arrays.)
Rationale
Enables garbage collection.
Lea-1
Avoid assignments (``='') inside if and while conditions
Rationale
They are almost always typos. The java compiler catches cases where ``='' should have been ``=='' except when the variable is a boolean.
Lea-1
Ensure that there is ultimately a catch for all unchecked exceptions
Rationale
Java allows you to not bother declaring or catching some common easily-handlable exceptions, for example java.util.NoSuchElementException. Declare and catch them anyway.
Lea-1
Embed casts in conditionals
Details and Examples
For example:
C cx = null;
if (x instanceof C) {
cx = (C) x;
} else {
evasiveAction();
}
Rationale
This forces you to consider what to do if the object is not an instance of the intended class rather than just generating a ClassCastException.
Lea-1
Documentation
Make code self documenting before commenting it
Try to make code as self document as possible before resorting to commenting it. This can both to improve the design and better describe an existing design than using comments.
Rationale
Specifications and code within a programming language are always more precise and useful than comments. If the code can describe itself better, this provides a constant reinforcement to future development (the clients and maintainers of this code). Comments are just auxiliary information and (although useful) should be a second choice.
ChiMu 97e, McConnell 92
Provide comments that augment, not repeat, program code
Make comments augment, not repeat, information available in Java syntax itself.
Rationale
Statements made in the programming language are precise, communicative, and guaranteed to be "true" (the program does what it says it does). If comments repeat information already specified they provide nothing and they are likely to become out of date and incorrect.
Details and Examples
Place the JavaDoc comments for methods immediately above and inset relative to the method declaration. This is so it is easy to read the method declaration before reading the comment. A method should have a good, intention revealing operator name, good parameter names, and a suitable return value type. This implies that the declaration itself is the best first source for documentation of the public use of the method.
/**
* Find a person with the particular name.
*@return null if can not find the person
*/
public Person findName(String name);
Consider method comments to be inside and subservient to the declaration (although JavaDoc requires it to be before the declaration).
ChiMu 97e, McConnell 92
Use Interfaces for Public Documentation
If you "Interface with Interfaces", put public documentation in the Interface and implementation documentation in the class. Do not repeat documentation between the two files.
Rationale
Clients should only be looking at the public documentation and then the Class file can focus on implementation needs. Repeating information just makes it likely to get out of synch.
ChiMu 97e
Specify a standard keyword order
Rationale
This is mostly a programming convention, but it can help team members to quickly understand other members programs. It can also help the mental classification of methods and reinforce other guidelines (e.g. avoid static).
Details and Examples
The following is a suggested order. Static methods are a completely different kind of method (they are actually statically bound functions and procedures), so this is the first qualifier mentioned. After this comes the access control (including the comment specifying more specific access than Java currently provides). This is followed by all the not-elsewhere-mentioned qualifiers. Finally we have the type specification.
- [static]
- public | /*subsystem*/ public | /*package*/ public | /*package*/ | protected | /*progeny*/ protected | private
- [abstract], [synchronized], [final], [native], [transient], [volatile]
- void | <TypeName>
ChiMu 97e
Augment javadoc keywords
Consider augmenting standard javadoc keywords (author, version, see, param) with additional descriptive keywords (require, ensure).
Rationale
Provides a more descriptive definition of the contract your class is providing to its clients. The compiler and running program can not use this information (without additional tools), but it supports the human communication among the developer, the clients, and future developers. See [Meyer 97] for the principles behind design by contract.
Details and Examples
If you are using standard javadoc, do not use the same @ format for the new keywords (they will disappear), but instead capitalize them with a colon. The following is an example of augmenting keywords.
/**
* Remove and return the top element.
*<P>REQUIRE: notEmpty()
*<P>ENSURE: NEW(count()) = OLD(count()) - 1
*/
public Object pop();
ChiMu 97e
Class Implementation
Never declare instance variables as public
Rationale
The standard OO reasons. Making variables public gives up control over internal class structure. Also, methods cannot assume that variables have valid values.
Lea-1
Minimize statics
Rationale
Static variables act like globals in non-OO languages. They make methods more context-dependent, hide possible side-effects, sometimes present synchronized access problems. and are the source of fragile, non-extensible constructions. Also, neither static variables nor methods are overridable in any useful sense in subclasses.
Lea-1
Prefer protected to private
Rationale
Unless you have good reason for sealing-in a particular strategy for using a variable or method, you might as well plan for change via subclassing. On the other hand, this almost always entails more work. Basing other code in a base class around protected variables and methods is harder, since you you have to either loosen or check assumptions about their properties. (Note that in Java, protected methods are also accessible from unrelated classes in the same package. There is hardly every any reason to exploit this though.)
Lea-1
Minimize reliance on implicit initializers
Minimize reliance on implicit initializers for instance variables (such as the fact that reference variables are initialized to null).
Rationale
Minimizes initialization errors.
Lea-1
Prefer abstract methods to those with default no-op implementations
Prefer abstract methods in base classes to those with default no-op implementations. (Also, if there is a common default implementation, consider instead writing it as a protected method so that subclass authors can just write a one-line implementation to call the default.)
Rationale
The Java compiler will force subclass authors to implement abstract methods, avoiding problems occurring when they forget to do so but should have.
Lea-1
Avoid giving a variable the same name as one in a superclass
Rationale
This is usually an error. If not, explain the intent.
Lea-1
Use final and/or comment conventions for instance variables
Use final and/or comment conventions to indicate whether instance variables that never have their values changed after construction are intended to be constant (immutable) for the lifetime of the object (versus those that just so happen not to get assigned in a class, but could in a subclass).
Rationale
Access to immutable instance variables generally does not require any synchronization control, but others generally do.
Lea-1
Avoid unnecessary instance variable access and update methods
Avoid unnecessary instance variable access and update methods. Write get/set-style methods only when they are intrinsic aspects of functionality.
Rationale
Most instance variables in most classes must maintain values that are dependent on those of other instance variables. Allowing them to be read or written in isolation makes it harder to ensure that consistent sets of values are always used.
Lea-1
Minimize direct internal access to instance variables inside methods
Minimize direct internal access to instance variables inside methods. Use protected access and update methods instead (or sometimes public ones if they exist anyway).
Rationale
While inconvenient and sometimes overkill, this allows you to vary synchronization and notification policies associated with variable access and change in the class and/or its subclasses, which is otherwise a serious impediment to extensibility in concurrent OO programming. (Note: The naming conventions for instance variables serve as an annoying reminder of such issues.)
Lea-1
Ensure that non-private statics have sensible values
Ensure that non-private statics have sensible values even if no instances are ever created. (Similarly ensure that static methods can be executed sensibly.) Use static intitializers (static { ... } ) if necessary.
Rationale
You cannot assume that non-private statics will be accessed only after instances are constructed.
Lea-1
Consider whether any class should implement Cloneable and/or Serializable.
Rationale
These are ``magic'' interfaces in Java, that automatically add possibly-needed functionality only if so requested.
Lea-1
Whenever reasonable, define a default (no-argument) constructor
Whenever reasonable, define a default (no-argument) constructor so objects can be created via Class.newInstance().
Rationale
This allows classes of types unknown at compile time to be dynamically loaded and instantiated (as is done for example when loading unknown Applets from html pages).
Lea-1
Overriding
If you override Object.equals, also override Object.hashCode
If you override Object.equals, also override Object.hashCode, and vice-versa.
Rationale
Essentially all containers and other utilities that group or compare objects in ways depending on equality rely on hashcodes to indicate possible equality. For further guidance see Taligent's Java Cookbook
Lea-1
Override readObject and WriteObject if a Serializable class relies on process state
Override readObject and WriteObject if a Serializable class relies on any state that could differ across processes, including, in particular, hashCodes and transient fields.
Rationale
Otherwise, objects of the class will not transport properly.
Lea-1
Explicitly define clone()
If you think that clone() may be called in a class you write, then explicitly define it (and declare the class to implement Cloneable).
Rationale
The default shallow-copy version of clone might not do what you want.
Lea-1
Miscellaneous
Generally prefer long to int, and double to float
Generally prefer long to int, and double to float. But use int for compatibility with standard Java constructs and classes (for the major example, array indexing, and all of the things this implies, for example about maximum sizes of arrays, etc).
Rationale
Arithmetic overflow and underflow can be 4 billion times less likely with longs than ints; similarly, fewer precision problems occur with doubles than floats. On the other hand, because of limitations in Java atomicity guarantees, use of longs and doubles must be synchronized in cases where use of ints and floats sometimes would not be.
Lea-1
Use method equals instead of operator == when comparing objects
Use method equals instead of operator == when comparing objects. In particular, do not use == to compare Strings.
Rationale
If someone defined an equals method to compare objects, then they want you to use it. Otherwise, the default implementation of Object.equals is just to use ==.
Lea-1
Prefer declaring arrays as Type[] arrayName rather than Type arrayName[].
Rationale
The second form is just for incorrigible C programmers.
Lea-1
Concurrent Programming
Doug Lea specializes in Concurrent Programming. See his book [Lea 96] for more information on Concurrent Programming in Java.
Declare all public methods as synchronized
Declare all public methods as synchronized; or if not, describe the assumed invocation context and/or rationale for lack of synchronization.
Rationale
In the absence of planning out a set of concurrency control policies, declaring methods as synchronized at least guarantees safety (although not necessarily liveness) in concurrent contexts (every Java program is concurrent to at least some minimal extent). With full synchronization of all methods, the methods may lock up, but the object can never enter in randomly inconsistent states (and thus engage in stupidly or even dangerously wrong behaviors) due to concurrency conflicts. If you are worried about efficiency problems due to synchronization, learn enough about concurrent OO programming to plan out more efficient and/or less deadlock-prone policies.
Lea-1
Prefer synchronized methods to synchronized blocks.
Rationale
Better encapsulation; less prone to subclassing snags; can be more efficient.
Lea-1
Always embed wait statements in while loops that re-wait
Always embed wait statements in while loops that re-wait if the condition being waited for does not hold.
Rationale
When a wait wakes up, it does not know if the condition it is waiting for is true or not.
Lea-1
Use notifyAll instead of notify or resume.
Rationale
Classes that use only notify can normally only support at most one kind of wait condition across all methods in the class and all possible subclasses. And unguarded suspends/resumes are even more fragile.
Lea-1
Always document the fact that a method invokes wait
Rationale
Clients may need to take special actions to avoid nested monitor calls.
Lea-1
Methodology, Notation, and CASE Guidelines
The following are some guidelines for working with methodologies, notations and CASE tools.
Use UML
Rationale
With deference to all the great work by other methodologists, engineers, and "thinkers", UML has won. The probability that someone understands what you are doing is significantly enhanced by having a (good-enough) core shared notation. Start with UML and then enhance it for needs it does not address.
ChiMu 97e
Go beyond UML
If UML does not express a concept well, extend it or modify it and then document how your extension relates to UML and other methods.
Rationale
Communication is the most important part of any notation. Make sure a standard does not hinder communicating concepts that are important to building good OO software. Although UML incorporates several peoples work together, other important concepts were left out (e.g. UI, Coordinator, Entity, Interface, Extensions, etc.) and should not be lost if they are important to your development process.
Details and Examples
UML is extendable via the stereotype functionality: you can further refine UML concepts by annotating a UML object with a guillemot surrounded phrase («coordinator»). Further, you can then define a new icon for the new refined concept. This, for example, allows you to use Jacobson concepts by adding «interface», «control», «entity» stereotypes and then using Jacobson icons for these new concepts.
ChiMu 97e
Dont draw models for everything
Dont draw models for everything; instead, concentrate on the key areas. It is better to have a few diagrams that you use and keep up-to-date than to have many forgotten, obsolete models.
Fowler 97b
Link your design diagrams to javadoc
If you create a diagram to explain an important concept for your design, connect the diagram into the Javadoc documentation.
Rationale
Javadoc is THE standard reference source for Java programming, so any documentation available within it will be much more likely to find.
Lea-2
Use a smart drawing tool
Use a smart drawing tool to make UML and other software design diagrams easier to create and modify.
Rationale
Smart drawing tools are the first logical step for creating software models above the simplicity and flexibility of the simple pen. They provide excellent support for creating, modifying, and printing readable diagrams quickly through their stencils, smart line connectors, and excellent drawing capabilities. Most smart diagram tools now have stencils for UML, Objectory, ER Diagrams, UI widgets, and many other types of software notations. Some tools even have a simple understanding of UML notation rules.
Details
There are many smart drawing tools available, and listings of them can be found on the web at:
- http://www.yahoo.com/Computers_and_Internet/Software/Reviews/Titles/Business/Flow_Chart/
- http://www.yahoo.com/Business_and_Economy/Companies/Computers/Software/Graphics/
- http://www.yahoo.com/Business_and_Economy/Companies/Computers/Software/Graphics/Flow_Charting/
ChiMu 97e
Use a CASE tool
If your diagramming needs go beyond a smart drawing tool, carefully consider the different CASE tools on the market and decide which (if any) meet your needs the best compared to the drawing tools.
Rationale
For some projects, smart drawing tools will not be enough and CASE tools may be a more appropriate solution. Smart drawing tools are very powerful but they do not understand the meaning of what they portray. This restricts their abilities to:
- Guide/enforce the user to create correct diagrams
- Update changes between different diagrams
- Support creating new diagrams based on the existing knowledge
- Create automatic reports
- Forward generate to code
All of these items might be useful for the project team to maintain its models. Be aware that all tools that are smarter are also less flexible and are never "smart enough", so you need to make a very careful consideration of the tradeoffs with each tool and which of the above items are actually useful (in practice) to your project.
ChiMu 97e
Avoid taking liberties with a CASE tool
Rationale
All CASE tool diagrams impact the conceptual model in the "repository", and if even one diagram is done strangely/loosely that model will be damaged. This could prevent others from understanding the model, or lead to a severe misunderstanding of the model. Make sure the repository is correct from all views.
Details
It is better to capture information that the CASE tool can not in a different (external to the tool) form. Put as much in the tool as possible and then organize and refer to this external information.
ChiMu 97e
Recognize the limits of CASE tools
Make sure you recognize what a CASE tool prevents you from expressing and determine when that is and is not acceptable. Use hand drawings, drawing tools, or just text when you your CASE tool would hinder important communication.
Rationale
CASE tools provide many benefits but they can sometimes interfere with the goal of good communication. No CASE tool can completely support all of UML, let alone all the useful ways to communicate precisely. The communication is more important than the tool.
ChiMu 97e
Drive CASE tools from the appropriate direction
Drive CASE tools from the appropriate direction. Before a significant amount of code is written, CASE tools can be driven forward (requirements to analysis to design to implementation). After code exists, drive design information from the code.
Rationale
The forward direction is the ideal, but a CASE repository is of no use later in a project if it does not reflect reality. Code is the reality; so CASE information must be generated from the code. The changes can then be reviewed as part of code review and acceptance. Previous designs can be kept as snapshots and new future designs can again drive code changes.
ChiMu 97e
Final Guidelines
Try it out
Live with a guideline for a while before deciding to scrap or change it. Let the goals of a guideline grow into your habits so you fully understand its value to you.
Rationale
Only by trying a guideline can you really understand how and whether it benefits you.
ChiMu 97e
Prove Performance
Do not sacrifice a guideline for performance reasons until you see the profiling numbers. Only optimize when it will quantifiably be worth the maintenance penalty.
Rationale
Optimization is always harmful and rarely beneficial unless you have numbers to back it up. A better design (which these guideline try to encourage) will allow more precise and effective optimizations later when the numbers come in.
ChiMu 97e
Take out the trash
If a standard does not work for your team, create a new one or let the issue be context and programmer dependent until a new standard emerges.
Rationale
Having standards that interfere with good system design is worse than no standard at all.
ChiMu 97e
Do not require 100% conformance to rules of thumb!
Rationale
Java allows you program in ways that do not conform to these rules for good reason. Sometimes they provide the only reasonable ways to implement things. And some of these rules make programs less efficient than they might otherwise be, so are meant to be conscientiously broken when performance is an issue.
Lea-1
|