Skip to main content

Diagnosing Java Code: The Broken Dispatch bug pattern

Argument upcasting cures inaccurate method invocation

Eric Allen (eallen@cyc.com), Lead Java Developer, Cycorp, Inc.
Eric Allen has an A.B. in computer science and mathematics from Cornell University. He is currently the lead Java software developer at Cycorp, Inc., and a part-time graduate student in the programming languages team at Rice University. His research concerns the development of formal semantic models of Java, and extensions of Java, both at the source and bytecode levels. Currently, he is implementing a source-to-bytecode compiler for the NextGen programming language, an extension of Java with generic run-time types. Contact Eric at eallen@cyc.com.

Summary:  Method dispatch in an object-oriented language such as the Java language, in which methods can be overloaded and overridden, can cause difficulties of code management in even moderately complex programs. In this week's Diagnosing Java Code, Eric Allen discusses the bug pattern caused by these difficulties, outlining the symptoms when argument mismatches cause the wrong method to be invoked, and offering a few solutions to combat the problem.

View more content in this series

Date:  01 May 2001
Level:  Introductory
Activity:  1129 views

Wholes and parts

Remember the adage, "The whole is greater than the sum of its parts"? Single events, when combined as an interacting unit, can demonstrate many more results than the sum of results from each event on its own.

Programs can be the same way. With each new method added to a program, the set of possible flows of control through the program grows dramatically. With large programs, it can quickly grow out of control. And like a perverse magic trick, sometimes the direction you expect is not where you end up -- similar to what can happen when you overload or override methods.


The Broken Dispatch bug pattern

One of the most powerful features of an object-oriented language is inheritance polymorphism. This feature allows us to overload and override methods depending on the argument types. Like any other powerful tool, though, this feature introduces new dangers.

Although Java programmers quickly learn the rules governing which method will be called on an invocation, it can be easy in a large program to overload a method in one class with the result of breaking code that previously worked in another class. Such bugs match what I call the Broken Dispatch pattern.

This pattern can be described as follows:

  • The arguments to an overloaded method, such as foo, are passed to another method, such as goo, that takes more general types.
  • goo then invokes foo with these arguments.
  • But because the static types of these arguments inside the scope of goo are more general, the wrong version of method foo might be invoked.

Errors like these can be difficult to diagnose because they can be introduced simply by adding new methods (as opposed to modifying exists ones). Also, the program execution may continue for quite some time before any problems are discovered.


The symptoms

To illustrate the nature of this pattern, let's consider the following example code for an implementation of immutable lists from my earlier article on the "Null Flag bug pattern."

interface List {
  public Object getFirst();
  public List getRest();
}

class Empty implements List {
  public Object getFirst() { throw new NoSuchElementException(); }
  public List getRest() { throw new NoSuchElementException(); }
  public boolean equals(Object that) { 
    return this.getClass() == that.getClass(); 
  }
}

class Cons implements List {
  
  Object first;
  List rest;
  
  Cons(Object _first) {
    this.first = _first;
    this.rest = new Empty();
  }
  Cons(Object _first, List _rest) {
    this.first = _first;
    this.rest = _rest;
  }
  public Object getFirst() { return this.first; }
  public List getRest() { return this.rest; }
  ...
}

In that article, we implemented linked lists as containers for these immutable lists.

Let's suppose that we're implementing linked lists in a separate package in which we know that all instances of class LinkedList will be lists of Strings. We could write the constructors to enforce this invariant as follows:

public class LinkedList {

  private List value;

  /**
   * Constructs an empty LinkedList.
   */
  public LinkedList() { this.value = new Empty(); }

  /**
   * Constructs a LinkedList containing only the given element.
   */
  public LinkedList(String _first) { this.value = new Cons(_first); }

  /**
   * Constructs a LinkedList consisting of the given Object followed by
   * all the elements in the given LinkedList.
   */
  public LinkedList(String _first, LinkedList _rest) {
   this.value = new Cons(_first, _rest.value);
  }

  public Object getFirst() { return this.value.getFirst();  }

  public LinkedList getRest() { 
    return new LinkedList(this.value.getRest()); 
  }

  public void push(String s) { this.value = new Cons(s, this.value); }

  public String pop() {
    String result = (String)this.value.getFirst();
    this.value = this.value.getRest();
    return result;
  }

  public boolean isEmpty() { return this.value instanceof Empty; }

  public String toString() {...}
  ...
}

