ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Managing Your Dependencies with JDepend
Pages: 1, 2

Interface Packages

In most projects of any size, you typically spend some time identifying and defining the key contracts or interfaces between the major components of your system. This arrangement allows various development groups to proceed in parallel using the interface as an informal contract that will allow them to successfully integrate at a future date.



You will typically capture this contract information as either Java interfaces and/or Java abstract classes that can be grouped, as a cohesive unit, in a Java package. From an OO perspective, you have a package with little or no implementation that is being used throughout the rest of the system. This conforms to the general guidance:

Program to an interface, not to an implementation.

These Interface packages are represented by the ellipse in the upper left hand corner of Figure 3. Because they are only composed of Java interfaces and/or abstract classes, they have a high Abstractness (A). Because they are used by other packages but have few (if any) dependencies on other packages, they tend to be very stable. That is to say, they have low Instability (I).

Implementation Packages

At the other end of the spectrum from Interface packages are Implementation packages. These packages are made up of predominately concrete classes that represent the implementation(s) of the various components of the system. The implementation classes are represented by the ellipse in the lower right hand corner of Figure 3. The key is that the classes in these Implementation packages may depend on all of the other packages in the system, but no other packages should depend on them. Because of this, the implementation is free to change without having these changes ripple through the rest of the system.

Low-abstraction packages should depend upon high-abstraction packages.

This fits well with the concept of introducing new implementations to improve maintainability or performance of one portion of the system without having the change ripple through other unrelated portions of the system.

Main Sequence

It would be nice if everything fit neatly into the categories of pure interface or pure implementation, but the real world involves compromises and trade-offs. The Main Sequence, shown by the dashed line in Figure 3, represents the notion that although the forces of Abstractness and Instability for a package may vary, they should vary proportionally to one another. The ellipses around the Main Sequence are intended to show that the JDepend metrics are generalized, versus absolute measures of package architecture quality. JDepend reports D, which is actually the perpendicular distance from the Main Sequence, to simplify the math. This is shown by the d1 and d2 in the figure. There are no absolutes for the value of D, but as its distance from the Main Sequence increases, there is a higher likelihood that the package(s) could benefit from a review or refactoring.

Analyzing the Pet Store

As an example of how you might apply JDepend in a project setting, let's take a look at the results of analyzing the popular Java Pet Store application using JDepend.

I downloaded the 1.3 version of Java Pet Store from the Sun site and extracted the class files from the .jar files into a common directory, C:/petstore. I then ran JDepend using the Swing GUI:

java -cp %CLASSPATH% jdepend.swingui.JDepend C:/petstore

Figure 4 shows the resulting Afferent Dependencies portion of the JDepend GUI.

Figure 4
Figure 4. Afferent dependencies for petstore.controller.ejb

Reviewing the results in the GUI shows the dreaded "cyclic" tag on the end of a number of the packages. The petstore.controller.ejb package has been expanded to show the packages that depend upon it, controller.ejb.actions and controller.web. You can also see that controller.web.actions is the only package that depends upon controller.web. Since we know cyclic dependencies are generally not desirable, let's see if we can identify their causes first.

Cyclic Dependencies

JDepend identifies packages that are cyclically dependent, but it also labels packages that depend on cyclically dependent packages as cyclic. These are the direct and indirect cyclic dependencies we discussed earlier. When refactoring to remove cyclic dependencies, your goal is to remove direct cyclic dependencies. Indirect cyclic dependencies are really just a side effect of the direct cyclic dependencies representing the ripple of the instability through the system. You can use the Swing GUI to identify the type of cyclic dependency your package is participating in.

Referring back to Figure 4, if the petstore.controller.ejb package is part of a direct cyclic dependency, then we would see the package names begin to repeat as we drilled down into the dependency structure. Because we do not see a pattern of repeated package dependencies in the afferent window, we know the package is cyclically dependent because it depends on a package that contains a cycle (indirect cyclic dependency). Figure 5 shows the efferent dependencies for the controller.ejb package.

Figure 5
Figure 5. Efferent dependencies for petstore.controller.ejb

Figure 5 shows that petstore.controller.ejb depends on six packages:

  • cart.ejb
  • customer.ejb
  • servicelocator
  • servicelocator.ejb
  • waf.controller.ejb
  • waf.exceptions

I've expanded the dependencies so you can see them in the display, but you should also note that the Ce metric also tells you there are six packages that this package depends upon. I drilled down further into the waf.controller.ejb package to allow you to see that the cycle is caused by dependencies between the waf.controller.ejb and waf.controller.ejb.action packages. The interface and abstract class composing the waf.controller.ejb.action package are shown below.

// ...waf.controller.ejb.action.EJBAction
// all packages begin with com.sun.j2ee.blueprints

package waf.controller.ejb.action;
import waf.event.Event;
import waf.event.EventResponse;
import waf.controller.ejb.StateMachine;
import waf.event.EventException;

public interface EJBAction  {

  public void init(StateMachine urc);
...
}

// ...waf.controller.ejb.action.EJBActionSupport
// all packages begin with com.sun.j2ee.blueprints

package waf.controller.ejb.action;
import waf.controller.ejb.StateMachine;

public abstract class EJBActionSupport
 implements java.io.Serializable, EJBAction {

  protected StateMachine machine = null;

  public void init(StateMachine machine) {
      this.machine = machine;
  }
...
}

