Wednesday, February 17, 2010

Beware of the SwingWorker

The SwingWorker is a nice class for executing long running tasks behind that won't block the event dispatch thread, but it has its gotchas that can be difficult to track down. The one in particular that has been the most cause of pain for me is that it swallows exceptions in the doInBackground method unless you explicitly call the get method from the done method.

Consider this seemingly simple code

SwingWorker w = new SwingWorker() {
@Override
protected Object doInBackground() throws Exception
{
throw new RuntimeException("Catch me");
}
};
w.execute();

Of course this throws an exception, right? In fact, it doesn't (well, it does but it never bubbles up so it would go completely unnoticed).

The way to ensure the exception is thrown is by calling get in the done method.


@Override
protected void done()
{
try
{
get();
}
catch (InterruptedException e)
{
throw new RuntimeException(e);
}
catch (ExecutionException e)
{
// throw the cause since the execution exception wraps the
// underlying exception
throw new RuntimeException(e.getCause());
}
}


It's an ugly solution though since the get method throws those checked exceptions but at least the exception gets thrown.

This is a very common problem when the swing worker is not returning any value and just performing a task, so you never call the get method.

Thursday, February 11, 2010

Difficulties of the Java clone method

Sometimes we have a need to copy objects. The clone method is a common way of doing that, but it is all too often misused. This post will discuss some common pitfalls and alternatives to cloning.

First, the Cloneable interface doesn't enforce any contracts. It has no methods. The clone method belongs to Object. Cloneable is just a marker interface to indicate that an object supports cloning. However, in order to make the clone method accessible to other objects, you need to make it public, since it is protected by default (and now all subclasses will have a public clone method, even if they are not directly cloneable since the visibility of the method can't be reduced, so be sure to implement clone for the subclasses).

Additionally, reading the clone method documentation a little more closely, it states the the following are generally true, but not requirements.
x.clone() != x
x.clone().getClass() == x.getClass()
x.clone().equals(x)

Even the stated contract of clone is quite vague, and there really is no guarantee of the object that will be returned. But working on the idea that we want a meaningful clone method, let's assume these will be true and move on to common problems with clone.

First, all objects in the hierarchy must implement clone, and you can't always control that. An implementation of clone will generally look like:

public class CloneMe extends NotCloneable implements Cloneable {
public CloneMe clone() {
try {
// This will fail if NotCloneable doesn't implement the clone method
CloneMe clone = (CloneMe) super.clone();

// copy the other fields
}
catch(CloneNotSupportedException e) {
// do something with the exception
}
}
}

It is difficult to copy objects in the object graph if they too don't
implement clone

public class CloneMe implements Cloneable {
private MyInterface obj;

public CloneMe clone() {
// try/catch omitted for brevity
CloneMe clone = (CloneMe) super.clone();
clone.obj = How can this be copied? We don't even know the type of MyInterface?.
Ideally use obj.clone(), but if it doesn't implement Cloneable, it's
impossible to make a deep copy (if the underlying type has a clone method,
it might be possible to reflectively invoke it).

}
}

Though the above example is illustrated by the fact that obj is an interface, it could have the same problem even if it is a non-final class.

Cloning lists and arrays must also clone the elements of the arrays

public class CloneMe implements Cloneable {
private Object[] myArray;
public CloneMe clone() {
CloneMe clone = (CloneMe) super.clone();
clone.myArray = Arrays.copyOf(myArray); // These arrays refer to the same elements
and modifying one's elements will modify the other's

}
}


Objects that implement clone can't clone final fields. Consider the class

public class CloneMe implements Cloneable {

private final Date myDate;

public CloneMe clone() {
// note: try/catch omitted for brevity
CloneMe clone = (CloneMe) super.clone();

// This won't compile since myDate is final
// This also illustrates a problem discussed above.
// What if date is actually a subclass of date? The clone and
// original objects will have different types for myDate.
clone.myDate = new Date(myDate.getTime());

}
}

Note: If the fields are completely immutable, they don't need to be clone since they can't be changed.

These are some of the major problems with clone, and Joshua Bloch discusses these points as well in Effective Java.

If you need to copy objects, what are the alternatives?

You could use copy constructors. However, copy constructors still have the problem that all of the objects being copied need to be deep copied as well. And since objects being copied may be a subclass of the declared type, the only way to copy them would be to reflectively invoke its copy constructor (if it has one).

You could write your own interface that has a copy method. It would function similarly to the clone method, but it would actually enforce some contract.

The best situation is to avoid object copying whenever possible (create totally new objects if needed). Favor immutability to avoid the need for clone and beware of the pitfalls and document well when copying is absolutely need it.

Tuesday, February 2, 2010

What to do when you find a bug

Inevitably you will find bugs in your code (yes, even you will write bugs). Once a bug has been identified, what steps do you take to fix it? Regardless of how small the bug is, it should be put into a bug tracking system (you do have one, right? If not, consider something free like Trac). This allows you to track bugs over time and do some deeper analysis.

Once the bug is identified, take steps to reproduce the bug by writing a test that exercises the bug. Then make the code change to fix the bug. Now you've fixed it and have a test to ensure it doesn't happen again.

This is where too many people stop. It is important to identify why the bug occurred. Was it an error in specifications? An error in coding logic? or some other error? Note this in the tracking system.

Periodically, query the bug tracking system to get an idea of why these bugs are happening (root cause analysis) and take the organizational steps necessary to correct them.