Suppose we write this code and all the test cases work. (Or, more, realistically, let's suppose that it doesn't work at first, but, after a few debugging cycles, we manage to get it working.)

Perhaps several months later you develop a new constructor on class Cons that takes a String representation of the list as its only argument. Such a constructor is quite useful -- it allows us to construct new lists with expressions such as the following:


  new Cons("(this is a list)")
  new Cons("(so is this)")


So, we write this constructor and all the test cases for it work. Great! But then, we notice that, mysteriously, some of the tests for methods on class LinkedList suddenly break. What's going on?


The cause

The problem is with the constructor on class LinkedList that takes a single String as its argument.

This constructor previously called the underlying constructor on class Cons. But, now that we've overloaded that constructor with a more specific method -- one that takes a String as argument -- this more specific method is invoked.

Unless the String passed to the LinkedList constructor is a valid representation of a Cons, the program will crash when trying to parse it. Worse yet, if that String just happens to be a valid representation of a Cons, the program will continue to execute with corrupt data. In that case, we would have introduced a saboteur into the data. For a discussion of saboteur data, see my last installment, "The Saboteur Data bug pattern."

The Broken Dispatch bug, like all bug patterns, is easiest to diagnose in "test-infested" code (to borrow from the terminology of Extreme Programming) in which all but the most trivial methods have corresponding unit tests. In such an environment, the most common symptom will be a test case for code you haven't touched that suddenly breaks.

When this happens, it's possibly a case of the Broken Dispatch pattern. If the test case breaks immediately after you overload another method, it almost certainly is.

If the code isn't test infested, things become more difficult. The bug might produce symptoms such as method invocations that return much more quickly than expected (and with incorrect results). Alternatively, you might find that certain events that were supposed to occur never did (because the appropriate method was never executed).

Remember though, symptoms like these can also be attributed to other bug patterns. If you encounter such symptoms, your best bet is to begin writing more unit tests, starting with the methods where the error was discovered and working back through the program execution.


Cures and preventions

The nice thing about this bug pattern is that there are some simple solutions. The most straightforward one is to upcast the arguments in the method invocation. In our example, this would mean rewriting the relevant LinkedList constructor as follows:


  public LinkedList(String _first) { 
    this.value = new Cons((Object)_first); 
  }

Of course, this approach solves the problem only for this one call to the Cons constructor. There may be other calls where we have to upcast, and this technique might become cumbersome for clients of the Cons class. In cases like these, you have to weigh the advantage you gain from having this convenient constructor against the potential for errors it introduces.

This dilemma is yet another example of the tension that exists between expressiveness and robustness of an interface. One way to get the best of both worlds would be to replace the String constructor on Cons with a static method that takes a String and returns a new Cons object.


Wrapup

Here's the summary of our latest bug pattern:

  • Pattern: Broken Dispatch
  • Symptoms: A test case for code you haven't touched suddenly breaks, right after you've overloaded another method.
  • Cause: The overloading has caused the untouched method to invoke a method other than the intended one.
  • Cures and preventions: Put in explicit upcasts. Or, rethink the set of methods you're providing on your various classes.

Remember, argument structure is a key component to assuring that when you overload or override a method, the call invokes the intended method.

Next month we'll take a breather from bug patterns to tackle some other important topics. My next installment of Diagnosing Java Code will examine how tail-recursive methods can impact performace of your Java programs. And don't worry, we'll get back to bug patterns soon.


Resources

  • Be sure to bookmark The Java Language Specification for a good discussion of the rules of method invocation.

  • This installment of Under the Hood (JavaWorld, June 1997) by Bill Venners describes the bytecode implementation of method invocation in the JVM.

  • Download JUnit and make your code "test infested."

  • For more on the Extreme Programming methodology, read "XP distilled" (developerWorks, March 2001), which provides an excellent digest of this very popular agile process.

  • Read Eric's complete series on diagnosing Java code.

About the author

Eric Allen has an A.B. in computer science and mathematics from Cornell University. He is currently the lead Java software developer at Cycorp, Inc., and a part-time graduate student in the programming languages team at Rice University. His research concerns the development of formal semantic models of Java, and extensions of Java, both at the source and bytecode levels. Currently, he is implementing a source-to-bytecode compiler for the NextGen programming language, an extension of Java with generic run-time types. Contact Eric at eallen@cyc.com.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=10534
ArticleTitle=Diagnosing Java Code: The Broken Dispatch bug pattern
publish-date=05012001
author1-email=eallen@cyc.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers