11 min read

Documenting Java

Everybody knows the basics of documenting Java, so we won’t go into much detail. We’ll talk a bit about ways of writing code whose intention is clear, mention some Javadoc tricks we can use, and highlight some tools that can help keep our code clean. Clean code is one of the most important ways we can document our application. Anything we can do to increase readability will reduce confusion later (including our own).

Self-documenting code

We’ve all heard the myth of self-documenting code. In theory, code is always clear enough to be easily understood. In reality, this isn’t always the case. However, we should try to write code that is as self-documenting as possible.

Keeping non-code artifacts in sync with the actual code is difficult. The only artifact that survives a project is the executable, which is created from code, not comments. This is one of the reasons for writing self-documenting code. (Annotations, XDoclet, and so on, make that somewhat less true.)

There are little things we can do throughout our code to make our code read as much like our intent as possible and make extraneous comments just that: extraneous.

Document why, not what

Over-commenting wastes everybody’s time. Time is wasted in writing a comment, reading it, keeping that comment in sync with the code, and, most importantly, a lot of time is wasted when a comment is not accurate.

Ever seen this?

a += 1; // increment a

This is the most useless comment in the world.

Firstly, it’s really obvious we’re incrementing something, regardless of what that something is. If the person reading our code doesn’t know what += is, then we have more serious problems than them not knowing that we’re incrementing, say, an array index.

Secondly, if a is an array index, we should probably use either a more common array index or make it obvious that it’s an array index. Using i and j is common for array indices, while idx or index is less common. It may make sense to be very explicit in variable naming under some circumstances. Generally, it’s nice to avoid names such as indexOfOuterArrayOfFoobars. However, with a large loop body it might make sense to use something such as num or currentIndex, depending on the circumstances.

With Java 1.5 and its support for collection iteration, it’s often possible to do away with the index altogether, but not always.

Make your code read like the problem

Buzzphrases like Domain Specific Languages (DSLs) and Fluent Interfaces are often heard when discussing how to make our code look like our problem. We don’t necessarily hear about them as much in the Java world because other languages support their creation in more “literate” ways. The recent interest in Ruby, Groovy, Scala, and other dynamic languages have brought the concept back into the mainstream.

A DSL, in essence, is a computer language targeted at a very specific problem. Java is an example of a general-purpose language. YACC and regular expressions are examples of DSLs that are targeted at creating parsers and recognizing strings of interest respectively.

DSLs may be external, where the implementing language processes the DSL appropriately, as well as internal, where the DSL is written in the implementing language itself. An internal DSL can also be thought of as an API or library, but one that reads more like a “little language”.

Fluent interfaces are slightly more difficult to define, but can be thought of as an internal DSL that “flows” when read aloud. This is a very informal definition, but will work for our purposes

Java can actually be downright hostile to some common DSL and fluent techniques for various reasons, including the expectations of the JavaBean specification. However, it’s still possible to use some of the techniques to good effect. One typical practice of fluent API techniques is simply returning the object instance in object methods. For example, following the JavaBean specification, an object will have a setter for the object’s properties. For example, a User class might include the following:

public class User {
private String fname;
private String lname;
public void setFname(String fname) { this.fname = fname; }
public void setLname(String lname) { this.lname = lname; }
}

Using the class is as simple as we’d expect it to be:

User u = new User();
u.setFname("James");
u.setLname("Gosling");

Naturally, we might also supply a constructor that accepts the same parameters. However, it’s easy to think of a class that has many properties making a full constructor impractical. It also seems like the code is a bit wordy, but we’re used to this in Java. Another way of creating the same functionality is to include setter methods that return the current instance. If we want to maintain JavaBean compatibility, and there are reasons to do so, we would still need to include normal setters, but can still include “fluent” setters as shown here:

public User fname(String fname) {
this.fname = fname;
return this;
}
public User lname(String lname) {
this.lname = lname;
return this;
}

This creates (what some people believe is) more readable code. It’s certainly shorter:

User u = new User().fname("James").lname("Gosling");

There is one potential “gotcha” with this technique. Moving initialization into methods has the potential to create an object in an invalid state. Depending on the object this may not always be a usable solution for object initialization.

Users of Hibernate will recognize the “fluent” style, where method chaining is used to create criteria. Joshua Flanagan wrote a fluent regular expression interface, turning regular expressions (already a domain-specific language) into a series of chained method calls:

Regex socialSecurityNumberCheck =
new Regex(Pattern.With.AtBeginning
.Digit.Repeat.Exactly(3)
.Literal("-").Repeat.Optional
.Digit.Repeat.Exactly(2)
.Literal("-").Repeat.Optional
.Digit.Repeat.Exactly(4)
.AtEnd);

Whether or not this particular usage is an improvement is debatable, but it’s certainly easier to read for the non-regex folks.

Ultimately, the use of fluent interfaces can increase readability (by quite a bit in most cases), may introduce some extra work (or completely duplicate work, like in the case of setters, but code generation and/or IDE support can help mitigate that), and may occasionally be more verbose (but with the benefit of enhanced clarity and IDE completion support).

Contract-oriented programming

Aspect-oriented programming (AOP) is a way of encapsulating cross-cutting functionality outside of the mainline code. That’s a mouthful, but essentially it means is that we can remove common code that is found across our application and consolidate it in one place. The canonical examples are logging and transactions, but AOP can be used in other ways as well.

Design by Contract (DbC) is a software methodology that states our interfaces should define and enforce precise specifications regarding operation.

“Design by Contract” is a registered trademark of Interactive Software Engineering Inc. Other terms include Programming by Contract (PbC) or Contract Oriented Programming (COP).

How does COP help create self-documenting code? Consider the following portion of a stack implementation:

public void push(final Object o) {
stack.add(o);
}

What happens if we attempt to push a null? Let’s assume that for this implementation, we don’t want to allow pushing a null onto the stack.

/**
* Pushes non-null objects on to stack.
*/
public void push(final Object o) {
if (o == null) return;
stack.add(o);
}

Once again, this is simple enough. We’ll add the comment to the Javadocs stating that null objects will not be pushed (and that the call will fail/return silently). This will become the “contract” of the push method—captured in code and documented in Javadocs.

The contract is specified twice—once in the code (the ultimate arbiter) and again in the documentation. However, the user of the class does not have proof that the underlying implementation actually honors that contract. There’s no guarantee that if we pass in a null, it will return silently without pushing anything.

The implied contract can change. We might decide to allow pushing nulls. We might throw an IllegalArgumentException or a NullPointerException on a null argument. We’re not required to add a throwsclause to the method declaration when throwing runtime exceptions. This means further information may be lost in both the code and the documentation.

Eiffel has language-level support for COP with the require/do/ensure/end construct. It goes beyond the simple null check in the above code. It actively encourages detailed pre- and post-condition contracts. An implementation’s push() method might check the remaining stack capacity before pushing. It might throw exceptions for specific conditions. In pseudo-Eiffel, we’d represent the push() method in the following way:

push (o: Object)
require
o /= null
do
-- push
end

A stack also has an implied contract. We assume (sometimes naively) that once we call the push method, the stack will contain whatever we pushed. The size of the stack will have increased by one, or whatever other conditions our stack implementation requires.

Java, of course, doesn’t have built-in contracts. However, it does contain a mechanism that can be used to get some of the benefits for a conceptually-simple price. The mechanism is not as complete, or as integrated, as Eiffel’s version. However, it removes contract enforcement from the mainline code, and provides a way for both sides of the software to specify, accept, and document the contracts themselves.

Removing the contract information from the mainline code keeps the implementation clean and makes the implementation code easier to understand. Having programmatic access to the contract means that the contract could be documented automatically rather than having to maintain a disconnected chunk of Javadoc.

SpringContracts

SpringContracts is a beta-level Java COP implementation based on Spring’s AOP facilities, using annotations to state pre- and post-contract conditions. It formalizes the nature of a contract, which can ease development.

Let’s consider our VowelDecider that was developed through TDD. We can also use COP to express its contract (particularly the entry condition). This is a method that doesn’t alter state, so post conditions don’t apply here.

Our implementation of VowelDecider ended up looking (more or less) like this:

public boolean decide(final Object o) throws Exception {
if ((o == null) || (!(o instanceof String))) {
throw new IllegalArgumentException(
"Argument must be a non-null String.");
}
String s = (String) o;
return s.matches(".*[aeiouy]+.*");
}

Once we remove the original contract enforcement code, which was mixed with the mainline code, our SpringContracts @Precondition annotation looks like the following:

@Precondition(condition="arg1 != null && arg1.class.name == 'java.
lang.String'",
message="Argument must be a non-null String")
public boolean decide(Object o) throws Exception {
String s = (String) o;
return s.matches(".*[aeiouy]+.*");
}

The pre-condition is that the argument must not be null and must be (precisely) a string. (Because of SpringContracts’ Expression Language, we can’t just say instanceof String in case we want to allow string subclasses.)

We can unit-test this class in the same way we tested the TDD version. In fact, we can copy the tests directly. Running them should trigger test failures on the null and non-string argument tests, as we originally expected an IllegalArgumentException. We’ll now get a contract violation exception from SpringContracts.

One difference here is that we need to initialize the Spring context in our test. One way to do this is with JUnit’s @BeforeClass annotation, along with a method that loads the Spring configuration file from the classpath and instantiates the decider as a Spring bean. Our class setup now looks like this:

@BeforeClass public static void setup() {
appContext = new ClassPathXmlApplicationContext(
"/com/packt/s2wad/applicationContext.xml");
decider = (VowelDecider)
appContext.getBean("vowelDecider");
}

We also need to configure SpringContracts in our Spring configuration file. Those unfamiliar with Spring’s (or AspectJ’s) AOP will be a bit confused. However, in the end, it’s reasonably straightforward, with a potential “gotcha” regarding how Spring does proxying.

<aop:aspectj-autoproxy proxy-target-class="true"/>
<aop:config>
<aop:aspect ref="contractValidationAspect">
<aop:pointcut id="contractValidatingMethods"
expression="execution(*
com.packt.s2wad.example.CopVowelDecider.*(..))"/>
<aop:around pointcut-ref="contractValidatingMethods"
method="validateMethodCall"/>
</aop:aspect>
</aop:config>
<bean id="contractValidationAspect"
class="org.springcontracts.dbc.interceptor.
ContractValidationInterceptor"/>
<bean id="vowelDecider"
class="com.packt.s2wad.example.CopVowelDecider" />

The SpringContracts documentation goes into it a bit more and the Spring documentation contains a wealth of information regarding how AOP works in Spring. The main difference between this and the simplest AOP setup is that our autoproxy target must be a class, which requires CGLib. This could also potentially affect operation.

The only other modification is to change the exception we’re expecting to SpringContract’s ContractViolationCollectionException, and our test starts passing. These pre- and post-condition annotations use the @Documented meta-annotation, so the SpringContracts COP annotations will appear in the Javadocs. It would also be possible to use various other means to extract and document contract information.

Getting into details

This mechanism, or its implementation, may not be a good fit for every situation. Runtime performance is a potential issue. As it’s just some Spring magic, it can be turned off by a simple configuration change. However, if we do, we’ll lose the value of the on-all-the-time contract management.

On the other hand, under certain circumstances, it may be enough to say that once the contracts are consistently honored under all of the test conditions, the system is correct enough to run without them. This view holds the contracts more as an acceptance test, rather than as run-time checking. Indeed, there is an overlap between COP and unit testing as the way to keep code honest. As unit tests aren’t run all the time, it may be reasonable to use COP as a temporary runtime unit test or acceptance test.

LEAVE A REPLY

Please enter your comment!
Please enter your name here