|IBM home | Products & services | Support & downloads | My account|
|Diagnosing Java code: Decoupling package dependencies|
One issue that comes up again and again in test-first programming is that many parts of a program seem impossible to test automatically. In particular, programs that make heavy use of outside resources and libraries seem hard to test because there's no good way to simulate the program's connections to these outside resources.
However, although such programs can be hard to test with Java code alone, there is a style of programming (with development tools) that addresses this problem -- component-based programming.
Component-based programming and the Java
Conceptually, these units of distribution correspond roughly to Java packages. However, packages in the Java language are quite limited in that they aren't decoupled from one another. The classes in each package are hardwired to the packages that they import (because they must refer to these packages explicitly).
Because the packages aren't decoupled from each other, it's difficult to uniformly replace package references in a program with other package references that provide the same functionality.
Also, separate development teams may accidentally use overlapping package names, causing trouble when teams try to use each other's packages. To ensure distinct package names, Sun has strongly urged the convention that each development team use the reverse of its Internet address as a prefix to all packages that the team develops. This convention is often followed, but not always.
Still, even if the package-naming convention were followed perfectly, there are other reasons programmers would want to decouple components from one another. One reason is that we can test the components much more effectively -- we'll explain this as we talk about a tool for component-based programming, the Jiazzi component system.
Jiazzi: A component system for the Java
... a system that enables the construction of [adds support for] large-scale binary components in Java. Jiazzi components can be thought of as generalizations of Java packages with added support for external linking and separate compilation. Jiazzi components are practical because they are constructed out of standard Java source code. Jiazzi requires neither extensions to the Java language nor special conventions for writing Java source code that will go inside a component. Our components are expressive because Jiazzi supports cyclic component linking and mixins, which are used together in an open class pattern that enables the modular addition of new features to existing classes.
The current implementation of Jiazzi integrates into the Java platform using a linker (for manipulating components) and a stub generator (to allow Jiazzi to be used with normal Java source compilers). Components in Jiazzi contain, import, and export Java classes and the Java platform's in-language support for inheritance can be used across component boundaries. Besides being expressive, the components are robust -- the implementation and linking of a component can be separately type checked.
Watch it decouple
Normally, this would tie the
Units are like LEGO bricks; they can be snapped together to create a program. If we view units as functions, we can say that Jiazzi provides for functional composition. Every unit takes one or more packages with specified "package signatures" and exports one or more packages, again with a specified signature.
Package signatures are like types; they constrain the shape of a package. A package signature would define the classes expected in a package, the method signatures of those classes, and so on. The signatures of the exported packages may depend on those imported.
There are two types of units:
Atoms describe the packages they import and export directly. Compounds inherit the imported and exported packages from the units they compose. If we view units as LEGO bricks, atoms are individual LEGO bricks and compounds are structures built from multiple LEGO bricks.
Units are described in separate files with a special specification
language. This language assigns names to the packages input and output by
a unit. For example, here is a simple atomic unit that takes in a
This unit is named
(By the way, notice that Jiazzi's approach of never actually viewing the Java source code is a big win. It allows us to use Jiazzi with compilers for non-Java languages targeted for the JVM, such as Jython, JSR-14, and NextGen. In fact, Jiazzi itself is written in JSR-14.)
After checking, the component linker generates a JAR file for each unit given to it. This JAR contains the compiled sources and stubs, as well as the signature information, as metadata. These JARs can then be linked together by feeding them to Jiazzi alongside appropriate compound units.
The Jiazzi unit linker works offline and solely over class file constant pools, renaming hidden methods to avoid accidental method-name collision.
Units can also be linked online by a special class loader. However, because the stub classes that the units were compiled against are not available, type checking must be done in the class loader as an "incremental whole-program analysis." In fact, at present, Jiazzi programmers must use a combination of offline and online linking, as many classes in the standard Java libraries can be linked to only through the class loader.
Another limitation of the current system is that renaming interferes with JNI and the reflection libraries. In particular, native methods are not renamed, as they are written in C code. As a result, many of the class libraries (which rely heavily on JNI and reflection) can't be repackaged as Jiazzi components.
As noted, compound units describe connections between other units. The linkages are in relation to the bound names of the imported and exported units of the compound; the linked units are completely unaware that they are linked (the linker ultimately macro-expands these compound units into atoms).
In this way, we can create and distribute JAR files for a program in such a way that we could swap new packages in and out with a simple recompilation. Not a single line of Java source code need be touched.
Additionally, other users of Jiazzi can develop against the classes provided by our program before our JAR file is available; all they would need are the package signatures corresponding to what we export. And their extensions would link just as well to any other implementation of that package signature.
Unit testing and
During testing, the constituent components of a program can be linked with special "mock" components whose classes simply record the actions of the components they test. In essence, these mock components serve a similar role to that of Recorders (see "Recorders test for proper method invocation" in Resources), but at the component level.
The tested components are like Cartesian "brains in vats"; they can't
tell the difference between being wired to mock components or to real
components. For example, in our sample application earlier, we could write
Compare this system with the Java package system, where every source file must explicitly state the packages it imports. In the Java package system, the only way to fool a whole package into linking to a test package would be to edit all of the source files and recompile. That procedure doesn't really lend itself to automated testing.
Unfortunately, because Jiazzi (quite reasonably) can't handle
reflection or JNI, and because these facilities are used extensively by
many of the built-in Java APIs, the existing API packages can't be
converted into Jiazzi components. But if they could, we'd be able to
perform much more powerful tests over our programs. For instance, a
program that used the Swing API could be wired to a mock Swing component
during testing that ensured all of the appropriate API calls were made
without actually trying to draw graphics objects. In a similar manner, we
could test interaction with the
Okay, nice dream, right? But even if reflection and JNI prevent us from making existing APIs into first-class Jiazzi units, there is a potential compromise.
Although connections between the existing APIs couldn't be decoupled, connections from new units to these APIs could be. In essence, the APIs could be bundled in special units that only exported classes; their imports would still be hardwired through the existing Java package system. Doing so would give us 99 percent of the functionality we'd have if these APIs were bona fide units; the only programmers that wouldn't be happy would be those who would like to build third-party replacements to the existing APIs.
Hopefully, Jiazzi evolution will move in this direction, or on a similar path that allows us to use it with the existing APIs. In the meantime, it still provides a very powerful mechanism for testing packages that don't use reflection or JNI.
Decoupling the past
Whether or not that comes to pass, there are big advantages to component-based programming even without a ubiquitous component software market. In particular, this style of programming allows for much more effective unit testing.
As we've shown, component-based programming with Jiazzi provides a very powerful way to test program components, and it works with existing Java (or Jython, or JSR-14) source files. We can all benefit from this powerful tool. It and others like it are an essential part of test-oriented programming.
Next time, I'll discuss Jam, an extension of the Java language that allows for mixin-based programming. Just as Jiazzi provides a way to decouple package dependencies, mixins provide a way to decouple class dependencies. As you might have guessed, mixins provide us with yet another powerful mechanism for testing a program.