Tuesday, March 07, 2006

Coding A Tight Ship

Good programmers get that way by making mistakes, and seeing the mistakes of others.  I saw a mistake made by a colleague not too long ago which caused a problem that was very difficult to track down, and I learned from it.  The mistake was changing shared objects that should not have been changed.  Specifically, the objects were cached objects used all over the system, so changing the objects caused the cached versions to be changed as well.  My solution to this problem was to make shared objects immutable, or read only.  This wasn't exactly as straight-forward as I would have thought.

The first problem, I had entire lists that were cached.  This list was drawn from the database, and used over and over as a complete list, so it made sense to cache the list as a whole.  I chose to use java.util.List as the interface for the lists I was returning, but quickly realized that there is no immutable List in Java, or at least none that I found (know of any?).  So I decided to roll my own.

I started by creating a class called ImmutableList which extends java.util.ArrayList, and has a constructor which takes a List that it is to secure.  I am using Java 5 with support for Generics, where "E" is the object type to be stored in the List (for those still using Java 1.4 you can for the most part replace "E" with "Object").


public ImmutableList (List<E> list)
{
super(list);
}


Next I went through and wrote methods to override methods that did stuff to the list.  I have all of these methods throwing a run-time exception that I created.


public boolean add (E o)
{
throw new ImmutableRuntimeException();
}

public boolean remove (Object o)
{
throw new ImmutableRuntimeException();
}

...etc...



But there is a problem, the List interface has methods that return a java.util.Iterator object or a java.util.ListIterator object.  Both of these iteration object s have the ability to alter the underlying list, so this too needed to be locked down.  I made these private inner classes so that they could only be created by the ImmutableList.  Every method that allowed changes to the List will throw a run-time exception.  Below is what the immutable version of the Iterator looks like.

 private class ImmutableIterator implements Iterator<E>
{
private Iterator<E> iter;

public ImmutableIterator (Iterator<E> iter)
{
this.iter = iter;
}

public boolean hasNext ()
{
return iter.hasNext();
}

public E next ()
{
return iter.next();
}

public void remove ()
{
throw new ImmutableRuntimeException();
}
}
 

This locks down the List, but don't forget to also lock down the objects inside of the List.  When you get done, it should be impossible to write to the object or any object that it holds.  Don't leave it to convention to enforce policy, enforce policy with the language.  If you do this, and the system starts to misbehave, you have one place to look.  Of course, the more you do this, the less likely a bug will be introduced into the system.  We are human, we make mistakes, so why not make it easier for us by enforcing some rules.

Happy coding.

2 comments:

Rob Sanheim said...

Googling for immutable list java returns the library you wanted - Collections.unmodifiableList. =)

Robert Hanson said...

I did Google it... Really! :)

I have no idea why that didn't come up in my searching. Thanks for the link.

...In any case, I think the underlying message is still valid, lock things down tight. The bigger the system, the more you need to run a tight ship.