Some things make development fun, some things are honoured by your professional environment. Things that provide for both are the most pleasant - literate programming is one!
Literate programming is a concept of programming which states that a program should have readability as a primary goal,
thus making programs works of literature.
Initially this embraced adding documentation to source code like with JavaDoc and has been adopted by many programming languages, e.g. with Doxygen. (For Historians: The original idea was stated by Donald Knuth in some former century and prototyped by his WEB environment, using Pascal as programming language and TeX for documentation.)
Since we all love to write documentation, people have found concepts of literate programming with a much higher coolness factor:
Designing program APIs to form human-readable statements.
A simple example everyone is supposed to be familiar with, is the StringBuffer/-Builder, e.g.
buffer.append("Hello ").append(user);
Great examples of complete APIs are EasyMock, JUnit 4 and commons-lang. For example, JUnit 4 allows you to write fancy test code like this (see the JUnit 4.4 release notes):
assertThat(answer, either(containsString("color")).or(containsString("colour")));
…do these statements really need documentation?
From the purpose of the mentioned libraries, you get the impression that especially dull tasks inpire people to ‘play’ with such ideas. I assume this is true since:
- building objects and testing software is complex and strongly repetitive
- repetitive tasks invite for facilitation by abstractions or at least abbreviations
- your grandma now gets an idea of what you are coding. Even better: you still understand your code when revisiting it six months later!
- when doing dull tasks, you are searching for things to have fun with
The latest statement is funny but exhibits a deeper truth: Making dull tasks a fun is infectuous and you can’t stop it. I will never be able to write stupid test cases again.
In each business or technical discipline you have common concepts to test that can be abstracted and ‘literated’.
An example from my work: While developing benerator, an open source framework for test data generation, I need to test data sequences that are generated in subsequent calls to a generator object. A naive test implementation looked like this:
assertTrue(generator.available());
assertEquals(1L, generator.generate());
assertTrue(generator.available());
assertEquals(3L, generator.generate());
assertTrue(generator.available());
assertEquals(2L, generator.generate());
assertFalse(generator.available());
With a literate API I could reduce this to
expectSequence(generator, 1L, 3L, 2L).withCeasedAvailability();
Much shorter, much quicker to write and understand, yet easier to maintain! It is even supported by code completion. I love it!
If you now state: “I code like a real man - sissies don’t need to understand my code”,you are headed for trouble with your boss and collegues, but I have a relief: Delving into the skills needed to design and implement such APIs you will find a wide playground to unleash your code monsters, yet making users of your code happy:
For implementing such behaviour you need to create
- a parent class of your test case (or a utility class from which you import statically)
- one or more helper classes for invocation chaining
The parent class provides the methods initially called (like expectSequence()), the helper classes provide for invocation chaining. These helper classes are your monster’s playground and will scare any sissy at first blush:
public class GeneratorTestCase extends TestCase {
// snip...
protected static <T> Helper expectGenerations(
Generator<T> generator, T ... products) {
for (T expectedProduct : products) {
assertTrue(generator.available);
assertEquals(expectedProduct, generator.generate());
}
return new Helper(generator);
}
public static class Helper {
private Generator generator;
public Helper(Generator generator) {
this.generator = generator;
}
public Helper withCeasedAvailability() {
assertFalse("Generator is expected to be unavailable: " +
generator, generator.available());
try {
generator.generate();
fail("Expectected IllegalGeneratorStateException");
} catch (IllegalGeneratorStateException e) {
// exception is required
}
}
// snip...
}
}
Reading through the code, you will easily understand the expectGenerations() method: It iterates through a varargs parameter verifying generator invocation results for each. Finally, it returns a Helper instance that enables you to add another .method() call to the expectGenerations() command. This needs to act on one or more of the objects processed by the first method call. Thus, the concerned objects need to be internally submitted to the helper when constructing it.
You can continue the concept iterativly or recursively: Iteratively, by returning the Helper from the method withCeasedAvailability() as shown in the example, or recursively by returning further helper classes that provide features dependent of the sematic that is implied by the current Helper method call.
Once you have grasped the concept, it is supposed to change your development life. When you get the taste of it, it will be hard to get back.
Now go on, infect others with the literate-programming-virus, have fun doing QA and don’t forget to let grandma check your code!