The Coad Letter: Test-driven Development, Issue 106, Intentional Coding, by Dave Astels

By: Coad Letter Test Driven Development Editor

Abstract: In this issue, we will be talking about a skill that is generally useful, indispensable for doing Test-Driven Development, and a requirement to really do XP. I'm referring to intentional coding...

1. Introduction

The main idea of intentional coding is communication, specifically the communication of our intent to whoever may be reading the code. The goal is to enable them to understand what we had in mind when we wrote the code. Have you ever had to work on a piece of code and found that it was hard to understand? Maybe the logic was convoluted, the identifiers meaningless or worse, misleading. You say there was documentation? Was it readable? Did it make sense? Was it up to date with the code? Are you sure? How do you know?

So, you can see that there are many ways for the communication of our intent to be less than ideal. Let's look at what we need to do in order to have our code be understandable, clearly communicating our intent.

2. Naming

By naming I mean the the choosing of identifiers to name the various classes, variables, methods, etc. that we create as we work. We need to choose names that are semantically transparent, i.e. they say what they mean and mean what they say.

We can use whatever word/name we like to refer to a thing, but if nobody else associates that name with that thing, then we must take time and effort to explain what our name means, or leave the reader wallowing in their confusion. The lesson here is to use names that make sense...call it what it is.

My oldest daughter used to have an annoying (although somewhat cute) habit of coming up with her own names for songs and stories that she liked. She would focus on one line from the whole song/story and derive a name from that. Many nights at bedtime she would ask frantically for a new favorite, while we went through a game of Twenty Questions to try and deduce what she was talking about. This is a danger of using non-obvious names for things. Sure, it may make sense to us, but what about everyone else.

There are several patterns that we can use when choosing names:

Nouns or noun phrases for class names

Name classes for what they represent or what they do, e.g. Movie, Review, or MovieRatingComparator.

Either nouns, noun phrases, or adjectives for interfaces

Interfaces are a bit different. If an adjective is used for an interface name, it usually ends with -able, e.g. Runnable, Serializable. My advice is to avoid conventions that prepend or append I to the name.

Verbs and verb phrases for method names

Methods do something, therefore using verbs to name them makes sense. Examples include isEmpty, addMovie, fetchReview.

Nouns and noun phrases for variable names

Variables hold things, so nouns make sense, e.g. rating, movies, connectionToReviewServer.

Choose names carefully, but don't spend too much time at it. Remember, they are easy to change. If later we decide that a different name would communicate the intent better, it can be changed. That's one of the most basic refactorings. Using a tool that has automated refactoring support helps.

3. Simplicity

When we are explaining something unfamiliar to someone we speak as simply as we can. Likewise, when we write code we should write it as simply as we can. Also keep in mind that someone will be reading that code later, trying to understand what it does and how it does it. We should strive to keep our code as clear and communicating intent as possible.

Do the Simplest Thing

How do we keep code as clear and intent communicating as possible? One way is to use simple algorithms as much as possible. Always assume that the simplest way of doing something is the best. If it proves not to be, we can always change it. One of the phrases heard a lot in the extreme programing (XP) world is "Do The Simplest Thing That Could Possibly Work".

"What," you may ask, "does that mean?" Well, back in Issue 1 we saw an example. We had a simple test, something like this:

public void testEmptyListSize() {
    MovieList emptyList = new MovieList();
    assertEqual(0, emptyList.size());
}

In order to get this test to pass (once it is compiling), we do the simplest thing that could possibly work. Specifically:


public int size() {
    return 0;
}

When we do this we know that the size() method is too simple, but at this point we don't know what it should be, and returning zero is the simplest thing that makes the test pass. We will have to generalize this method later, but not until we write tests for non-empty list behavior.

Keep It Simple by Refactoring

Another way to keep code simple and revealing intent is to change unclear code so that it is clear. This is one way that refactoring is used. To quote the subtitle of Fowler's breakthrough book on the topic[2]:

Refactoring is the process of changing a software system in such a way that it does not alter the external behavior of the code yet improves its internal structure. It is a disciplined way to clean up code that minimizes the chances of introducing bugs.

There are many refactorings that deal with communicating the intent of a piece of code more clearly. While it could be argued that most of Fowler's refactorings make code clearer, we only consider a handful here. See [1] and [2] for more information on large selection of refactorings. The simplest of these include renaming classes, methods, variables, etc. Some of the more complex intent-revealing refactorings are explained next.

Introduce Explaining Variable

If we have a complex expression, we take parts of it and put those intermediate values in temporary variables with names that explain the purpose of the subexpression.

Extract Method

If we have a method that is getting overly complex, we make part of it into a separate method. This is also useful if we see common code in multiple methods: we extract the common part (possibly parameterized) into a separate method that can be called by multiple clients.

Extract Class

If we have a class that is doing too much or that has multiple responsibilities, we extract related responsibilities into separate classes.

Replace Temp with Query

Rather than computing a value and storing it in a temporary variable, we extract the computation into a method that returns the result, and call that instead.

Split Temporary Variable

If we find a case where a temporary variable is reused, we create a new temporary variable for each use.

Remove Assignments to Parameters

