Design techniques and coding standards for J2EE projects
OO Design recommendations for J2EE appliations
OO design is more important than any particular implementation technology (such as J2EE, or even Java). Good programming practices and sound OO design underpin good J2EE applications. bad Java code is bad J2EE code.
Take every opportunity to learn from the good (and bad) code of others, inside and outside your organization. Useful sources in the public domain include succesful open source projects and the code in the core Java libraries. License permitting, it may be possible to decompile interesting parts of commercial products. A professional programmer or architect cares more about learning and discovering the best solution than the buzz of finding their own solution to a particular problem.
Achieving loose coupling with interfaces
Program to interfaces, not classes. This decouples interfaces from their implementations. Using loose coupling between objects promotes flexibility. to gain maximum flexibility, declare instance variables and method parameters to be of the least specific type required.
Prefer object composition to concrete inheritance
Interface inheritance (that is, the implementation of interfaces, rather than inheritance of functionality from concrete classes) is much more flexible than concrete inheritance.
The template method design pattern
The centralization of the workflow into an abstract superclass, is known as inversion of control or the Hollywood principle. In “the old days”, the centralized code was called from the user code (i.e. library calls), nowadays the centralized code (e.g. frameworks) call user code instead.
Use the template method design pattern to capture an algorithm in an abstract superclass, but defer the implementation of individual steps to subclasses. This has the potential to head off bugs, by getting tricky operations right once and simplifying user code. When implementing the Template Method pattern, the abstract superclass must gactor out those methods that may change between subclasses and ensure that the method signatures enable sufficient flexibility in implementation.
Always make the abstract parent class implement an interface. The Template Method design pattern is especially valuable in framework design.
The strategy design pattern
The Strategy design pattern is an alternative to the Template Method design pattern, but here the variant behaviour is isolated using an interface.
Using callbacks to achieve extensibility
Another use of the “inversion of control” principle is the use of callbacks, to parameterize a single operation. In fact, it is a special case of the Strategy design pattern.
The observer design pattern
The Observer design pattern in useful when decoupling components to enable extensibility without modification. It also promotes separation of concerns. Example: Java beans can publish property change events to Observers (also known as listeners) who have subscribed to this kind of events.
Consider consolidating method parameters
“Sometimes it’s a good idea to encapsulate multiple parameters to a method into a single object”. This is also know as the Passing Parameter design pattern.
Exception handling – Checked or unchecked exceptions
Using checked exceptions exclusively leads to several problems
- Too much code
- Unreadable code
- Endless wrapping of exceptions
- Fragile method signatures
- Checked exceptions don’t always work well with interfaces
Checked exceptions are much superior to error return codes (as used in many older languages). Sooner or later (probably sooner) someone will fail to check an error return value; it’s good to use the compiler to enforce correct error handling. Such checked exceptions are as integral to an object’s API as parameters and return values.
Use a checked exception if calling code can do something sensible with the exception. Use an unchecked exception if the exception is fatal, or if callers won’t gain by catching it. Temember that a J2EE container (such as a web container) can be relied on to catch unchecked exceptions and log them.
Decide at a package level how each package will use checked or unchecked exceptions. Document the decision to use unchecked exceptions, as many developers will not expect it.
The only danger in using unchecked exceptions is that the exceptions may be inadequately documented. When using unchecked exceptions, be sure to document all exceptions that may be thrown from each method, allowing calling code to choose to catch even exceptions that you expect to be fatal. Ideally, the compiler should enforce Javadoc-ing of all exceptions, checked and unchecked.
If allocating resources such as JDBC connections that must be released under all circumstances, remember to use a finally block to ensure cleanup, whether or not you need to catch checked exceptions. Remember that a finally block can be used even without a catch block.
Ultimately, whether to use checked or unchecked exception is a matter of opinion. Thus, it’s not only vital to document the approach taken, but to respect the practice of others. While I [Rod Jonhson, ZWH] prefer to use unchecked exceptions in general, when maintaining or enhancing code wirtten by others who favor exclusive use of checked exceptions, I [Rod Johnson, ZWH] follow their style.
Good Exception handling practices
In Java 1.4 there is no longer any need for writing chainable exceptions.
Exceptions in J2EE
Use the Business delegate J2EE pattern to conceal communication with the remote system, i.e. remote exceptions. This client-side facade should throw exceptions (checked or unchecked) that are dictated by business need.
Making exceptions informative
Separate error messages for display to users from exception code, by including an error code with exceptions. When it’s time to display the exceptions, the code can be resolved: for example, from a properties file.
Include as much context information as possible with exceptions. If an exception probably results from a programming error, try to include information on how to rectify the problem.
Many design patterns can best be expressed by use of reflection. For example, there’s no need to hard-code class names into a Factory if classes are JavaBeans, and can be instantiated and configured via reflection. Only the names of classes – for example, different implementations of an interface- need be supplied in configuration data.
Used appropriately, reflection can enable us to write less code. Code using reflection can also minimize maintenance by keeping itself up to date. As an exmaple, consider the implmentation of object serialization in the core Java libraries. Since it uses reflection, there’s no need to update serialization and deserialization code when fields are added to or removed from an object. At a small cost to efficiency, this greatly reduces the workload on developers using serialization, and eliminates many programming errors.
Used appropriately, reflection won’t degrade performance. Using reflection appropriately should actually improve code maintainability. Direct use of reflection should be limited to infrastructure classes, not scattered through application objects.
Reflection is a core feature of Java, and any serious J2EE developer should have a strong grasp of the Reflection API. Although reflective idioms (such as, the ternary operator) may seem puzzling at first, they’re equally a part of the language’s design, and it’s vital to be able to read and understand them easily.
Using reflection is one of the best ways to parameterize Java code. Using reflection to choose instantiate and configure objects dynamically allows us to exploit the full power of loose cou;oing using interfaces. Such use of reflection is consistent with the J2EE philosophy of declarative configuration.
A warning: I feel dangerously good after I’ve made a clever use of reflection. Excessive cleverness reduces maintainability. Although I’m a firm believer that reflection, used appropriately, is beneficial, don’t use reflection if a simpler approach might work equally well.
Using JavaBeans to achieve flexibility
Designing objects to JavaBeans has many benefits. Most importantly, it enables objects to be instantiated and configured easily using configuration data outside Java code.
To be continued as I work my way through the book…