Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 4 of 5
Even if you cannot create a class in this ideal form, you should still strive to create classes with all final fields—at least you'll know each field's reference cannot change after construction, eliminating numerous test cases. When accessor methods are necessary, return a copy of the member data (unless the data is already immutable) so a client cannot modify your class's private data. Here's an example of one such mutable class we developed:
public class ActivationDefinitionBuilder {
// A List of ActivationTask objects
private final List activationTasks;
public ActivationDefinitionBuilder() {
this.activationTasks = new ArrayList();
}
public ActivationDefinition getActivationDefintion() {
// ActivationTasks are immutable, so ArrayList's shallow clone is sufficient
return new ConcreteActivationDefinition(this.activationTasks.clone());
}
public void addActivationTask(ActivationTask activationTask) {
this.activationTasks.add(activationTask);
}
}
This class has a single final field, which is mutable (the List can be modified). The getActivationDefinition() method passes a copy of the list to the ConcreteActivationDefinition constructor to prevent that class from modifying ActivationDefinitionBuilder's private data. The getActivationDefinition() method does not need to perform a deep copy of the list, since the ActivationTask type (not shown) is immutable. This reinforces the point mentioned earlier—the client code (in this case, ActivationDefinitionBuilder) is simpler and easier to test because ActivationTask is immutable.
In our experience, nearly all classes can be written in the "ideal" fashion. On a recent project, we created roughly 200 new
production classes, and 95 percent had only final fields to immutable objects. The classes that did not meet this criteria
were either builder classes, such as the one seen above, or classes representing a physical connection to a device (with open() and close() methods). Of the 200 new classes, 99 percent had only final fields. Little conscious effort was required for us to create
so many classes of this form. Once we discovered how immutability eases the testing burden, creating immutable or nearly immutable
classes whenever possible became natural.
We've found pair programming and TDD to be mutually complementary. Before diving into pair programming, we should clarify our application of TDD—for us, TDD means test-first development. We always write the test prior to writing the production code required to make the test pass. Writing tests first drives our code's development and design. Our attempts at test-last development were not successful for reasons we'll discuss shortly.
In our experience, doing TDD for the first time is hard. We frequently got into situations where we were unsure what to do next. Often the person at the keyboard would revert back to old behaviors and implement functionality prior to a test requiring it. Even after having practiced TDD for months, we still experience the temptation to write code prior to writing the unit test. Having a back-seat driver, as it were, to help keep us focused on TDD is a huge benefit to us. For example, the back-seat driver might ask, "What test should we write next?" "What are this object's responsibilities?" or "What are we trying to test?" When applying TDD, you must carefully consider these questions, which are easy to lose track of when applying TDD for the first or even tenth time. We're convinced that our success in employing TDD is in large part due to our pairing.
Archived Discussions (Read only)