Skip to main content

Diagnosing Java Code: The Fictitious Implementation bug pattern, Part 1

Assumed invariants can sabotage your interfaces

Eric Allen (eallen@cs.rice.edu), Ph.D. candidate, Java programming languages team, Rice University
Eric Allen has an A.B. in computer science and mathematics from Cornell University, and is currently a Ph.D. candidate in the Java programming languages team at Rice University. His research concerns the development of semantic models and static analysis tools for the Java language, both at the source and bytecode levels. Currently, he is implementing a source-to-bytecode compiler for the NextGen programming language, an extension of the Java language with generic run-time types. Contact Eric at eallen@cs.rice.edu.

Summary:  The Java language is free from many of the problems endemic to languages that support multiple inheritance -- in part because it restricts the specification of interface invariants to type signatures. But that restriction can combine with poor documentation to create problems: if you make incorrect assumptions about how an interface will be implemented, you may encounter frustrating bugs at run time. This article, first in a two-part series, outlines the development practices that can lead to such errors -- and ways to avoid them.

View more content in this series

Date:  31 Jul 2001
Level:  Introductory
Activity:  1457 views

Fast-track to the code

Listing 1. A interface for stacks
This interface looks simple, but its ambiguities can breed errors.

Listing 2. An interface for poppers
This interface's incompatibility with Listing 1 will not be detected by a compiler.

Java language interfaces are a great tool. They provide many of the advantages of multiple inheritance without all of the problems. Specifying an interface for all the services that a client expects to use makes it possible to plug in various implementations of that interface as necessary.

Unfortunately, the only parts of a specification that can be expressed are method signatures. There may very well be many other invariants that are expected to hold for any implementation, but the Java language provides no facility to check them.

The Fictitious Implementation bug pattern

Because of this limitation, it is possible to "implement" an interface without actually meeting its intended semantics. Bugs that result from such fictitious implementations are the topic of this week's column.

For example, consider the following interface for stacks:


Listing 1. An interface for stacks

public interface Stack {
  public Object pop();
  public void push(Object top);
  public boolean isEmpty();
}

Any class containing methods that match the above signatures would, from the perspective of the Java type checker, serve as a legal implementation of a Stack. But in practice there are several additional requirements we would expect a stack to fulfill. For instance:

  • If an object o is pushed on a stack s, and the next operation performed on the stack is pop, then the return value of that operation should be o.

  • If, for a given stack s, the return value of s.isEmpty() is true, and the next operation performed on the stack is pop, then that call to pop should throw a RuntimeException.

Don't miss the rest of "The Fictitious Implementation bug pattern" series

Part 2: "Use assertions and unit tests as executable documentation to stamp out bugs" (August 2001)

There are lots of other invariants we could specify. How do we expect a stack to handle multiple push operations? What behavior do we expect with multiple threads? It is difficult to enforce invariants such as these programmatically. We could (and should) mention them in the documentation, but a developer writing an implementation could easily ignore them. If that happens, then a client that relies on such invariants will not work with such an implementation, and we'll have a bug. I call bugs of this pattern fictitious implementations because I place the blame for them squarely on the implementation rather than the client. Like any bug that deserves its own pattern, a fictitious implementation may not be immediately apparent, but can lurk hidden until some uncommon execution path uncovers it.


The Java language is not to blame!

Before continuing, I should mention that I am not criticizing the Java language's inability to specify such invariants. Any mechanism that allowed for such specification would have many accompanying disadvantages. For one thing, many of the invariants that we would like to specify cannot be checked statically. Although type signatures express only a small set of invariants, they are easy to check compared to stipulations like the ones we outlined above for stacks.

There's another disadvantage to allowing for more expressive specifications in interfaces: in doing so, we could easily burden the Java language with many of the problems that plague languages with full multiple inheritance. Consider the following interface:


Listing 2. An interface for poppers

public interface Popper {
  public Object pop();
}

Let's suppose that the pop() method in this interface has an intended invariant: a call to pop() should never throw a RuntimeException. Now, with the current Java interface facility, it would be possible for a class to implement both Stack and Popper. But, according to the intended specifications, the implementations of pop() in each class would be mutually incompatible.

If we wanted to continue to allow a class to implement both interfaces, we would have to provide the ability to overload a method based not just on type signature, but also on the extra kinds of invariant specifications we add to the language. But this would introduce a serious problem: how would we determine, at a given method invocation, which version of the method to invoke? Normally, this is done by determining the static types of the method arguments; but with extra invariants, such a technique would not be sufficient. This problem is very similar to one experienced with full multiple inheritance: if more than one parent class defines a method with the same name and type signature, how do we disambiguate calls to these methods? There are many ways to allow for manual disambiguation of such calls, but they all tend to add a great deal of complexity to a language. What's worse, schemes to automatically disambiguate such calls are themselves complex and prone to error, as programmers will often make mistakes when predicting which method will be called.

So, it is a perfectly justifiable design choice to restrict the specification of interface invariants to type signatures. And we don't need to loosen that restriction to detect and correct fictitious implementations.


Detecting fictitious implementations

The main problem with fictitious implementations is, of course, that they will pass compilation without incident. The symptoms at run time will often be very puzzling because the extra invariants a programmer expects an interface to satisfy are often left unspoken; the programmer may not even be consciously aware that he is expecting them to be satisfied. The process of correcting a bug often starts with a period of confusion; and the programmer tripped up by the fictitious implementation pattern may at first try to convince himself that the problem he has observed cannot possibly have occurred. If you find yourself in this situation, it's a good time to check your premises. What hidden assumptions have you not stated? How can you test these assumptions to thoroughly eliminate the possibility that they are faulty? If you rely on an interface to another part of the system, and the implementation of that interface has been modified since the last release, then you might be running up against a fictitious implementation.


Some ideas for cures

In cases such as these, it is important that the maintainer of the interface document any invariants that may be assumed by a client programmer. If a client programmer discovers that an invariant he was relying on was not, in fact, documented, then the client programmer and the maintainer of the interface should sit down and discuss whether that invariant should be made explicit. Often, it will be easy to add an assumed invariant to the specification, saving the client the trouble of modifying all the code that relied on it.

If the maintainer of the interface is not available, then the client cannot rely on anything but the documented invariants of the interface. If these are scant, then the interface is much less valuable than it could be. If the client chooses to rely on undocumented invariants, the client code he writes can quickly lose value itself, since it may be incompatible with future releases of the interface implementation.

If you're designing an interface, you have two very powerful tools to prevent bugs of this pattern: unit tests and assertions. In Part 2 of this series, I will discuss how these technologies can be used as a sort of executable documentation to aid in enforcing interface invariants.


Resources

About the author

Eric Allen has an A.B. in computer science and mathematics from Cornell University, and is currently a Ph.D. candidate in the Java programming languages team at Rice University. His research concerns the development of semantic models and static analysis tools for the Java language, both at the source and bytecode levels. Currently, he is implementing a source-to-bytecode compiler for the NextGen programming language, an extension of the Java language with generic run-time types. Contact Eric at eallen@cs.rice.edu.

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=10570
ArticleTitle=Diagnosing Java Code: The Fictitious Implementation bug pattern, Part 1
publish-date=07312001
author1-email=eallen@cs.rice.edu
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