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:
- there is a class called
MovieList,
MovieList has a zero-argument constructor, and
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:
- If we are practising XP, specifically collective code ownership,
everyone will work on that code. It is owned by the team.
- 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.