It has become cliché to say that the only constant in life is change, and most people accept it as a given. However, we often don’t take it to heart when we code. We prototype something together using “Magic Number” hard-coded values, we use a new library by making calls directly into its functionality, we cut-and-paste a function that does almost what we need, changing one or two lines to get it to work, and so on. All those decisions seem harmless and allow us to get our prototype working more quickly, but they invariably come back to bite us (and if not us directly, the poor guy who has to maintain our code).
There are things we can take better advantage of to help us avoid a lot of these situations. You are probably aware of most (if not all) of them, but being reminded about them every once in a while helps us all remember to use them, even if it slows us down a bit.
Note: this article is based on a presentation I originally prepared for a computer conference back in the early 2000s. However, based on some of my recent experiences, it is still quite relevant, so I thought I would resurrect it here.
Avoiding “Magic Numbers”
Most modern languages provide constructs for easily dealing with “Magic Numbers.” In Java, there are enums to hold collections of constant data. And now that we can extend them to add our own methods, they are even more powerful. If you have a constant that is pretty stand alone, make it a static. I usually put it in the interface of the class that needs it defined.
For those things that are constant across an activation of your program, but perhaps not across all activations (database access parameters are a classic example of this), property files are best used. This allows you to take advantage of the constant-ness of the values in your code, and yet swap them out depending on the environment you are running in.
Another good tool for managing these pseudo-constants is dependency injection. By programming to the interface we can swap out the implementation as desired, changing the “constants” as needed.
Avoiding Duplicated Code
This is one of those situations that we all mean to avoid, but often use when push comes to shove. One of my colleagues espouses the attitude that you should never type the same code twice. If you have two classes that have a similar method, should they have a common superclass? Our tools have improved much over the decades, and creating that superclass (if it doesn’t already exist) has become almost trivial, so why not do it?
Oftentimes you need similar functionality across many different types of classes. Before you copy that method, think about what it is trying to accomplish. Would it make sense to have that capability encapsulated in a utility class? If so, take some time to explore the utilities that are already available to you. I recently ran into a situation where some older code was doing a somewhat specialized string comparison (multiple strings with unusual null handling). The code was long and somewhat convoluted (not to mention very specific to the problem domain). By making use of the null aware functions in org.apache.commons.lang.StringUtils, we were able to reduce the lines of code by an order of magnitude.
Sometimes the similar functionality needs to interact with the surrounding code more than a simple utility method will allow for. This may be a good candidate for using the Strategy pattern. Utilizing this pattern allows you to use common code across many different classes, without having to worry about a complex or convoluted class hierarchy.
Avoiding Library Dependency
As more and more open source tools are made available, the likelihood that your team will want to change which library they depend upon goes up. As different libraries leap-frogging one another, you want a clean way to take advantage of the new features offered. If they all programmed to the same API, you could handle that with dependency injection, as was mentioned above. However, that is rarely the case between competing projects in relatively new problem spaces. So what’s the solution? Create your own interface.
You can start out small, incorporating only the functionality you need. You can even “improve” the functionality from your point of view, combining several library calls into a single interface call. This can simplify a more complex and configurable interface to something more suited to your applications use.
One recent client was using three different libraries for accessing Excel(tm) worksheets from Java. They didn’t really need all three, but they had coded directly to each as they used it. The problem arose when they wanted to use a new feature from one library in a project that had started in a different library. If they had coded to a common interface they could have made the change quite simply.
None of these suggestions are revolutionary, but remembering to follow them from the beginning of a project will make everyone’s life easier in the long run.
— Clayton Neff, [email protected]