34 Chapter 3: Functions
Unless you are a student of FitNesse, you probably don’t understand all the details.
Still, you probably understand that this function performs the inclusion of some setup and
teardown pages into a test page and then renders that page into HTML. If you are familiar
with JUnit,2 you probably realize that this function belongs to some kind of Web-based
testing framework. And, of course, that is correct. Divining that information from Listing 3-2
is pretty easy, but it’s pretty well obscured by Listing 3-1.
So what is it that makes a function like Listing 3-2 easy to read and understand? How
can we make a function communicate its intent? What attributes can we give our functions
that will allow a casual reader to intuit the kind of program they live inside?
Small!
The first rule of functions is that they should be small. The second rule of functions is that
they should be smaller than that. This is not an assertion that I can justify. I can’t provide
any references to research that shows that very small functions are better. What I can tell
you is that for nearly four decades I have written functions of all different sizes. I’ve writ-
ten several nasty 3,000-line abominations. I’ve written scads of functions in the 100 to 300
line range. And I’ve written functions that were 20 to 30 lines long. What this experience
has taught me, through long trial and error, is that functions should be very small.
In the eighties we used to say that a function should be no bigger than a screen-full.
Of course we said that at a time when VT100 screens were 24 lines by 80 columns, and
our editors used 4 lines for administrative purposes. Nowadays with a cranked-down font
and a nice big monitor, you can fit 150 characters on a line and a 100 lines or more on a
screen. Lines should not be 150 characters long. Functions should not be 100 lines long.
Functions should hardly ever be 20 lines long.
How short should a function be? In 1999 I went to visit Kent Beck at his home in Ore-
gon. We sat down and did some programming together. At one point he showed me a cute
little Java/Swing program that he called Sparkle. It produced a visual effect on the screen
very similar to the magic wand of the fairy godmother in the movie Cinderella. As you
moved the mouse, the sparkles would drip from the cursor with a satisfying scintillation,
falling to the bottom of the window through a simulated gravitational field. When Kent
showed me the code, I was struck by how small all the functions were. I was used to func-
tions in Swing programs that took up miles of vertical space. Every function in this pro-
gram was just two, or three, or four lines long. Each was transparently obvious. Each told
a story. And each led you to the next in a compelling order. That’s how short your functions
should be!3
2. An open-source unit-testing tool for Java. [Link]
3. I asked Kent whether he still had a copy, but he was unable to find one. I searched all my old computers too, but to no avail.
All that is left now is my memory of that program.
Do One Thing 35
How short should your functions be? They should usually be shorter than Listing 3-2!
Indeed, Listing 3-2 should really be shortened to Listing 3-3.
Listing 3-3
[Link] (re-refactored)
public static String renderPageWithSetupsAndTeardowns(
PageData pageData, boolean isSuite) throws Exception {
if (isTestPage(pageData))
includeSetupAndTeardownPages(pageData, isSuite);
return [Link]();
}
Blocks and Indenting
This implies that the blocks within if statements, else statements, while statements, and
so on should be one line long. Probably that line should be a function call. Not only does
this keep the enclosing function small, but it also adds documentary value because the
function called within the block can have a nicely descriptive name.
This also implies that functions should not be large enough to hold nested structures.
Therefore, the indent level of a function should not be greater than one or two. This, of
course, makes the functions easier to read and understand.
Do One Thing
It should be very clear that Listing 3-1 is doing lots
more than one thing. It’s creating buffers, fetching
pages, searching for inherited pages, rendering paths,
appending arcane strings, and generating HTML,
among other things. Listing 3-1 is very busy doing
lots of different things. On the other hand, Listing 3-3
is doing one simple thing. It’s including setups and
teardowns into test pages.
The following advice has appeared in one form
or another for 30 years or more.
FUNCTIONS SHOULD DO ONE THING. THEY SHOULD DO IT WELL.
THEY SHOULD DO IT ONLY.
The problem with this statement is that it is hard to know what “one thing” is. Does
Listing 3-3 do one thing? It’s easy to make the case that it’s doing three things:
1. Determining whether the page is a test page.
2. If so, including setups and teardowns.
3. Rendering the page in HTML.
36 Chapter 3: Functions
So which is it? Is the function doing one thing or three things? Notice that the three
steps of the function are one level of abstraction below the stated name of the function. We
can describe the function by describing it as a brief TO4 paragraph:
TO RenderPageWithSetupsAndTeardowns, we check to see whether the page is a test page
and if so, we include the setups and teardowns. In either case we render the page in
HTML.
If a function does only those steps that are one level below the stated name of the
function, then the function is doing one thing. After all, the reason we write functions is to
decompose a larger concept (in other words, the name of the function) into a set of steps at
the next level of abstraction.
It should be very clear that Listing 3-1 contains steps at many different levels of
abstraction. So it is clearly doing more than one thing. Even Listing 3-2 has two levels of
abstraction, as proved by our ability to shrink it down. But it would be very hard to mean-
ingfully shrink Listing 3-3. We could extract the if statement into a function named
includeSetupsAndTeardownsIfTestPage, but that simply restates the code without changing
the level of abstraction.
So, another way to know that a function is doing more than “one thing” is if you can
extract another function from it with a name that is not merely a restatement of its imple-
mentation [G34].
Sections within Functions
Look at Listing 4-7 on page 71. Notice that the generatePrimes function is divided into
sections such as declarations, initializations, and sieve. This is an obvious symptom of
doing more than one thing. Functions that do one thing cannot be reasonably divided into
sections.
One Level of Abstraction per Function
In order to make sure our functions are doing “one thing,” we need to make sure that the
statements within our function are all at the same level of abstraction. It is easy to see how
Listing 3-1 violates this rule. There are concepts in there that are at a very high level of
abstraction, such as getHtml(); others that are at an intermediate level of abstraction, such
as: String pagePathName = [Link](pagePath); and still others that are remark-
ably low level, such as: .append("\n").
Mixing levels of abstraction within a function is always confusing. Readers may not
be able to tell whether a particular expression is an essential concept or a detail. Worse,
4. The LOGO language used the keyword “TO” in the same way that Ruby and Python use “def.” So every function began with
the word “TO.” This had an interesting effect on the way functions were designed.
Switch Statements 37
like broken windows, once details are mixed with essential concepts, more and more
details tend to accrete within the function.
Reading Code from Top to Bottom: The Stepdown Rule
We want the code to read like a top-down narrative.5 We want every function to be fol-
lowed by those at the next level of abstraction so that we can read the program, descending
one level of abstraction at a time as we read down the list of functions. I call this The Step-
down Rule.
To say this differently, we want to be able to read the program as though it were a set
of TO paragraphs, each of which is describing the current level of abstraction and refer-
encing subsequent TO paragraphs at the next level down.
To include the setups and teardowns, we include setups, then we include the test page con-
tent, and then we include the teardowns.
To include the setups, we include the suite setup if this is a suite, then we include the
regular setup.
To include the suite setup, we search the parent hierarchy for the “SuiteSetUp” page
and add an include statement with the path of that page.
To search the parent. . .
It turns out to be very difficult for programmers to learn to follow this rule and write
functions that stay at a single level of abstraction. But learning this trick is also very
important. It is the key to keeping functions short and making sure they do “one thing.”
Making the code read like a top-down set of TO paragraphs is an effective technique for
keeping the abstraction level consistent.
Take a look at Listing 3-7 at the end of this chapter. It shows the whole
testableHtml function refactored according to the principles described here. Notice
how each function introduces the next, and each function remains at a consistent level
of abstraction.
Switch Statements
It’s hard to make a small switch statement.6 Even a switch statement with only two cases is
larger than I’d like a single block or function to be. It’s also hard to make a switch state-
ment that does one thing. By their nature, switch statements always do N things. Unfortu-
nately we can’t always avoid switch statements, but we can make sure that each switch
statement is buried in a low-level class and is never repeated. We do this, of course, with
polymorphism.
5. [KP78], p. 37.
6. And, of course, I include if/else chains in this.
38 Chapter 3: Functions
Consider Listing 3-4. It shows just one of the operations that might depend on the
type of employee.
Listing 3-4
[Link]
public Money calculatePay(Employee e)
throws InvalidEmployeeType {
switch ([Link]) {
case COMMISSIONED:
return calculateCommissionedPay(e);
case HOURLY:
return calculateHourlyPay(e);
case SALARIED:
return calculateSalariedPay(e);
default:
throw new InvalidEmployeeType([Link]);
}
}
There are several problems with this function. First, it’s large, and when new
employee types are added, it will grow. Second, it very clearly does more than one thing.
Third, it violates the Single Responsibility Principle7 (SRP) because there is more than one
reason for it to change. Fourth, it violates the Open Closed Principle8 (OCP) because it
must change whenever new types are added. But possibly the worst problem with this
function is that there are an unlimited number of other functions that will have the same
structure. For example we could have
isPayday(Employee e, Date date),
or
deliverPay(Employee e, Money pay),
or a host of others. All of which would have the same deleterious structure.
The solution to this problem (see Listing 3-5) is to bury the switch statement in the
basement of an ABSTRACT FACTORY,9 and never let anyone see it. The factory will use the
switch statement to create appropriate instances of the derivatives of Employee, and the var-
ious functions, such as calculatePay, isPayday, and deliverPay, will be dispatched poly-
morphically through the Employee interface.
My general rule for switch statements is that they can be tolerated if they appear
only once, are used to create polymorphic objects, and are hidden behind an inheritance
7. a. [Link]
b. [Link]
8. a. [Link]
b. [Link]
9. [GOF].
Use Descriptive Names 39
Listing 3-5
Employee and Factory
public abstract class Employee {
public abstract boolean isPayday();
public abstract Money calculatePay();
public abstract void deliverPay(Money pay);
}
-----------------
public interface EmployeeFactory {
public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType;
}
-----------------
public class EmployeeFactoryImpl implements EmployeeFactory {
public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType {
switch ([Link]) {
case COMMISSIONED:
return new CommissionedEmployee(r) ;
case HOURLY:
return new HourlyEmployee(r);
case SALARIED:
return new SalariedEmploye(r);
default:
throw new InvalidEmployeeType([Link]);
}
}
}
relationship so that the rest of the system can’t see them [G23]. Of course every circum-
stance is unique, and there are times when I violate one or more parts of that rule.
Use Descriptive Names
In Listing 3-7 I changed the name of our example function from testableHtml to
[Link]. This is a far better name because it better describes what
the function does. I also gave each of the private methods an equally descriptive name
such as isTestable or includeSetupAndTeardownPages. It is hard to overestimate the value
of good names. Remember Ward’s principle: “You know you are working on clean code
when each routine turns out to be pretty much what you expected.” Half the battle to
achieving that principle is choosing good names for small functions that do one thing.
The smaller and more focused a function is, the easier it is to choose a descriptive
name.
Don’t be afraid to make a name long. A long descriptive name is better than a short
enigmatic name. A long descriptive name is better than a long descriptive comment. Use
a naming convention that allows multiple words to be easily read in the function names,
and then make use of those multiple words to give the function a name that says what
it does.
40 Chapter 3: Functions
Don’t be afraid to spend time choosing a name. Indeed, you should try several differ-
ent names and read the code with each in place. Modern IDEs like Eclipse or IntelliJ make
it trivial to change names. Use one of those IDEs and experiment with different names
until you find one that is as descriptive as you can make it.
Choosing descriptive names will clarify the design of the module in your mind and
help you to improve it. It is not at all uncommon that hunting for a good name results in a
favorable restructuring of the code.
Be consistent in your names. Use the same phrases, nouns, and verbs in the function
names you choose for your modules. Consider, for example, the names includeSetup-
AndTeardownPages, includeSetupPages, includeSuiteSetupPage, and includeSetupPage. The
similar phraseology in those names allows the sequence to tell a story. Indeed, if I
showed you just the sequence above, you’d ask yourself: “What happened to
includeTeardownPages, includeSuiteTeardownPage, and includeTeardownPage?” How’s that
for being “. . . pretty much what you expected.”
Function Arguments
The ideal number of arguments for a function is
zero (niladic). Next comes one (monadic), followed
closely by two (dyadic). Three arguments (triadic)
should be avoided where possible. More than three
(polyadic) requires very special justification—and
then shouldn’t be used anyway.
Arguments are hard. They take a lot of con-
ceptual power. That’s why I got rid of almost all of
them from the example. Consider, for instance, the
StringBuffer in the example. We could have
passed it around as an argument rather than mak-
ing it an instance variable, but then our readers
would have had to interpret it each time they saw
it. When you are reading the story told by the
module, includeSetupPage() is easier to understand than includeSetupPageInto(newPage-
Content). The argument is at a different level of abstraction than the function name and
forces you to know a detail (in other words, StringBuffer) that isn’t particularly important
at that point.
Arguments are even harder from a testing point of view. Imagine the difficulty of
writing all the test cases to ensure that all the various combinations of arguments work
properly. If there are no arguments, this is trivial. If there’s one argument, it’s not too hard.
With two arguments the problem gets a bit more challenging. With more than two argu-
ments, testing every combination of appropriate values can be daunting.
Function Arguments 41
Output arguments are harder to understand than input arguments. When we read a
function, we are used to the idea of information going in to the function through arguments
and out through the return value. We don’t usually expect information to be going out
through the arguments. So output arguments often cause us to do a double-take.
One input argument is the next best thing to no arguments. SetupTeardown-
[Link](pageData) is pretty easy to understand. Clearly we are going to render the
data in the pageData object.
Common Monadic Forms
There are two very common reasons to pass a single argument into a function. You may be
asking a question about that argument, as in boolean fileExists(“MyFile”). Or you may be
operating on that argument, transforming it into something else and returning it. For
example, InputStream fileOpen(“MyFile”) transforms a file name String into an
InputStream return value. These two uses are what readers expect when they see a func-
tion. You should choose names that make the distinction clear, and always use the two
forms in a consistent context. (See Command Query Separation below.)
A somewhat less common, but still very useful form for a single argument function,
is an event. In this form there is an input argument but no output argument. The overall
program is meant to interpret the function call as an event and use the argument to alter the
state of the system, for example, void passwordAttemptFailedNtimes(int attempts). Use
this form with care. It should be very clear to the reader that this is an event. Choose
names and contexts carefully.
Try to avoid any monadic functions that don’t follow these forms, for example, void
includeSetupPageInto(StringBuffer pageText). Using an output argument instead of a
return value for a transformation is confusing. If a function is going to transform its input
argument, the transformation should appear as the return value. Indeed, StringBuffer
transform(StringBuffer in) is better than void transform-(StringBuffer out), even if the
implementation in the first case simply returns the input argument. At least it still follows
the form of a transformation.
Flag Arguments
Flag arguments are ugly. Passing a boolean into a function is a truly terrible practice. It
immediately complicates the signature of the method, loudly proclaiming that this function
does more than one thing. It does one thing if the flag is true and another if the flag is false!
In Listing 3-7 we had no choice because the callers were already passing that flag
in, and I wanted to limit the scope of refactoring to the function and below. Still, the
method call render(true) is just plain confusing to a poor reader. Mousing over the call
and seeing render(boolean isSuite) helps a little, but not that much. We should have
split the function into two: renderForSuite() and renderForSingleTest().