ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Hardcore Java

Seven Low-Cost Ways to Improve Legacy Code

by Robert Simmons, Jr., author of Hardcore Java
04/28/2004

Author's note: This article presents seven techniques I've developed and used in my consulting work that are designed to improve legacy code. You can apply some of these techniques using either freely available tools or with scripts. You'll apply the others manually, but they shouldn't represent a significant investment in time. Be forewarned, however, that all of these techniques may reveal other issues in the code base, such as hidden bugs, which could take a significant amount of time to fix.

1. Use a Stronger Compiler for Your Code

This technique calls for a tool change to a stronger compiler. While there are many Java IDEs on the market, most of them use the default Sun compiler. Unfortunately, this compiler is not as strict as it could be. Many common programming errors slip through the cracks. The following code illustrates one of these errors:

public class SomeClass {
    private String someValue;
	
    public SomeClass(final String someValue) {
        this.someValue = someValue;
    }
	
    public void setFirstName(final String value) {
        this.someValue = someValue;
    }
}

In this example, the developer meant to change the name of the property from value to someValue. The developer did a good job in changing the instance variable and the constructor, but he didn't change the name of the parameter to the setter. The resulting error is that the assignment in the method setFirstName() has no effect since it merely sets this.someValue to whatever it currently is. As a result, SomeClass has a difficult-to-find logic bug. Unfortunately, the standard JDK compiler will not find this problem, but there are other compilers that will. The compiler that comes with the free Eclipse platform can be configured to look for problems such as these. It can check for assignments that have no effect, variables that are unused, and a host of other issues. Not even the expensive JBuilder product can do many of the things Eclipse does for free. For example, I had a client who was using the JBuilder IDE. When I imported the project into Eclipse, it detected some 200 bugs in 700 classes that JBuilder didn't find. Although this led to long debugging sessions, the project's code became more stable and the client's customers much happier. Today this company uses Eclipse as its main tool. (For more on Eclipse, check out O'Reilly's just-released book on mastering the Eclipse platform.)

Eclipse

Related Reading

Eclipse
By Steve Holzner

2. Use a Code Formatter to Reformat Your Code

One of the most important things any IT department can do is to create coding standards. Things such as spacing, bracket placement, and commenting guidelines may seem trivial. Without these standards, however, your developers will have a hard time reading each other's code. What's more, the ramp-up time of consultants and new employees can easily triple. If that isn't enough to convince you, think of what would happen if one of your critical developers left the company, leaving behind a large amount of uncommented, obfuscated code. At that point, it may be cheaper just to redesign the product than to figure it out. Unfortunately, these are not hypothetical examples, as I have seen them repeatedly in my work. Coding standards make all of these problems much easier to manage.

However, coding standards are meaningless if they aren't enforced. A great way to enforce them is with code-formatting tools such as Jalopy or Eclipse. These tools will reformat legacy code and turn it into something more readable. Jalopy can even be configured to insert special tags to alert developers of missing documentation (which is not inexpensive to fix, but definitely worth it). These tools are easy to use and, once you've decided on the configuration, can be passed to all departments. What's more, source-code formatters can be hooked up to version-control systems to automatically format the code upon check-in.

One common objection I hear to code formatting is that "It messes up the diffs with the source control management (SCM) system." It is true that the first time you format your code, it is likely to generate heavy diffs. However, if your developers depend on diffs instead of code documentation to determine problems, then you probably already have a serious problem in your code base. Furthermore, since your data objects don't require that the listeners be explicitly removed, the user of these data objects need not worry about managing the addition and removal of listeners. The removal of this management task makes code much easier to maintain.

3. Introduce final All Over Your Code

Those of you who have read Chapter 2 ("The Final Story") of Hardcore Java know that final is one of the most useful and underused keywords in the Java language. Many of my colleagues see final all over the place in my code and are startled by it. However, once I explain that final causes logic errors to be turned into compiler errors, they quickly become converts.

If you have junior developers, the best tack is to simply be firm. At first, they might complain and grumble a bit, but they will quickly get used to it. Every parameter to a method should be declared final unless the parameter is intended to be an out or in-out parameter. In addition, final should be used on all variables in immutable classes and whenever you declare a local variable that you don't intend to change throughout the method. You can even use it inside of loops, as shown here:

public int find(final List domain, final SomeClass target) {
    final String targetValue = target.value;
    for (final Iterator iter = domain.iterator(); iter.hasNext();) {
        final SomeClass element = (SomeClass)iter.next();
            if (element.value == targetValue) {
                return domain.indexOf(element);
            }
    }
    return -1;
}

Notice all of the places final is used in this code. It makes the code unbelievably solid. However, be prepared for your compiler to complain when you apply it to your code base. You will most likely find quite a few hidden bugs. Regardless, I recommend you start using final today. And every time you are editing legacy code, add it into code.

(Unfortunately, I know of no tool that will automate this conversion for you, though that would be a great feature to add to tools such as Eclipse, Checkstyle, or Jalopy. If anyone knows of a tool or writes one to do it, I would love to hear about it.)

4. Remove Commented-Out Code

Often I am confronted with legacy code that contains good-sized portions of commented-out code. Usually, this code was commented out long ago by a developer unwilling to delete the code, "just in case." The problem here is that the rest of the code base has had ongoing development, but the commented code hasn't. So if you were to uncomment the code, you would have big problems.

You should go in and delete all commented-out code. Not only does it make code hard to read, but it could be disastrous if some eager-to-please junior developer sees the "for later use" comment and decides to uncomment the code. Just remove it and save yourself the hassle.

In fact, I recommend you remove old code rather than commenting it out. Rarely are things commented out and put back in later. The only time I can think of this happening routinely is in the case of commenting for very short-term debugging. Other than that, the best policy is "don't be afraid of the delete key." If you have some great algorithm in the code, copy it into a junk file, and rip it out of the code. The result will be less lint in your code.

