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

advertisement

AddThis Social Bookmark Button

Managing Your Dependencies with JDepend

by Glen Wilcox
01/21/2004

As a developer and architect, I'm always on the lookout for tools that will quickly provide feedback on the quality of software architectures and designs. The problem is that most measures of architectural and design quality tend to be vague qualities — scalability, reliability, maintainability, flexibility, modularity, etc. — that are difficult to measure in a repeatable, quantitative sense.

In this article, I'll introduce you to JDepend, a freely available tool that can provide insight into several qualities of your software architecture. JDepend analyzes the relationships between Java packages using the class files. Since packages represent cohesive building blocks of your architecture, maintaining a well-defined package structure provides insight into architectural qualities of maintainability, flexibility, and modularity. Packages also provide a useful mechanism for estimating the impact of requirements changes, so understanding their dependencies is also useful in this respect as well. Because JDepend's metrics are based on class files, they can be used to track the true state of your architecture at any given point in the software lifecycle.

Finally, I'll use JDepend to analyze Sun's J2EE Java Pet Store to give you an idea of how to utilize JDepend to manage your software development efforts.

A Little OO Background

Before we dive into the specifics of JDepend, I though it would be a good idea to review a few OO design concepts to set the stage for the rest of our discussion. A logical question is: "Why not measure dependencies between classes instead of dependencies between packages"? The problem with using classes is that OO design is predicated upon the notion of autonomous (or semi-autonomous) groupings of state and behavior (classes) collaborating to perform work. So at some level, you want to have classes working together or depending on each other. Unfortunately, the number of classes tends to get large quickly. If you pick an arbitrary maximum size for a class, say 300 lines of code (LOC), building a system of 300,000 LOC will give you a minimum of 1000 classes. Very quickly, you see that managing dependencies at the class level will not work. Packages provide a reasonable alternative. Classes that collaborate closely to perform a specific task are referred to as "cohesive" and they get grouped together into a package. The same technique can be applied to packages that collaborate closely; they form "subsystem" packages. You can now focus on managing the dependencies between the packages, which turns out to be a more realistic goal.

So how do we go about managing dependencies between packages? As a starting point, let us look at the differences between the two primary types of classes: concrete and abstract. In Java, a concrete class is any class that can be directly created using the new operator. The type of these classes is fixed when the code is compiled and at some point in the system, every object is represented by a concrete class. A dependency occurs when one class uses another concrete class within its implementation. This basically makes the statement "my implementation depends on this concrete type." As experience in developing OO systems has evolved, designs that minimize the dependencies on concrete types have proven to be the most flexible. This flexibility is achieved through the use of abstract classes (this includes abstract classes, as well as interfaces in Java). By utilizing abstract classes in your implementation, you allow your class to accept any class that implements the contract defined by the abstract class. You see this pattern at work in the J2SE and J2EE APIs, as well as other design patterns. So managing package dependencies boils down to minimizing your use of concrete classes defined in other packages.

Getting and Using JDepend

JDepend is an open source software program available for download at www.clarkware.com. The download includes the source code, JUnit test cases, documentation, pre-build .jar files, an ANT build script, and a sample application for testing purposes. The online documentation covers all of the features available for setting up, configuring, and running JDepend so I'll just highlight a few of the key features here.

After unzipping the download file, you can verify your download by running JDepend on the sample application provided in the download. Assuming you are in the directory where you unzipped the files, execute the following command:

java -cp ./lib/jdepend.jar jdepend.swingui.JDepend ./sample

This should produce a window similar to the one shown in Figure 1.

Figure 1
Figure 1. JDepend's Swing GUI

This is JDepend's Swing GUI. For a target package, the GUI provides drill-down capability for the packages it depends upon and the packages that depend upon it. The GUI also provides a series of metrics for each package. We will talk more about the metrics reported by the GUI in the following sections. JDepend can also provide output in either text or XML format for integration with other tools or reports within your environment.

Once you understand the JDepend metrics, you may want to automate their collection within your normal build and release cycle. Fortunately, there are optional Ant tasks for doing just that. If you are using Ant 1.5, you can use an XSL stylesheet to transform your JDepend XML output into an HTML report. These reports can be used as a regular part of your quality or metrics program. Turbine and Maven are two projects utilizing this feature.

Understanding JDepend's Metrics Definitions

JDepend computes a series of metrics based on the dependencies between Java packages. These metrics were first identified in Robert Martin's work with C++ (see Designing Object Oriented C++ Applications Using The Booch Method ) and later extended to work with Java packages by Mike Clark. If the definitions in Table 1 are slightly intimidating, don't worry. We will tie them back into some familiar design and architecture concepts in the next section.