If we find code that assigns to a parameter, we use a temporary variable instead.

4. Warranted Assumptions

The test in the previous section was the first written in the project it came from. It was written as shown and no other code had been written yet. Why is this significant? Well, if we look at the test code we will see that it makes some assumptions:

  1. there is a class called MovieList,
  2. MovieList has a zero-argument constructor, and
  3. MovieList has a method named size which takes no parameters and returns an int.

Making these assumptions as we write tests (and also when we write the real code) gives us the ability to design interfaces from the point of view of code that will use them. This allows us to choose names that make sense and that read well, i.e. are understandable and clear. It also allows us to decide what behavior is required from a much better point of view: again, that of the client. This limits the behavior to exactly what is required, which lets us write less code, which lets us work faster, which lets us deliver sooner and more often.

As Ron Jeffries et.al. [3] say: "Code what you want, not how to do it.".

5. "No Comment"

There are valid reasons to write comments. We'll talk about these later. However, most comments are not written for valid reasons. Fowler's Refactoring[2] calls comments "deodorant"...they are there to try to hide the bad smell of the code. The code is unclear, the code is poorly written, names are badly chosen, the logic is obtuse, etc. Comments were added to try to explain what the code does. The code should have been, and should be, refactored to make the comments unnecessary.

Don't get me wrong. I'm not saying "Don't write documentation." Nobody really doing XP will say that. Sometimes it is important to the customer to have specific documentation written. Also, I'm not saying "Never write comments." What I am saying is "Never write unnecessary comments." Most comments are unnecessary if the code is written so that the intent is clear. If and when we do write comments, we need to make sure they communicate why and not how.

Valid Comments

As I mentioned above, there are several valid reasons for us to write comments.

Incomplete code
This type of comment serves as a note to what we were in the midst of working on, or how we see the code evolving. There generally is not much need for this type of comment, since tasks should be no larger than what can be accomplished in a single day. A valid use for this type of comment might be to note code that could benefit from being refactored. Maybe we saw the need to refactor but didn't have time to do it. Make a note so that someone will spot it and do the refactoring when there is time. It might be prudent to use standard wording for these "refactoring TODO" comments so that a global search can be performed as a rough code-debt metric.
Refactoring doesn't make it clear enough
This isn't really a valid comment, rather it is more like the previous type...it's an IOU. If refactoring doesn't clean up the code, either someone else should try their hand at it, or (more likely) the code in question should be scrapped and rewritten.
Use of an unusual algorithm
If we use an uncommon algorithm for some reason, we should make a note of it with a comment. Point the reader to where they can learn more. Don't try to document the algorithm in a huge comment. Just note what algorithm it is, why it was used, and where to find more information.
Use of a published algorithm
If we use an algorithm that is published somewhere, we should add a comment saying where and giving credit to the author. This type of comment is often used with the previous type.
Performance tuning
This is important. If we tune for performance, we should add a comment explaining it. At the very least we need to add a note saying that the method in question has been tuned. If we tune without adding a note making it known we may find that someone later refactors to make the code clearer, undoing the optimization in the process. Keep in mind that we generally shouldn't be performance tuning until late in the project, once performance has been measured and bottlenecks have been found. There are times when a certain level of performance has business value. When this is the case, that value will determine when you should work on the performance. There are extensions to JUnit (e.g. JUnitPerf) that you can use to test-drive performance tuning.
Class comment
This is the one case that should always be adhered to, in spite of the general disapproval of comments. Classes and interfaces should always be commented. Not much is required. A simple note at the beginning of the class briefly explaining why the class exists and what it is used for will suffice. Avoid writing a how to use this class tutorial. That's one of the things the tests are for.

A final note related to comments. Programmers often sign their work by including a comment in the file header noting who wrote it. That's fine, credit where credit is due and all that, except that:

  1. If we are practising XP, specifically collective code ownership, everyone will work on that code. It is owned by the team.
  2. A record of who worked on each file will be maintained by the source code control system (SCCS). It is redundant to include the information in the file itself as well. Another comment on this: please don't include an expandable change log in the file (e.g.$Log$ in CVS). This clutters the code, makes the files larger and duplicates information that is easy to extract from the SCCS.

6. In Closing

I hope you've enjoyed this issue, and that it will improve the overall clarity of your code. This chapter was based on material from an early draft of my upcoming book on TDD. The book covers this (and many other) topics in much more detail. Watch for it.

In the next issue we'll have a look at what it's like to work test-first by working through a couple of tasks.

Dave

www.adaptionsoft.com

Bibliography

1
Martin Fowler.
The refactoring home page.
www.refactoring.com.
2
Martin Fowler.
Refactoring: Improving the Design of Existing Code.
Addison Wesley Longman, 1999.
ISBN 0-201-48567-2.
3
Ron Jeffries, Ann Anderson, , and Chet Hendrickson.
Extreme Programming Installed.
The XP Series. Addison Wesley Longman, 2001.
ISBN 0-201-70842-6.

Server Response from: SC4

 
Copyright© 1994 - 2008 Embarcadero Technologies, Inc. All rights reserved. Contact Us   Site Map   Legal Notices   Privacy Policy   Report Software Piracy