5. Refactor Classes to Remove Anonymous Classes

In Chapter 4 ("Collections") of Hardcore Java, I explore the realm of nested classes in detail. One of the most commonly used nested classes is the anonymous class. However, the fact that anonymous classes can't be reused at all generally makes using them a bad idea. In addition, anonymous classes make code harder to read and bloat up the class declaration space of your program.

You should remove these classes, and instead either convert the anonymous classes to inner classes or, better yet, just implement that listener interface yourself. Although it involves a bit of typing and cut-and-paste code, the resulting code will be easier to read and understand.

6. Replace Listeners with Weak Listeners

Chapter 11 ("References in Four Flavors") of Hardcore Java illustrates a recurring problem in many Java programs, namely the creation of circular reference trees that pin objects in memory and interfere with garbage collection. The widely held misconception that a "Java developer does not have to worry about the memory of his program" has led many Java developers to create programs that require excessive amounts of resources. The extra resources consumed by objects unintentionally pinned in memory are Java's version of a memory leak.

This kind of memory leak can be avoided by the use of weak references and weak listeners. Weak references are a special kind of reference that do not block garbage collection. This allows your data objects to hold references to GUI panels in their property change listeners without blocking the garbage collection of those panels. Furthermore, it relieves the user of the data object from having to constantly manage the addition and removal of listeners, which results in code that is easier to manage. Legacy programs can be easily converted to use weak listeners. To show how these programs can be converted, consider the following legacy code:

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class SomeButtonClass1 {
    private final Set listeners = new HashSet();
    public void addActionListener(final ActionListener l) {
        listeners.add(l);
    }
    public void removeActionListener(final ActionListener l) {
        listeners.remove(l);
    }
    protected void fireActionPerformed(final ActionEvent event) {
        for (final Iterator iter = listeners.iterator(); 
             iter.hasNext();) {
            ((ActionListener)iter).actionPerformed(event);
        }
    }
}

This code can easily be converted to use weak listeners with a couple of minor changes:

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;

public class SomeButtonClass {
    private final Map listeners = new WeakHashMap();
    public void addActionListener(final ActionListener l) {
        listeners.put(l, null);
    }
    public void removeActionListener(final ActionListener l) {
        listeners.remove(l);
    }
    protected void fireActionPerformed(final ActionEvent event) {
        for (final Iterator iter = listeners.keySet().iterator();
             iter.hasNext();) {
            ((ActionListener)iter).actionPerformed(event);
        }
    }
}

After these three small changes, the listeners don't even need to bother with removing themselves, if they don't wish to. If they remove themselves, all will be OK. If they are merely garbage collected, all will still be OK. To understand how this works in more detail, I encourage you to read Chapter 11 of Hardcore Java.

Hardcore Java

Related Reading

Hardcore Java
By Robert Simmons, Jr.

7. Replace Integer Constants with Constant Objects or Enums

Chapter 7 ("All About Constants") of Hardcore Java talks about constants in detail. One of the most important lessons you should carry out of that chapter is that using integers for option constants is a bad idea. For example, if you want to create a class that can only take a color of the rainbow as an argument to a method, using integers for each of the seven colors is the wrong approach. We show an example of such code here:

public final class RainbowColor {
    public final static int RED = 0;
    public final static int ORANGE = 1;
    public final static int YELLOW = 2;
    public final static int GREEN = 3;
    public final static int BLUE = 4;
    public final static int INDIGO = 5;
    public final static int VIOLET = 6;
}

When using integer constants such as these, you might write the following code:


public void doSomething(final int rainbowColor) {
       // ...
     }

The problem here is that the user can pass you any integer. In order to make solid code, you will have to check that integer in every piece of code against the valid integers. This checking code will have to be maintained in several places. On the other hand, the use of Enums or Constant Objects relieves you of the need to check, as we see in the revised option constant class:

public final class RainbowColor {
    public final static RainbowColor RED = new RainbowColor("RED");
    public final static RainbowColor ORANGE = new RainbowColor("ORANGE");
    public final static RainbowColor YELLOW = new RainbowColor("YELLOW");
    public final static RainbowColor GREEN = new RainbowColor("GREEN");
    public final static RainbowColor BLUE = new RainbowColor("BLUE");
    public final static RainbowColor INDIGO = new RainbowColor("INDIGO");
    public final static RainbowColor VIOLET = new RainbowColor("VIOLET");

    final String name;

    private RainbowColor(final String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}

With the revised class, no checking is necessary during its use:

public void doSomething(final RainbowColor rainbowColor) {
  // ...
}

Since the user cannot create any more instances of the RainbowColor, there is no need to check anything. Furthermore, since comparisons of the objects are done by instance instead of by equality, comparisons are extremely fast. If you have access to JDK 1.5 in your coding environment, enums are the equivalent of Constant objects, with less code needed from the developer. If you are stuck writing for an earlier JDK (as many corporate programmers are), you should investigate and use the Constant object pattern extensively.

Conclusion

These seven techniques are fairly low cost in terms of man hours, and all are cost-free in terms of equipment and software. They can all be performed by hand or with freely available software. Although they represent only the tip of the iceberg of code-quality improvement, they offer a good place to start when time is tight and quality demands are high. Given the tools at the disposal of software engineers, there should be no excuse for writing code that is anything other than solid as stone. This will allow you to concentrate less on finding little annoying typos and more on the business your customers or employers use to make money. They will be happier with your code, and you will have to work less to accomplish the same tasks.

Robert Simmons, Jr. lives and works as a senior software architect in Germany. He is the author of O'Reilly's Hardcore Java.


O'Reilly Media recently released Hardcore Java.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.