Note that both of these files meet the general criteria for an Interface package. However, both the interface and the abstract class depend on the concrete class com.sun.j2ee.blueprints.waf.controller.ejb.StateMachine. As I mentioned earlier in the article, Interface packages should, if possible, generally avoid depending on concrete Implementation packages. In this case, simply defining an interface, waf.controller.ejb.action.StateMachineIF, for the EJBActionSupport class and EJBAction interface to use in their definitions would correct the cycle. The StateMachine class could then implement the StateMachineIF and the dependencies would only flow into the ejb.action package.

Larger Distance from Main Sequence

Another package that stood out in a quick review of the Pet Store packages is the servicelocator.ejb package, which had the following metrics:

CC: 1   AC: 0   Ca: 12   Ce: 1   A: 0   I: 0.08   D: 0.92

The distance from the main sequence, D, was what brought the package to my attention. Let's look at the story the metrics tell about the package. First, there is only one concrete class in the package (CC = 1) and no abstract classes (AC = 0), so it falls into the general category of an Implementation class. Next, the package is depended upon by 12 other packages (Ca = 12) but only depends on one other package (Ce = 1). Because there are no abstract classes, the Abstractness is 0 (A = AC/(AC+CC)= 0). Because 12 packages depend on this package and it depends on only 1 package, the Instability is 0.08 (I = Ce/(Ce+Ca)).

At this point, you can begin to see several contradictions in the package. The package is heavily depended, so it should have a high degree of abstraction, but it is composed of only one concrete class. The package consists of only one class, so the JDepend metrics are really class metrics in this case. The class is also used by 12 other packages, so changes to the ServiceLocator class will ripple through these packages. The incredible part is that we already know quite a bit about the package and its relationships, even though we have not yet looked at the source. If you are using the JDepend GUI, you can also find out the specific packages this package depends upon and the packages that depend on this package.

From the package name, you may have already guessed that the concrete class contained in the servicelocator.ejb package is an implementation of the ServiceLocator pattern from the Core J2EE Patterns book. The asyncsender.ejb.AsyncSenderEJB implementation shows how the ServiceLocator is used by one of these packages, asyncsender.ejb.

//asyncsender.ejb.AsyncSenderEJB
// all packages begin with com.sun.j2ee.blueprints

package asyncsender.ejb;

import asyncsender.util.JNDINames;
import servicelocator.ejb.ServiceLocator;
import servicelocator.ServiceLocatorException;

public class AsyncSenderEJB implements SessionBean
{
  private SessionContext sc;
  private Queue q;
  private QueueConnectionFactory qFactory;
  ....
  public void ejbCreate( ) throws CreateException
  {
    try {
      ServiceLocator serviceLocator =
        new ServiceLocator();

      qFactory =
        serviceLocator.getQueueConnectionFactory(
         JNDINames.QUEUE_CONNECTION_FACTORY);

      q = serviceLocator.getQueue(
        JNDINames.ASYNC_SENDER_QUEUE);

    }catch (ServiceLocatorException sle) {
      throw new EJBException(
        "AsyncSenderEJB.ejbCreate failed", sle);
    }
  }
  ....
}

You can see that the AsyncSenderEJB class creates and holds a local reference to an instance of the concrete class, ServiceLocator. A more flexible approach would be to define a new interface, servicelocator.ejb.ServiceLocatorIF, that the ServiceLocator class could implement. Because JDepend considers ServiceLocatorIF to be an abstract class, this would improve the Abstractness of the servicelocator.ejb package from 0 to A = AC/(AC+CC) = 1/(1+1) = 0.5. Points D1(original) and D2(modified) in Figure 6 illustrate the impact of this change on this distance from the main sequence for servicelocator.ejb package.

Figure 6
Figure 6. servicelocator.ejb package analysis

Note that while this improves the servicelocator.ejb package's metrics, it does not address the problems of the classes that use the ServiceLocator class. Currently, adding a new type of ServiceLocator, say a CachingServiceLocator, would require modifying all 12 packages (24 files) that use the ServiceLocator. A more flexible approach would be to declare the local variable to be of type ServiceLocatorIF. The creation of the correct type of ServiceLocator could then be abstracted behind a factory method. This is left as an exercise for the reader.

Some Parting Remarks

There is always the tendency to attempt to identify a metric(s) that can be cast in stone as the ultimate proof of good software or architectural quality. I've had great success utilizing the metrics described in this article, on both Java and C++ projects, to identify architecture and implementation hotspots. I've never found a desirable cyclic dependency; however, I have found cases where the cost to fix the cycle was too high. The distance metric, D, also provides a reasonable approach to organizing classes into cohesive packages, but there is not a magical value for D.

Architectural "ilities" are often difficult to quantify in a concrete and repeatable manner. As your team size grows, it becomes impossible to ensure good design qualities are maintained by simply reviewing all the implementations. As you iterate through the design-implement-refactor cycle, it is relatively easy to introduce undesirable dependencies or erode the cohesion of one or more packages. JDepend provided a series of simple, repeatable metrics that allow you to monitor the evolution of your package architecture as part of your normal development process. The ultimate decision as to what to do is still up to the team, but the key is that you now have a simple, repeatable approach for monitoring the impacts of design and implementation decisions on the architecture.

Glen Wilcox has been developing software in various industries for over 15 years.


Return to ONJava.com.