Suppose you've just joined a project that is building a banking application. Browsing the code, you find the following interface for a (simplistic) BankAccount:
interface BankAccount {
float getBalance();
float deposit(float amount);
float withdraw(float amount);
...
}
|
While succinct, the above interface leaves a lot of questions
unanswered. Can the amount argument to deposit() or
withdraw() be negative
or zero? Are negative balances (overdrafts) allowed? What happens in
deposit() or withdrawal() if an invalid amount is specified?
Clearly, it's important to be able to answer these questions, both for implementers of the interface and for clients of the components that expose the interface. One way to specify the behavior implicitly is to use unit tests written with JUnit (see Resources). With JUnit tests, you can invoke the methods with various legal and illegal arguments and make assertions that the expected resulting behavior occurs. Another approach is Design by Contract, a proven technique for clarifying component design details.
In this final article in the AOP@Work series, I introduce you to Contract4J, an AspectJ-based tool that supports Design by Contract. I show you how to use Contract4J to implicitly specify component behavior, document proper component usage for clients, and programmatically test for compliance. At the end of the article, I discuss how Contract4J fits into emerging trends in aspect-oriented design.
Overview of Design by Contract
With Design by Contract, you can use programmatic expressions to specify the requirements on inputs to a component, as well as on the returned results. The expressions are evaluated at run time during developer and QA testing, and if a test fails, program execution instantly terminates. A termination comes with useful diagnostic information, forcing you to fix the bug immediately.
It may seem onerous to force termination immediately. Why not just issue an error message and keep going? While carrying on seems more productive, it actually isn't. First, if you're not forced to deal with the bug immediately, you're likely to put off fixing it, so bugs tend to accumulate. Second, a failed test should indicate that something unexpected happened (for example, a reference is null) and normal execution can't continue. You could put in "contingency handling" code, but that would complicate the implementation for a condition that should never occur, thereby increasing the complexity of the code and the risk of more bugs.
Design by contract is a tool for finding and fixing logic bugs in code. It does not address other issues such as performance, user input mistakes, etc. Design by contract uses three types of tests to specify and ensure component behavior:
- Tests on component inputs (such as parameters passed to methods) are called precondition tests. They specify conditions that the component requires to be true to perform the requested operation. The client must meet these requirements to use the component.
- Postcondition tests are guarantees that the component promises to satisfy when it completes the operation, assuming the preconditions are met. Postconditions are often expressed as assertions about method return values.
- Finally, invariant tests assert conditions that never change. A class invariant must hold before and after all method calls (once the object is constructed). A method invariant must hold before and after the method executes; a field invariant must be true for a field for the life of the object.
Note that Design by Contract tests can be turned off in production deployments to remove their overhead.
Unit testing and Design by Contract
Design by Contract has some advantages over unit testing, but the two methods are complementary. One of the major benefits of Design by Contract is that it provides explicit information within the interface or class itself about expected behavior. The result is essential documentation for component developers and clients in a format that can be programmatically tested. Design by Contract also makes explicit the contract definition that is more implicit in unit tests. I tend to use the two techniques in tandem, especially when working with technologies that are difficult to unit test, like EJB2 beans. As you'll soon see, Contract4J has the added feature of enforceable usage constraints, which is a considerable advantage over the informal documentation that is implicit in JUnit tests.
Contract4J is an open source developer tool that implements design by contract using Java 5 annotations (see Resources). Behind the scenes, it uses aspects to insert "advice" at the program join points (for example, calls to methods) where the tests should be evaluated, and it handles any failure of those tests by terminating program execution.
Consider the BankAccount interface again, but this time with Contract4J annotations. Note that I've highlighted the original code in bold and some strings have been wrapped to fit the column and to aid clarity:
Listing 1. BankAccount with Contract4J annotations
@Contract
@Invar("$this.balance > = 0.0")
interface BankAccount {
@Post("$return >= 0.0")
float getBalance();
@Pre("amount >= 0.0")
@Post("$this.balance == $old($this.balance)+amount
&& $return == $this.balance")
float deposit(float amount);
@Pre("amount >= 0.0 &&
$this.balance -- amount >= 0.0")
@Post("$this.balance == $old($this.balance)-amount
&& $return == $this.balance")
float withdraw(float amount);
...
}
|
Table 1 defines the keywords you see Listing 1:
Table 1. A sample of Contract4J keywords
| Keyword | Definition |
|---|---|
| $this | The object under test. |
| $target | Currently used only for fields in field-invariant tests (you can also refer to fields using $this.field_name). Future use may extend $target to other contexts. |
| $return | The object (or primitive value) returned by a method. Valid only in postcondition tests. |
| $args[n] | The nth parameter passed to a method, counting from 0. The parameters can also be referred to by name. |
| $old | The "old" value (before a join point is actually
executed) of the contents within the parentheses. Valid only
for invariant and postcondition tests. Because the Java language doesn't
require that all classes support "cloning," Contract4J
cannot know whether it can clone a particular object.
Therefore, expressions within $old(...) should contain only
primitive values or objects that won't change. Otherwise,
the "old" value may change when the join point executes,
possibly yielding unexpected results. Example expressions
include $old("$this.userName") and
$old("$this.calcMin(x,y)"). The Contract4J
documentation describes the allowed expressions in
detail. |
Based on what you learned in the previous section, the annotations in
Listing1 shouldn't be mysterious to you. The @Contract annotations indicate that the interface (or class) has a contract specification. The @Pre, @Post, and @Invar annotations define precondition, postcondition, and invariant tests, respectively. You'll also note that the tests in Listing 1 are Java expressions defined as strings, evaluating to true or false.
If you omit the test expression, Contract4J uses reasonable defaults, depending on the context. For example, the default invariant condition for a field requires the field to be non-null. Similarly, the default method precondition requires all input arguments that aren't primitives to be non-null, and the default method postcondition requires the return value to be non-null.
The contract specification for BankAccount includes a class-wide invariant that the balance must always be greater than or equal to zero (sorry, no overdrafts allowed!). The getBalance() method has a postcondition that it must always return a value greater than or equal to 0. Notice that the invariant and other tests refer to an implied balance field, even though an interface can't define fields. However, because the interface does define a corresponding JavaBean accessor method, Contract4J infers the existence of the field. Note that the postcondition test on getBalance() may seem redundant with the class invariant, but it partially tests the assumption that this method actually returns the balance.
The contract also has preconditions that require clients to pass in an amount greater than or equal to zero to both withdraw() and deposit(). withdraw() has the additional precondition requirement that the amount can't exceed the existing balance. Finally, withdraw() and deposit() have similar postcondition requirements; the value returned must equal the new balance and the new balance must equal the old (original) balance minus or plus the input amount, respectively.
The README in the Contract4J distribution (see below) discusses its syntax in great detail, including known limitations and idiosyncrasies. Contract4J's own unit tests in the distribution provide extensive examples of valid, as well as invalid, test expressions.
You can also write contract tests on classes or aspects, where you can define tests on constructors and invariant conditions on instance fields. (The class invariant above was effectively a field-invariant specification!) Methods and constructors can also have invariant tests.
Because the contract affects discrete points of execution as seen by component users and subclasses, Contract4J treats the public, protected, and package-visible methods as "atomic." This means that a test can be violated temporarily during the method, as long as the conditions are satisfied when it completes. Note that nested calls to other methods or fields with tests will trigger those tests, with some special-case exceptions to prevent infinite recursions in aspect code, etc. Also, Contract4J currently disallows defining tests on private methods because they are not visible to external clients. Tests on static methods also are not supported because they don't affect object state. However, these two "theoretical" restrictions may be eliminated in a future release.
Finally, field-invariant tests are only evaluated after field reads and writes, to permit lazy evaluation. Similarly, field-invariant tests are never evaluated during object construction, but they are evaluated after construction completes.
You don't really need Contract4J to write Design by Contract tests. You could just write your own aspects (as discussed in a previous article in this series; see Resources). The advantage of Contract4J is that developers with no AspectJ experience can use it; only a straightforward build process change is required, which I discuss below. Contract4J also provides a very succinct way of defining tests, using familiar Java constructs, without having to define lots of additional AspectJ "boilerplate." Not only are the contracts executable, they are also part of the user-visible code and documentation, information that would be more obscure if captured in separate aspects.
As previously noted, it is true that unit testing and Design by Contract accomplish similar objectives by different means. A Design by Contract tool like Contract4J is most helpful in situations where unit testing is sparse or difficult. Integration and burn-in tests are a good place to capture those obscure integration problems often missed by lower-level testing. Whether or not you use Contract4J with your unit tests, thinking about the contract of your components will improve your designs.
At run time, Contract4J uses built-in aspects to advise the join points where the tests should be executed. The pointcuts in these aspects look for the appropriate annotations. Precondition tests are handled by before advice, which is executed just before the corresponding method execution join point. The before advice uses the Apache Jakarta Commons JEXL interpreter to convert the special keywords in the test strings to the appropriate objects and to evaluate the resulting expressions, returning true (pass) or false (fail). If a test fails, an error message is reported, indicating the point of failure, and program execution halts.
In Listing 1, for example, if withdraw() is called, just before executing the method, Contract4J evaluates the expression amount >= 0, with the input value for amount. If amount = -1.0, for example, then the test fails, an error reporting the location of the failure with a stack trace is reported, and the application exits.
Similarly, postcondition tests correspond roughly to after advice. However, to support the $old keyword, around advice is actually used, where the "subexpressions" in the $old keywords are evaluated, the results are saved, the original join point is executed, and the full test expression is then evaluated with the "old" values inserted appropriately.
Finally, invariant tests use around advice, where the tests are evaluated both before and after join point execution with the exceptions noted previously.
Invoking an interpreter like JEXL does add nontrivial overhead, but because Contract4J is designed for use only during development and testing, the overhead is not a serious issue most of the time. However, you may find that some frequently executed code blocks should not have tests.
Because Contract4J's contract tests are written using familiar Java conventions, adoption into a Java environment is straightforward, consisting of four steps:
- Download Contract4J and unzip or untar it somewhere convenient. Unless you want to rebuild it (following the instructions in the included README), all you need is the contract4j5.jar file.
- Add the location of the contract4j5.jar file to your build CLASSPATH.
- Download and install AspectJ.
- Switch from your current Java compiler to the AspectJ "ajc" compiler, which also compiles Java code. Details are provided on the AspectJ home page and the Ant scripts that come with the distribution. Or, if you prefer to continue using your Java compiler, you have two additional options:
- You can introduce an ajc "weaving" step at the end of the build that weaves the aspects from contract4j5.jar into your precompiled classes or JARs.
- You can "weave" the contracts at load time, as explained in the AspectJ documentation.
- You can introduce an ajc "weaving" step at the end of the build that weaves the aspects from contract4j5.jar into your precompiled classes or JARs.
- Start adding the Contract4J annotations to your source code to define your contracts!
Using property files or API calls at run time, you can enable or disable all tests, just precondition tests, just postcondition tests, or just invariant tests. Normally, for production deployments, you will build without the contract4j5.jar so that you incur no run-time overhead.
Extensive customization is possible using API calls, including "plug-in hooks" for inserting your own Java classes that implement different behaviors. You can even replace the JEXL expression interpreter.
The Contract4J home page (see Resources) provides extensive documentation on the API and other customization options, as well as allowed test expressions, known limitations, and idiosyncrasies. You can also build the "ant docs" target in the distribution to generate complete Javadocs.
Besides being a useful tool for developers, Contract4J is interesting for two other reasons. First, it is one of a growing number of Java development tools that use aspects under the hood, more or less transparently to the developer. Another example is the Glassbox Inspector discussed previously in this series. Additionally, the Spring framework uses pure-Java and AspectJ aspects extensively to support middleware services, and JBoss uses pure-Java aspects for the same purpose. See Resources to learn more about all three.
Second, Contract4J uses a simple interface-based approach to aspect design. Many in the aspect-oriented community are currently working to extend the concepts of interface-based design from the object world to the emerging aspect/object world, so this topic bears further discussion.
Annotations are often used to indicate metainformation about code. In this case, Contract4J uses annotations to capture the contractual constraints of the component, which I have argued form an essential part of the interface, not something "peripheral" to it. In fact, the contract isn't really "crosscutting" in the usual AOP sense of being part of a problem domain that is "orthogonal" to the primary problem domain of the component. In the BankAccount example, the allowed values for an account balance, part of the account object's "state," are integral to the account object, not orthogonal to it.
Therefore, strictly speaking, it may appear that Design by Contract isn't an appropriate candidate for an AOP approach at all. However, while the contract itself is integral to the BankAccount's domain, how that information gets used is crosscutting. Contract4J is interested in enforcing the contract programmatically and the Contract4J annotations were designed to support this goal. However, the contract information exposed through annotations could just as easily be used in a tool that generates unit tests automatically or in an IDE that warns the user if the component is being used incorrectly.
Using annotations, Contract4J defines a pattern-like protocol, a kind-of interface, for expressing contract information. In this way, it resembles the Observer pattern, where the contract specification, in the form of annotations, is made "observable" and can be acted upon by tools. Expressed in a form of structured English, the protocol says, for the example of method postconditions:
if @Contract is on a class and @Post is on a method, then get the @Post expression string and evaluate it, aborting if false. |
The Contract4J aspects implement this logic in AspectJ. The aspects require no explicit information about the classes under test, such as their names or method names. All they care about are the annotations. Hence, they remain completely generic and reusable. Contrast this with many typical AspectJ aspects that are written with explicit references to particular packages and classes, thereby making reuse and evolution more difficult.
A similar example of using annotations to convey metainformation is the sets of annotations defined for use with Hibernate and EJB3 for expressing persistence requirements in POJOs (see Resources).
Challenges of aspect interfaces
Of course, annotation-based metainformation can only go so far as an aspect design approach. As with object design, we should expect that creating reusable, loosely coupled, aspect-based systems and reusable, generic aspect libraries will require a comprehensive approach based on interfaces. Interfaces define appropriate abstractions for coupling components without exposing too many details. Hence, interfaces tend to be more stable than the underlying components as the software evolves.
As for aspects, they pose a unique challenge. By their nature, aspects usually touch the rest of the system pervasively, whereas object components are more "localized." Aspects also have the ability to modify object state and behavior in new, more fine-grained ways, raising worries about maintaining system integrity, robustness, and even comprehension.
To address these issues, researchers and practitioners are finding that interfaces for aspect/object systems should not only contain the methods and state information that we're accustomed to, but also contractual information that constrains the modifications aspects are allowed to make to other components (sound familiar?). For example, an aspect may not be allowed to make state changes to the component or it may be restricted from advising certain "critical regions" for performance or security reasons.
So, instead of aspect pointcuts coupling directly to component details, like particular naming conventions, aspects will instead couple to interfaces that are implemented by the components. Components will use the interfaces to expose allowed join points and state information of interest to any aspect(s). The aspects will advise components only through the exposed interfaces. Because interfaces tend to be more stable than the components that implement them, the coupling will be more stable and resilient to change as the system evolves. Just as for object systems, interface-based programming will make it far easier for designers to build robust, non-trivial, yet reusable aspect/object systems.
Contract4J makes it efficient and straightforward to define Design by Contract tests in an intuitive manner, using Java 5 annotations. These tests are evaluated automatically during testing, where they help catch logic bugs in your code. Contract4J leverages the power of AspectJ without requiring the developer to be an expert in using AspectJ.
Learn
- The Challenges of Writing Portable and Reusable Aspects in AspectJ: Lessons from Contract4J (Dean Wampler, AOSD 2006, ACM): The author discusses Contract4J as an exercise in writing truly reusable, generic aspects. Contact Dean Wampler for a copy.
- Modular Software Design with Crosscutting Interfaces (William G. Griswold, Macneil Shonle, Kevin Sullivan, Yuanyuan Song, Nishit Tewari, Yuanfang Cai, Hridesh Rajan; IEEE Software, January/February 2006): An excellent recent summary of promising work on aspect interfaces.
- "Contract enforcement with AOP" (Filippo Diotalev, developerWorks, July 2004): An introduction to hand-coding Design by Contract aspects.
- "AOP@Work: Unit test your aspects" (Nicholas Lesiecki, developerWorks, November 2005): A catalog of patterns for testing crosscutting behavior in AspectJ.
- "AOP@Work: Performance monitoring with AspectJ, Part 1" (Ron Bodkin, developerWorks, September 2005): Learn more about the Glassbox Inspector.
- AspectJ home page: Learn more about using aspects for Design by Contract testing.
- Object-Oriented Software Construction, Second Edition (Bertrand Meyer, Prentice Hall, 1997): Includes the inventor's own introduction to Design by Contract.
- AspectJ in Action (Ramnivas Laddad, Manning 2003): Discusses policy enforcement using aspects and the Observer pattern.
- The Java technology zone: Hundreds of articles about every aspect of Java programming.
Get products and technologies
- Contract4J home page: For
download links and more details on using Contract4J.
- AspectJ home page: Download the
latest release and get information about AspectJ and AJDT.
- JUnit: The most popular unit testing framework for the Java platform.
- Barter: An XDoclet-based precursor
to Contract4J that also used AspectJ under the hood.
- JEXL interpreter: Find it on the Jakarta home page.
- Spring and JBoss: More Java-based
technologies that incorporate aspects.
Discuss
- developerWorks
blogs: Get involved in the developerWorks community.
Dean Wampler provides AspectJ, Enterprise Java, and Ruby on Rails consulting through his company Aspect Research Associates. He is the founder of the Contract4J open source project.
Comments (Undergoing maintenance)





