Recently I was working on this large project originally written in Java 1.4. In this particular effort we were refactoring parts of the project while moving the source code to Java 6 (introducing Generics) as well as using some of the new libraries available. Everything was going smooth (a little too smooth maybe), until we hit a regression defect. One of the extended processes in the application was no longer working…
As I mentioned earlier it was a large project, everything compiled fine, the unit tests worked fine and static code analysis and code review looked OK. It took us a while to figure out what went wrong: with the introduction of generics, an overridden method had become overloaded!
Funny… let me try an explain with a small example. For the purposes of this example let us consider two simple beans BaseBean and ExtensionBean:
public abstract class BaseBean { // Fields, methods etc. } public class ExtensionBean extends BaseBean { // Extension stuff }
Now, let us assume we have an interface, a Processor for the ExtensionBean as well a special case processor (extending the regular processor) of ExtensionBean which is used for certain use cases:
public interface Processor { /** * Our process method */ public void process (BaseBean bean); } /** * This is the regular processor for the extension bean */ public class ExtensionBeanProcessor implements Processor{ public void process(BaseBean bean) { processInternal (bean, 1); } protected void processInternal (BaseBean bean, int value) { // Regular processing System.out.println("Regular Processing"); } } /** * This is an additional special case processor for he extension bean */ public class SpecialExtensionBeanProcessor extends ExtensionBeanProcessor { protected void processInternal(BaseBean bean, int value) { // Do something else System.out.println("Extended Processing"); } }
If we run a simple test script:
ExtensionBean b = new ExtensionBean(); new ExtensionBeanProcessor().process(b); new SpecialExtensionBeanProcessor().process(b);
We get the following output:
Regular Processing Extended Processing
Assuming this was the state of the code before we started our effort, we now introduce Generics into the mix. What I have not mentioned so far is that the project had a few hundred Processors spread across and handling multiple extensions of BaseBean. Most of the processors were specialized for certain types of bean extensions and the first step most of them did was to cast the object. So, one of our targets for generics was the processor, and we changed it to:
/** * @param <B> The type of bean the processor handles */ public interface Processor <B> { /** * Our process method - note the change in method signature now */ public void process (B bean); }
The ExtensionBeanProcessor was changed to (note the generics and the method signature):
/** * This is the regular processor for the extension bean */ public class ExtensionBeanProcessor implements Processor<ExtensionBean> { @Override public void process(ExtensionBean bean) { processInternal (bean, 1); } protected void processInternal (ExtensionBean bean, int value) { // Regular processing System.out.println("Regular Processing"); } }
This is where things went wrong. A run of the same test as above now returns (note the second output):
Regular Processing Regular Processing
Note that we mentioned the code was initially written in Java 1.4, so we did not have the @Override tag anywhere. The developer forgot about the overridden internal method (processInternal) in SpecialExtensionBeanProcessor, there was no compilation error. The processInternal method in SpecialExtensionBeanProcessor has now become an overloaded method rather than an overridden method.
A second set of eyes mostly looked for the interface methods process(B) on all the processors, and with a few hundred of them to review, this one particular guy was missed.
The fix: was actually pretty simple once identified, we changed the method signature of processInternal in SpecialExtensionBeanProcessor to take in a ExtensionBean and the world made sense again!
Discussion
No comments yet.