Table 1. JDepends metric definitions

  Metric Definition
CC Concrete Classes The number of concrete classes in this package.
AC Abstract Classes The number of abstract classes or interfaces in this package.
Ca Afferent Couplings The number of packages that depend on classes in this package. Answers the question "How will changes to me impact the rest of the project?"
Ce Efferent Couplings The number of other packages that classes in this package depend upon. Answers the question "How sensitive am I to changes in other packages in the project?"
A Abstractness Ratio (0.0-1.0) of Abstract Classes (and interfaces) in this package. AC/(CC+AC)
I Instability Ratio (0.0-1.0) of Efferent Coupling to Total Coupling (Ce/(Ce+Ca)).
D Distance from Main Sequence The perpendicular distance of a package from the idealized line A+I=1. Answers the question "How balanced am I in terms of Abstractness and Instability?" The range of this metric is 0 to 1, with D=0 indicating a package that is coincident with the main sequence (balanced) and D=1 indicating a package that is as far from the main sequence as possible (unbalanced).

In many ways, packages represent an ideal unit for managing architectural qualities of the system. Packages represent groups of classes, so the package definition must accommodate the broader purpose of the classes that it represents. A well-defined package architecture allows the system to be partitioned into major subcomponents, which supports the isolation of concerns and the ability to understand and reason about the architecture in manageable chunks.

The second strength of packages is that their dynamic components, package dependencies, are tied to the implementation of the classes contained within the package. Because of this, the dependencies can be determined automatically and are representative of the true state of the architecture at that given point in time.

While package dependencies allow us to reason about the structure and relationships within our architecture, this ability is diminished by cyclic package dependencies. Figure 2 illustrates the ways in which a package can become cyclically dependent.

Figure 2
Figure 2. Cyclic dependencies

In the simplest form, two packages, A and B, are cyclically dependent if package A depends on package B and package B depends on package A. I refer to this as a direct cyclic dependency because the packages are directly in the cycle. Note that these direct cycles can also span multiple packages: for example, A depends on B, B depends on C, and C depends on A. The second form of cyclic dependency in figure 2 is what I call an indirect cyclic dependency. Package Z doesn't directly participate in a direct cycle, but because it depends on one (or more) packages that do participate in a direct cyclic relationship, it is inherently less stable.

Cyclic dependencies have the following negative consequences for the system:

  • Diminish the ability to reason about components of the architecture in isolation.

  • Changes impact seemingly unrelated components of the architecture. This makes it difficult to accurately assess and manage the impact of changes to the system.

  • Separation of layers. Most architectural approaches recognize the advantages of layered architectures. Cyclic dependencies across layers couple the layers, defeating the purpose of layering.

  • Packages cooperating in a cycle must be released as an atomic unit.

Because of the negative consequences of cyclic package dependencies, it is often better to catch them before they make it into your software baseline. The following code sample demonstrates how to write a JUnit test using JDepend to verify that no cyclic dependencies exist for a particular package.

package com.xyz.ejb;

import java.io.*;
import java.util.*;
import junit.framework.*;
import jdepend.framework.*;

public class CycleTest extends TestCase {
..
  /**  Tests that a single package does not contain
    *  any package dependency cycles.
    */
 public void testOnePackageCycle() {

  JDepend _jdepend = new JDepend();

  _jdepend.addDirectory("/projects/ejb/classes");
  _jdepend.addDirectory("/projects/web/classes");

  _jdepend.analyze();

  JavaPackage p = _jdepend.getPackage("com.xyz.ejb");

  assertNotNull(p);
  assertEquals("Cycle exists: " + p.getName(),
                false, p.containsCycle());
 }
..
}

The CycleTest simply creates a new instance of the jdepend.framework.JDepend class, initializes the instance with the directories containing the classes to be analyzed, and then calls the instance's analyze() method. After that, you just ask the JDepend instance for the JavaPackage in which you are interested. Each JavaPackage contains all of the metrics we just described. In this case, we are only requiring that the package not include any cyclic dependencies, but you can adjust the test as needed for your project.

Now that we have discussed the negative consequences of cyclic dependencies on package design, we can address to other metrics JDepend provides for assessing package design. You can also think about the other metrics in terms of the graphical representation shown in Figure 3.

Figure 3
Figure 3. Graphical view of JDepends metrics

From an architectural perspective, there are two primary categories for packages: Interface packages and Implementation packages.

Pages: 1, 2

Next Pagearrow