ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Towards Bug-Free Code

by Ashwin Jayaprakash
12/22/2004

"Bug-free code." Well, that's a bold statement to make about one's code. In August 2004, Mozilla announced that it would offer $500 for every serious bug found by security researchers. I wouldn't dare to make such a claim about my code on a regular basis, or I'd be broke in a month. However, if we make good use of some of the basic idioms and rules of thumb of design and programming, we can take a step closer towards software with fewer bugs. Any programmer worth his or her salt will agree that lately, design patterns have been overused to the point that the programmers start off directly with advanced patterns, while being completely ignorant of basic rules.

If you think that loose coupling is something to do with car parts, or if you find yourself amongst those who think test-driven development is only for developers who have a lot of time on their hands, then you should read further. In this article, you will learn how to design and develop your projects using some of the shiny new features of Java 1.5 that will result in fewer bugs and facilitate easy testing.

One of the fundamental constructs of an object-oriented programming language is an interface. As you might already know, an interface represents a set of contracts or basic behavior that allows the developer to switch the implementation without having to make changes to the code again and again.

A conscientious programmer will have a handful of interfaces in his program to ensure that the program is not too tightly coupled. An overzealous or novice programmer will be indiscriminate in creating too many interfaces and will still have packages that are neither stable nor easily extensible.

A line of code such as MyInterface intf = new MyInterfaceImpl() has little meaning, as the programmer has already decided that the implementation of MyInterface is MyInterfaceImpl right away. To a mature programmer, the solution seems obvious: use a factory pattern.

Hooking Implementations up to Interfaces

Let's assume that there are three interfaces with three respective implementations. The factory can be written as follows:


public class MyFactory
{
    Interface1 static getIntf1()
    {
    }

    Interface2 static getIntf2()
    {
    }

    Interface3 static getIntf3()
    {
    }
}
        

In a reasonably complex program, there could be around 10 to 15 interfaces. Imagine how unsightly the factory would look like with methods like getIntf1() to getIntf15(). For every new interface that gets introduced to the program, we'd have to add a new factory method, which does not sound like a good idea.

On the other hand, one could design a generic Factory that almost resembles a Map:


public class MyOtherFactory
{
    Object static getImplementationFor(Class
              interfaceClass)
    {
    }
}
        

The problem with this is that the key that uniquely identifies the implementation does not guarantee that the return type will be of the same type as the one expected by the caller. An explicit cast is required, like this:


MyInterface7 intf7 = (MyInterface7)
              MyOtherFactory.getImplementationFor(
              MyInterface7.class);
        

It is also prone to copy-and-paste mistakes like the one below, which cannot be identified until the program is run:


MyInterface7 intf7 = (MyInterface7)
              MyOtherFactory.getImplementationFor(
              MyInterface1.class);
        

With the introduction of generics in Java 1.5, we can design a factory/registry that is both type-safe and future-proof. Although the SimpleRegistry shown below is indeed very simple and has a few subtle shortcomings, the getImplFor(...) is the method on which to focus your attention. We'll talk about writing a better registry later.


public class SimpleRegistry
{
    private static final Map IMPL_MAP =
              new HashMap();

    static
    {
        IMPL_MAP.put(Number.class,
              new Integer(100));
    }

    public static <V> V getImplFor(
              Class<V> clazz)
    {
        final Object obj = IMPL_MAP.get(clazz);

        return clazz.cast(obj);
    }

    public static void main(String[] args)
    {
        Number number = SimpleRegistry.getImplFor(
              Number.class);

        System.out.println(number);
    }
}
        

The code that uses this is Number number = SimpleRegistry.getImplFor(Number.class). You will notice that there are no casts required, unlike the older MyOtherFactory example. The getImplFor(...) method says that it can accept a Class as a parameter, and also returns an Object of the same Class as the parameter. It would be impossible to do this:

Number number = SimpleRegistry.getImplFor(String.class) 

as the compile step would fail. You will also notice that it works with abstract classes (java.lang.Number) and even concrete classes like Integer integer = SimpleRegistry.getImplFor(Integer.class), although this does not make much sense.

If you want to streamline your interfaces, an empty/marker interface can be introduced that indicates that the implementation must be obtained from the registry. Drawing inspiration from electronic components, which are easily interchangeable, we'll name it Component:


public interface Component
{
}
        

Generic, Type-Safe Registry/Factory

The new and more practical registry to store all components will look like this:


public class Registry
{
    private final Map<Class<? extends
              Component>,
              Component> componentImplMap;

    protected Registry()
    {
        componentImplMap = Collections.
                synchronizedMap(
                new HashMap<Class<? extends
                Component>, Component>());
    }

    protected Component getComponentImpl(
                Class<? extends
                Component> componentClass)
    {
        /*
         Return the Component implemenation by
         looking up an internal map.
         Implementations can be cached too.
        */

        return componentImplMap.get(
                             componentClass);
    }

    public <T extends Component>
                void setComponentImpl(
                Class<T> componentClass,
                T componentImpl)
    {
        componentImplMap.put(componentClass,
                componentImpl);
    }

    //--- static fields and methods below ---

    protected static volatile Registry
                registry = null;

    public static void init()
    {
        getInstance();
    }

    public static Registry getInstance()
    {
        if (registry == null)
        {
            synchronized (Registry.class)
            {
                if (registry == null)
                {
                    registry = new Registry();
                }
            }
        }

        return registry;
    }

    /**
     * @param clazz
     * @return Instance of type clazz
     */
    public static <V extends Component> V
                getImplFor(Class<V> clazz)
    {
        final Registry registry = getInstance();

        final Component component =
                registry.getComponentImpl(clazz);

        return clazz.cast(component);
    }
}
        

This registry is used to store implementations of interfaces or abstract classes that ultimately implement the component interface. For example:


public interface MessagePublisher extends
                Component
{
    public void publish(Message msg)
                throws PublishException;
}

MessagePublisher publisher = Registry.getImplFor(
                MessagePublisher.class);
        

The registry will have to be populated with implementations as soon as the program starts up in the main(...) method or the startup hooks/classes that are allowed by some application servers.

Another advantage of this registry is that it truly is type-safe. The following are disallowed by the compiler itself:


//Cannot add non-Component instances.
Registry.getInstance().setComponentImpl(
                Integer.class, new Integer(100));

Registry.getInstance().setComponentImpl(
               Component.class, new Integer(100));

//Cannot add spurious mappings.
MessagePublisher anonPublisher =
                new MessagePublisher()
{
};

Registry.getInstance().setComponentImpl(
                MessageReceiver.class,
                anonPublisher);
        

Asking developers to write and run tests for the classes/modules they are working on before checking in their code is always met with resistance. "My module depends on another module and that is not yet ready," or "the code is still a work in progress," or "the system is too complicated to run a unit-level test." They continue, "Nothing short of a full-blown, end-to-end integration test will do. And that can be done only when everything is ready." Well, these were some of the reasons I myself must have given in the past, I must confess. But when you sit down to think about it, you'll see that the reasons are lame. Most of the time, the real reason is that the program design did not allow for easy unit testing. This is where essential object-oriented concepts such as the dependency inversion principle, the acyclic dependencies principle, the stable dependencies principle, and the rest prove to be extremely useful.

The reason why I'm harping on using interfaces or abstractions will become clearer next, where we take on the "boring" task of unit testing the program.

Pages: 1, 2, 3, 4

Next Pagearrow