Java Tips & Techniques
General Practices
Code for the programmer who is going to be maintaining your code and assume it is someone with less training and experience than yourself. Most of your application's cost will be in fixing and maintaining it; make this as easy as possible to accomplish!
- Be precise.
- Follow coding standards whenever you create new code. Code that is consistently formatted is much easier to read.
- Follow proper naming convensions so others can better understand your code. This includes things like proper capitalization, meaningful variable names, etc.
- Avoid abbreviations as they make things unclear to people who are not expert in the problem domain.
- Create testable code. For example:
- Create data access objects via factories so you can swap implementations to ones that return testable data.
- Put "Business Logic" in POJOs (Plain Old Java Objects) so you can test it without deploying it into a container (J2EE/Servlet).
- Pick a frontend test tool ahead of time and create some standards for webpage authors to follow so your presentation is easily tested.
- Keep your code simple and easily understood.
- Methods should be short, break up long methods into shorter methods that accomplish discreet tasks.
- Classes should group methods that are related, but should not have too many methods in them or they'll become confusing and hard to navigate.
- Use packages to group related classes.
- If either your code or your documentation is wrong, both should be considered wrong. You can't trust either if one is out of date.
- You should document all classes (at the class level) and public methods other than simple getters/setters. Be sure to include assumptions and dependancies in your documentation.
- It's more important to document why than what. Reading code will tell you what the code does, but knowing why a function exists or why it does things in a certain order or in a certain way is much harder to understand.
Specific Practices
- Program to avoid null pointer exceptions. These are very time consuming (and thus costly) to find. When comparing, always place the constant before the variable:
if (CONSTANT_VALUE.equals(variable)) // do something here - Validate the parameters passed to boundary methods to avoid unchecked exceptions. This is especially important to people using your boundary classes as they will tend to forget about your assumptions and dependancies when they first use your components.
- Do not leave a
catchblock empty. At the very least, add a logging statement.try { // Code that causes an exception } catch (IOException) { logger.severe("Cannot perform operation"); } - Use
iterator()to visit each object in a collection rather than accessing via an index (get(int)). - Use a
for()construct to iterate through a collection, rather than awhile(). Using afor()limits the scope of the iterator variable.for (Iterator iter = employeeList.iterator(); iter.hasNext();) { Employee employee = (Employee)iter.next(); // Do some work here } - Create tests for classes which perform business logic at an absolute minimum. Preferably you should be testing all your public interfaces and even go beyond simple unit/functional testing and create tests that can validate your deployed code in an automated manner. Make sure your tests validate boundary conditions, not simply happy paths.
- Prefer composition to inheritence. Inheritence makes many methods (like
cloneandequals) hard to implement. It also limits the usefulness of your class (for instance, if a framework demands inheritence, you business logic cannot also demand it). - Prefer helper/service classes to inherited functionality. Adding common functionality to a whole group of classes almost always means you are adding a cross cutting concern to those classes. It is better to have that functionality called outside of the scope of the object than within the object itself (from a service, for instance, that provides that functionality). Examples of this are getting a database connection, participating in a transaction, returning a view of a data object as XML, etc).
- Try to make things
finalor immutable as often as possible. - Always program to an interface whenever possible, not to a concrete class.
- Do not have an
ArrayListas a method parameter or a return type when aCollectionis what you really want. The implementation details for that attribute should be determined when the attribute is created.
public class Widget { // Let's use a ArrayList, but we can always change // it later. private Collection parts = new ArrayList(); public Collection getParts() { return parts; } public void setParts(Collection parts) { this.parts = parts; } } - Do not have an
- Create interfaces for your boundary classes. Instantiate boundary classes in a way that decouples them from your other code. This allows you to change the implementation easily (possibly at runtime) and to change the implementation for testing. There are many ways of implementing this idea:
- Use a delegate or proxy class. This class implements the interface, but delegates all method calls to a separate implementing class. The fully qualified classname of the implementing class could be passed in the proxy's constructor or set through a setter.
public class AccountManagerProxy implements AccountManagerInterface { AccountManagerInterface implementation; public AccountManagerProxy(Class theClass) { implementation = theClass.newInstance(); } public void setImplementation(String className) { implementation = Class.forName(className).newInstance(); } public void adjustAccount(String accountNumber, float amount) { implementation.adjustAccount(accountNumber, amount); } } - Use a ServiceLocator class to instantiate an implementation of an interface. This creates a central configuration of which implementation to use for each interface and moves the code for instantiating implementations into a single class (the ServiceLocator itself). The disadvantage, of course, is that components need to know about the ServiceLocator to use other components.
- Use an IoC (Inversion of Control) container such as PicoContainer or Spring to abstract away the dependancies between components (such that external components specified in setters and/or constructors are auto-magically instantiated and associated).
- Use a delegate or proxy class. This class implements the interface, but delegates all method calls to a separate implementing class. The fully qualified classname of the implementing class could be passed in the proxy's constructor or set through a setter.
Finally, I really like this quote (someone on The Server Side attributes it to C.H.R. Hoare in 1980).
At first I hoped that such a technically unsound project would collapse but I soon realized it was doomed to success. Almost anything can be implemented, sold, and even used given enough determination. There is nothing a mere scientist can say that will stand against the flood of a hundred million dollars. But there is one quality that cannot be purchased in this way -- and that is reliability. The price of reliability is the pursuit of the utmost simplicity. It is a price which the very rich find most hard to pay.