Chào các bạn! Vì nhiều lý do từ nay Truyen2U chính thức đổi tên là Truyen247.Pro. Mong các bạn tiếp tục ủng hộ truy cập tên miền mới này nhé! Mãi yêu... ♥

Cleancode Part2

Refactoring SerialDate

If you go to http://www.jfree.org/jcommon/index.php, you will find the JCommon library.

Deep within that library there is a package named org.jfree.date. Within that package

there is a class named SerialDate. We are going to explore that class.

The author of SerialDate is David Gilbert. David is clearly an experienced and competent

programmer. As we shall see, he shows a significant degree of professionalism and

discipline within his code. For all intents and purposes, this is "good code." And I am

going to rip it to pieces.

268 Chapter 16: Refactoring SerialDate

This is not an activity of malice. Nor do I think that I am so much better than David

that I somehow have a right to pass judgment on his code. Indeed, if you were to find some

of my code, I'm sure you could find plenty of things to complain about.

No, this is not an activity of nastiness or arrogance. What I am about to do is nothing

more and nothing less than a professional review. It is something that we should all be

comfortable doing. And it is something we should welcome when it is done for us. It is

only through critiques like these that we will learn. Doctors do it. Pilots do it. Lawyers do

it. And we programmers need to learn how to do it too.

One more thing about David Gilbert: David is more than just a good programmer.

David had the courage and good will to offer his code to the community at large for free.

He placed it out in the open for all to see and invited public usage and public scrutiny. This

was well done!

SerialDate (Listing B-1, page 349) is a class that represents a date in Java. Why have

a class that represents a date, when Java already has java.util.Date and

java.util.Calendar, and others? The author wrote this class in response to a pain that I

have often felt myself. The comment in his opening Javadoc (line 67) explains it well. We

could quibble about his intention, but I have certainly had to deal with this issue, and I

welcome a class that is about dates instead of times.

First, Make It Work

There are some unit tests in a class named SerialDateTests (Listing B-2, page 366). The

tests all pass. Unfortunately a quick inspection of the tests shows that they don't test everything

[T1]. For example, doing a "Find Usages" search on the method MonthCodeToQuarter

(line 334) indicates that it is not used [F4]. Therefore, the unit tests don't test it.

So I fired up Clover to see what the unit tests covered and what they didn't. Clover

reported that the unit tests executed only 91 of the 185 executable statements in SerialDate

(~50 percent) [T2]. The coverage map looks like a patchwork quilt, with big gobs of unexecuted

code littered all through the class.

It was my goal to completely understand and also refactor this class. I couldn't do that

without much greater test coverage. So I wrote my own suite of completely independent

unit tests (Listing B-4, page 374).

As you look through these tests, you will note that many of them are commented out.

These tests didn't pass. They represent behavior that I think SerialDate should have. So as

I refactor SerialDate, I'll be working to make these tests pass too.

Even with some of the tests commented out, Clover reports that the new unit tests are

executing 170 (92 percent) out of the 185 executable statements. This is pretty good, and I

think we'll be able to get this number higher.

The first few commented-out tests (lines 23-63) were a bit of conceit on my part. The

program was not designed to pass these tests, but the behavior seemed obvious [G2] to me.

First, Make It Work 269

I'm not sure why the testWeekdayCodeToString method was written in the first place, but

because it is there, it seems obvious that it should not be case sensitive. Writing these tests

was trivial [T3]. Making them pass was even easier; I just changed lines 259 and 263 to

use equalsIgnoreCase.

I left the tests at line 32 and line 45 commented out because it's not clear to me that

the "tues" and "thurs" abbreviations ought to be supported.

The tests on line 153 and line 154 don't pass. Clearly, they should [G2]. We can easily

fix this, and the tests on line 163 through line 213, by making the following changes to the

stringToMonthCode function.

The commented test on line 318 exposes a bug in the getFollowingDayOfWeek method

(line 672). December 25th, 2004, was a Saturday. The following Saturday was January 1st,

2005. However, when we run the test, we see that getFollowingDayOfWeek returns December

25th as the Saturday that follows December 25th. Clearly, this is wrong [G3],[T1]. We

see the problem in line 685. It is a typical boundary condition error [T5]. It should read as

follows:

It is interesting to note that this function was the target of an earlier repair. The change

history (line 43) shows that "bugs" were fixed in getPreviousDayOfWeek, getFollowing-

DayOfWeek, and getNearestDayOfWeek [T6].

The testGetNearestDayOfWeek unit test (line 329), which tests the getNearestDayOfWeek

method (line 705), did not start out as long and exhaustive as it currently is. I added a lot

of test cases to it because my initial test cases did not all pass [T6]. You can see the pattern

of failure by looking at which test cases are commented out. That pattern is revealing [T7].

It shows that the algorithm fails if the nearest day is in the future. Clearly there is some

kind of boundary condition error [T5].

The pattern of test coverage reported by Clover is also interesting [T8]. Line 719

never gets executed! This means that the if statement in line 718 is always false. Sure

enough, a look at the code shows that this must be true. The adjust variable is always negative

and so cannot be greater or equal to 4. So this algorithm is just wrong.

457 if ((result < 1) || (result > 12)) {

result = -1;

458 for (int i = 0; i < monthNames.length; i++) {

459 if (s.equalsIgnoreCase(shortMonthNames[i])) {

460 result = i + 1;

461 break;

462 }

463 if (s.equalsIgnoreCase(monthNames[i])) {

464 result = i + 1;

465 break;

466 }

467 }

468 }

685 if (baseDOW >= targetWeekday) {

270 Chapter 16: Refactoring SerialDate

The right algorithm is shown below:

Finally, the tests at line 417 and line 429 can be made to pass simply by throwing an

IllegalArgumentException instead of returning an error string from weekInMonthToString

and relativeToString.

With these changes all the unit tests pass, and I believe SerialDate now works. So now

it's time to make it "right."

Then Make It Right

We are going to walk from the top to the bottom of SerialDate, improving it as we go

along. Although you won't see this in the discussion, I will be running all of the JCommon

unit tests, including my improved unit test for SerialDate, after every change I make. So

rest assured that every change you see here works for all of JCommon.

Starting at line 1, we see a ream of comments with license information, copyrights,

authors, and change history. I acknowledge that there are certain legalities that need to be

addressed, and so the copyrights and licenses must stay. On the other hand, the change history

is a leftover from the 1960s. We have source code control tools that do this for us now.

This history should be deleted [C1].

The import list starting at line 61 could be shortened by using java.text.* and

java.util.*. [J1]

I wince at the HTML formatting in the Javadoc (line 67). Having a source file with

more than one language in it troubles me. This comment has four languages in it: Java,

English, Javadoc, and html [G1]. With that many languages in use, it's hard to keep things

straight. For example, the nice positioning of line 71 and line 72 are lost when the Javadoc

is generated, and yet who wants to see <ul> and <li> in the source code? A better strategy

might be to just surround the whole comment with <pre> so that the formatting that is

apparent in the source code is preserved within the Javadoc.1

Line 86 is the class declaration. Why is this class named SerialDate? What is the significance

of the world "serial"? Is it because the class is derived from Serializable? That

doesn't seem likely.

int delta = targetDOW - base.getDayOfWeek();

int positiveDelta = delta + 7;

int adjust = positiveDelta % 7;

if (adjust > 3)

adjust -= 7;

return SerialDate.addDays(adjust, base);

1. An even better solution would have been for Javadoc to present all comments as preformatted, so that comments appear the

same in both code and document.

Then Make It Right 271

I won't keep you guessing. I know why (or at least I think I know why) the word

"serial" was used. The clue is in the constants SERIAL_LOWER_BOUND and

SERIAL_UPPER_BOUND on line 98 and line 101. An even better clue is in the comment

that begins on line 830. This class is named SerialDate because it is implemented using a

"serial number," which happens to be the number of days since December 30th, 1899.

I have two problems with this. First, the term "serial number" is not really correct.

This may be a quibble, but the representation is more of a relative offset than a serial number.

The term "serial number" has more to do with product identification markers than

dates. So I don't find this name particularly descriptive [N1]. A more descriptive term

might be "ordinal."

The second problem is more significant. The name SerialDate implies an implementation.

This class is an abstract class. There is no need to imply anything at all about the

implementation. Indeed, there is good reason to hide the implementation! So I find this

name to be at the wrong level of abstraction [N2]. In my opinion, the name of this class

should simply be Date.

Unfortunately, there are already too many classes in the Java library named Date, so

this is probably not the best name to choose. Because this class is all about days, instead of

time, I considered naming it Day, but this name is also heavily used in other places. In the

end, I chose DayDate as the best compromise.

From now on in this discussion I will use the term DayDate. I leave it to you to remember

that the listings you are looking at still use SerialDate.

I understand why DayDate inherits from Comparable and Serializable. But why does it

inherit from MonthConstants? The class MonthConstants (Listing B-3, page 372) is just a

bunch of static final constants that define the months. Inheriting from classes with constants

is an old trick that Java programmers used so that they could avoid using expressions

like MonthConstants.January, but it's a bad idea [J2]. MonthConstants should really be

an enum.

public abstract class DayDate implements Comparable,

Serializable {

public static enum Month {

JANUARY(1),

FEBRUARY(2),

MARCH(3),

APRIL(4),

MAY(5),

JUNE(6),

JULY(7),

AUGUST(8),

SEPTEMBER(9),

OCTOBER(10),

NOVEMBER(11),

DECEMBER(12);

Month(int index) {

this.index = index;

}

272 Chapter 16: Refactoring SerialDate

Changing MonthConstants to this enum forces quite a few changes to the DayDate class

and all it's users. It took me an hour to make all the changes. However, any function that

used to take an int for a month, now takes a Month enumerator. This means we can get rid

of the isValidMonthCode method (line 326), and all the month code error checking such as

that in monthCodeToQuarter (line 356) [G5].

Next, we have line 91, serialVersionUID. This variable is used to control the serializer.

If we change it, then any DayDate written with an older version of the software won't be

readable anymore and will result in an InvalidClassException. If you don't declare the

serialVersionUID variable, then the compiler automatically generates one for you, and it

will be different every time you make a change to the module. I know that all the documents

recommend manual control of this variable, but it seems to me that automatic control

of serialization is a lot safer [G4]. After all, I'd much rather debug an

InvalidClassException than the odd behavior that would ensue if I forgot to change the

serialVersionUID. So I'm going to delete the variable—at least for the time being.2

I find the comment on line 93 redundant. Redundant comments are just places to collect

lies and misinformation [C2]. So I'm going to get rid of it and its ilk.

The comments at line 97 and line 100 talk about serial numbers, which I discussed

earlier [C1]. The variables they describe are the earliest and latest possible dates that

DayDate can describe. This can be made a bit clearer [N1].

It's not clear to me why EARLIEST_DATE_ORDINAL is 2 instead of 0. There is a hint in the

comment on line 829 that suggests that this has something to do with the way dates are

represented in Microsoft Excel. There is a much deeper insight provided in a derivative of

DayDate called SpreadsheetDate (Listing B-5, page 382). The comment on line 71 describes

the issue nicely.

The problem I have with this is that the issue seems to be related to the implementation

of SpreadsheetDate and has nothing to do with DayDate. I conclude from this that

public static Month make(int monthIndex) {

for (Month m : Month.values()) {

if (m.index == monthIndex)

return m;

}

throw new IllegalArgumentException("Invalid month index " + monthIndex);

}

public final int index;

}

2. Several of the reviewers of this text have taken exception to this decision. They contend that in an open source framework it is

better to assert manual control over the serial ID so that minor changes to the software don't cause old serialized dates to be

invalid. This is a fair point. However, at least the failure, inconvenient though it might be, has a clear-cut cause. On the other

hand, if the author of the class forgets to update the ID, then the failure mode is undefined and might very well be silent. I

think the real moral of this story is that you should not expect to deserialize across versions.

public static final int EARLIEST_DATE_ORDINAL = 2; // 1/1/1900

public static final int LATEST_DATE_ORDINAL = 2958465; // 12/31/9999

Then Make It Right 273

EARLIEST_DATE_ORDINAL and LATEST_DATE_ORDINAL do not really belong in DayDate and

should be moved to SpreadsheetDate [G6].

Indeed, a search of the code shows that these variables are used only within

SpreadsheetDate. Nothing in DayDate, nor in any other class in the JCommon framework, uses

them. Therefore, I'll move them down into SpreadsheetDate.

The next variables, MINIMUM_YEAR_SUPPORTED, and MAXIMUM_YEAR_SUPPORTED (line 104

and line 107), provide something of a dilemma. It seems clear that if DayDate is an abstract

class that provides no foreshadowing of implementation, then it should not inform us

about a minimum or maximum year. Again, I am tempted to move these variables down

into SpreadsheetDate [G6]. However, a quick search of the users of these variables shows

that one other class uses them: RelativeDayOfWeekRule (Listing B-6, page 390). We see that

usage at line 177 and line 178 in the getDate function, where they are used to check that

the argument to getDate is a valid year. The dilemma is that a user of an abstract class

needs information about its implementation.

What we need to do is provide this information without polluting DayDate itself.

Usually, we would get implementation information from an instance of a derivative.

However, the getDate function is not passed an instance of a DayDate. It does, however,

return such an instance, which means that somewhere it must be creating it. Line 187

through line 205 provide the hint. The DayDate instance is being created by one of the

three functions, getPreviousDayOfWeek, getNearestDayOfWeek, or getFollowingDayOfWeek.

Looking back at the DayDate listing, we see that these functions (lines 638–724) all return

a date created by addDays (line 571), which calls createInstance (line 808), which creates

a SpreadsheetDate! [G7].

It's generally a bad idea for base classes to know about their derivatives. To fix this, we

should use the ABSTRACT FACTORY3 pattern and create a DayDateFactory. This factory will

create the instances of DayDate that we need and can also answer questions about the

implementation, such as the maximum and minimum dates.

3. [GOF].

public abstract class DayDateFactory {

private static DayDateFactory factory = new SpreadsheetDateFactory();

public static void setInstance(DayDateFactory factory) {

DayDateFactory.factory = factory;

}

protected abstract DayDate _makeDate(int ordinal);

protected abstract DayDate _makeDate(int day, DayDate.Month month, int year);

protected abstract DayDate _makeDate(int day, int month, int year);

protected abstract DayDate _makeDate(java.util.Date date);

protected abstract int _getMinimumYear();

protected abstract int _getMaximumYear();

public static DayDate makeDate(int ordinal) {

return factory._makeDate(ordinal);

}

274 Chapter 16: Refactoring SerialDate

This factory class replaces the createInstance methods with makeDate methods, which

improves the names quite a bit [N1]. It defaults to a SpreadsheetDateFactory but can be

changed at any time to use a different factory. The static methods that delegate to abstract

methods use a combination of the SINGLETON,4 DECORATOR,5 and ABSTRACT FACTORY

patterns that I have found to be useful.

The SpreadsheetDateFactory looks like this.

public static DayDate makeDate(int day, DayDate.Month month, int year) {

return factory._makeDate(day, month, year);

}

public static DayDate makeDate(int day, int month, int year) {

return factory._makeDate(day, month, year);

}

public static DayDate makeDate(java.util.Date date) {

return factory._makeDate(date);

}

public static int getMinimumYear() {

return factory._getMinimumYear();

}

public static int getMaximumYear() {

return factory._getMaximumYear();

}

}

4. Ibid.

5. Ibid.

public class SpreadsheetDateFactory extends DayDateFactory {

public DayDate _makeDate(int ordinal) {

return new SpreadsheetDate(ordinal);

}

public DayDate _makeDate(int day, DayDate.Month month, int year) {

return new SpreadsheetDate(day, month, year);

}

public DayDate _makeDate(int day, int month, int year) {

return new SpreadsheetDate(day, month, year);

}

public DayDate _makeDate(Date date) {

final GregorianCalendar calendar = new GregorianCalendar();

calendar.setTime(date);

return new SpreadsheetDate(

calendar.get(Calendar.DATE),

DayDate.Month.make(calendar.get(Calendar.MONTH) + 1),

calendar.get(Calendar.YEAR));

}

Then Make It Right 275

As you can see, I have already moved the MINIMUM_YEAR_SUPPORTED and

MAXIMUM_YEAR_SUPPORTED variables into SpreadsheetDate, where they belong [G6].

The next issue in DayDate are the day constants beginning at line 109. These should

really be another enum [J3]. We've seen this pattern before, so I won't repeat it here. You'll

see it in the final listings.

Next, we see a series of tables starting with LAST_DAY_OF_MONTH at line 140. My first

issue with these tables is that the comments that describe them are redundant [C3]. Their

names are sufficient. So I'm going to delete the comments.

There seems to be no good reason that this table isn't private [G8], because there is a

static function lastDayOfMonth that provides the same data.

The next table, AGGREGATE_DAYS_TO_END_OF_MONTH, is a bit more mysterious because it is

not used anywhere in the JCommon framework [G9]. So I deleted it.

The same goes for LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_MONTH.

The next table, AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH, is used only in SpreadsheetDate

(line 434 and line 473). This begs the question of whether it should be moved

to SpreadsheetDate. The argument for not moving it is that the table is not specific to any

particular implementation [G6]. On the other hand, no implementation other than

SpreadsheetDate actually exists, and so the table should be moved close to where it is

used [G10].

What settles the argument for me is that to be consistent [G11], we should make the

table private and expose it through a function like julianDateOfLastDayOfMonth. Nobody

seems to need a function like that. Moreover, the table can be moved back to DayDate easily

if any new implementation of DayDate needs it. So I moved it.

The same goes for the table, LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_MONTH.

Next, we see three sets of constants that can be turned into enums (lines 162–205).

The first of the three selects a week within a month. I changed it into an enum named

WeekInMonth.

protected int _getMinimumYear() {

return SpreadsheetDate.MINIMUM_YEAR_SUPPORTED;

}

protected int _getMaximumYear() {

return SpreadsheetDate.MAXIMUM_YEAR_SUPPORTED;

}

}

public enum WeekInMonth {

FIRST(1), SECOND(2), THIRD(3), FOURTH(4), LAST(0);

public final int index;

WeekInMonth(int index) {

this.index = index;

}

}

276 Chapter 16: Refactoring SerialDate

The second set of constants (lines 177–187) is a bit more obscure. The INCLUDE_NONE,

INCLUDE_FIRST, INCLUDE_SECOND, and INCLUDE_BOTH constants are used to describe whether

the defining end-point dates of a range should be included in that range. Mathematically,

this is described using the terms "open interval," "half-open interval," and "closed interval."

I think it is clearer using the mathematical nomenclature [N3], so I changed it to an

enum named DateInterval with CLOSED, CLOSED_LEFT, CLOSED_RIGHT, and OPEN enumerators.

The third set of constants (lines 18–205) describe whether a search for a particular

day of the week should result in the last, next, or nearest instance. Deciding what to call

this is difficult at best. In the end, I settled for WeekdayRange with LAST, NEXT, and NEAREST

enumerators.

You might not agree with the names I've chosen. They make sense to me, but they

may not make sense to you. The point is that they are now in a form that makes them easy

to change [J3]. They aren't passed as integers anymore; they are passed as symbols. I can

use the "change name" function of my IDE to change the names, or the types, without

worrying that I missed some -1 or 2 somewhere in the code or that some int argument declaration

is left poorly described.

The description field at line 208 does not seem to be used by anyone. I deleted it along

with its accessor and mutator [G9].

I also deleted the degenerate default constructor at line 213 [G12]. The compiler will

generate it for us.

We can skip over the isValidWeekdayCode method (lines 216–238) because we deleted

it when we created the Day enumeration.

This brings us to the stringToWeekdayCode method (lines 242–270). Javadocs that

don't add much to the method signature are just clutter [C3],[G12]. The only value this

Javadoc adds is the description of the -1 return value. However, because we changed to the

Day enumeration, the comment is actually wrong [C2]. The method now throws an

IllegalArgumentException. So I deleted the Javadoc.

I also deleted all the final keywords in arguments and variable declarations. As far as

I could tell, they added no real value but did add to the clutter [G12]. Eliminating final

flies in the face of some conventional wisdom. For example, Robert Simmons6 strongly

recommends us to ". . . spread final all over your code." Clearly I disagree. I think that

there are a few good uses for final, such as the occasional final constant, but otherwise

the keyword adds little value and creates a lot of clutter. Perhaps I feel this way because the

kinds of errors that final might catch are already caught by the unit tests I write.

I didn't care for the duplicate if statements [G5] inside the for loop (line 259 and

line 263), so I connected them into a single if statement using the || operator. I also used

the Day enumeration to direct the for loop and made a few other cosmetic changes.

It occurred to me that this method does not really belong in DayDate. It's really the

parse function of Day. So I moved it into the Day enumeration. However, that made the Day

6. [Simmons04], p. 73.

Then Make It Right 277

enumeration pretty large. Because the concept of Day does not depend on DayDate, I moved

the Day enumeration outside of the DayDate class into its own source file [G13].

I also moved the next function, weekdayCodeToString (lines 272–286) into the Day

enumeration and called it toString.

There are two getMonths functions (lines 288–316). The first calls the second. The

second is never called by anyone but the first. Therefore, I collapsed the two into one and

vastly simplified them [G9],[G12],[F4]. Finally, I changed the name to be a bit more selfdescriptive

[N1].

public enum Day {

MONDAY(Calendar.MONDAY),

TUESDAY(Calendar.TUESDAY),

WEDNESDAY(Calendar.WEDNESDAY),s

THURSDAY(Calendar.THURSDAY),

FRIDAY(Calendar.FRIDAY),

SATURDAY(Calendar.SATURDAY),

SUNDAY(Calendar.SUNDAY);

public final int index;

private static DateFormatSymbols dateSymbols = new DateFormatSymbols();

Day(int day) {

index = day;

}

public static Day make(int index) throws IllegalArgumentException {

for (Day d : Day.values())

if (d.index == index)

return d;

throw new IllegalArgumentException(

String.format("Illegal day index: %d.", index));

}

public static Day parse(String s) throws IllegalArgumentException {

String[] shortWeekdayNames =

dateSymbols.getShortWeekdays();

String[] weekDayNames =

dateSymbols.getWeekdays();

s = s.trim();

for (Day day : Day.values()) {

if (s.equalsIgnoreCase(shortWeekdayNames[day.index]) ||

s.equalsIgnoreCase(weekDayNames[day.index])) {

return day;

}

}

throw new IllegalArgumentException(

String.format("%s is not a valid weekday string", s));

}

public String toString() {

return dateSymbols.getWeekdays()[index];

}

}

278 Chapter 16: Refactoring SerialDate

The isValidMonthCode function (lines 326–346) was made irrelevant by the Month

enum, so I deleted it [G9].

The monthCodeToQuarter function (lines 356–375) smells of FEATURE ENVY7 [G14]

and probably belongs in the Month enum as a method named quarter. So I replaced it.

This made the Month enum big enough to be in its own class. So I moved it out of

DayDate to be consistent with the Day enum [G11],[G13].

The next two methods are named monthCodeToString (lines 377–426). Again, we see

the pattern of one method calling its twin with a flag. It is usually a bad idea to pass a flag

as an argument to a function, especially when that flag simply selects the format of the output

[G15]. I renamed, simplified, and restructured these functions and moved them into the

Month enum [N1],[N3],[C3],[G14].

The next method is stringToMonthCode (lines 428–472). I renamed it, moved it into the

Month enum, and simplified it [N1],[N3],[C3],[G14],[G12].

public static String[] getMonthNames() {

return dateFormatSymbols.getMonths();

}

7. [Refactoring].

public int quarter() {

return 1 + (index-1)/3;

}

public String toString() {

return dateFormatSymbols.getMonths()[index - 1];

}

public String toShortString() {

return dateFormatSymbols.getShortMonths()[index - 1];

}

public static Month parse(String s) {

s = s.trim();

for (Month m : Month.values())

if (m.matches(s))

return m;

try {

return make(Integer.parseInt(s));

}

catch (NumberFormatException e) {}

throw new IllegalArgumentException("Invalid month " + s);

}

Then Make It Right 279

The isLeapYear method (lines 495–517) can be made a bit more expressive [G16].

The next function, leapYearCount (lines 519–536) doesn't really belong in DayDate.

Nobody calls it except for two methods in SpreadsheetDate. So I pushed it down [G6].

The lastDayOfMonth function (lines 538–560) makes use of the LAST_DAY_OF_MONTH

array. This array really belongs in the Month enum [G17], so I moved it there. I also simplified

the function and made it a bit more expressive [G16].

Now things start to get a bit more interesting. The next function is addDays (lines 562–

576). First of all, because this function operates on the variables of DayDate, it should not

be static [G18]. So I changed it to an instance method. Second, it calls the function

toSerial. This function should be renamed toOrdinal [N1]. Finally, the method can be

simplified.

The same goes for addMonths (lines 578–602). It should be an instance method [G18].

The algorithm is a bit complicated, so I used EXPLAINING TEMPORARY VARIABLES8 [G19]

to make it more transparent. I also renamed the method getYYY to getYear [N1].

private boolean matches(String s) {

return s.equalsIgnoreCase(toString()) ||

s.equalsIgnoreCase(toShortString());

}

public static boolean isLeapYear(int year) {

boolean fourth = year % 4 == 0;

boolean hundredth = year % 100 == 0;

boolean fourHundredth = year % 400 == 0;

return fourth && (!hundredth || fourHundredth);

}

public static int lastDayOfMonth(Month month, int year) {

if (month == Month.FEBRUARY && isLeapYear(year))

return month.lastDay() + 1;

else

return month.lastDay();

}

public DayDate addDays(int days) {

return DayDateFactory.makeDate(toOrdinal() + days);

}

8. [Beck97].

public DayDate addMonths(int months) {

int thisMonthAsOrdinal = 12 * getYear() + getMonth().index - 1;

int resultMonthAsOrdinal = thisMonthAsOrdinal + months;

int resultYear = resultMonthAsOrdinal / 12;

Month resultMonth = Month.make(resultMonthAsOrdinal % 12 + 1);

280 Chapter 16: Refactoring SerialDate

The addYears function (lines 604–626) provides no surprises over the others.

There is a little itch at the back of my mind that is bothering me about changing

these methods from static to instance. Does the expression date.addDays(5) make it

clear that the date object does not change and that a new instance of DayDate is returned?

Or does it erroneously imply that we are adding five days to the date object? You might

not think that is a big problem, but a bit of code that looks like the following can be very

deceiving [G20].

DayDate date = DateFactory.makeDate(5, Month.DECEMBER, 1952);

date.addDays(7); // bump date by one week.

Someone reading this code would very likely just accept that addDays is changing the

date object. So we need a name that breaks this ambiguity [N4]. So I changed the names to

plusDays and plusMonths. It seems to me that the intent of the method is captured nicely by

DayDate date = oldDate.plusDays(5);

whereas the following doesn't read fluidly enough for a reader to simply accept that the

date object is changed:

date.plusDays(5);

The algorithms continue to get more interesting. getPreviousDayOfWeek (lines 628–

660) works but is a bit complicated. After some thought about what was really going on

[G21], I was able to simplify it and use EXPLAINING TEMPORARY VARIABLES [G19] to

make it clearer. I also changed it from a static method to an instance method [G18], and

got rid of the duplicate instance method [G5] (lines 997–1008).

The exact same analysis and result occurred for getFollowingDayOfWeek (lines 662–693).

int lastDayOfResultMonth = lastDayOfMonth(resultMonth, resultYear);

int resultDay = Math.min(getDayOfMonth(), lastDayOfResultMonth);

return DayDateFactory.makeDate(resultDay, resultMonth, resultYear);

}

public DayDate plusYears(int years) {

int resultYear = getYear() + years;

int lastDayOfMonthInResultYear = lastDayOfMonth(getMonth(), resultYear);

int resultDay = Math.min(getDayOfMonth(), lastDayOfMonthInResultYear);

return DayDateFactory.makeDate(resultDay, getMonth(), resultYear);

}

public DayDate getPreviousDayOfWeek(Day targetDayOfWeek) {

int offsetToTarget = targetDayOfWeek.index - getDayOfWeek().index;

if (offsetToTarget >= 0)

offsetToTarget -= 7;

return plusDays(offsetToTarget);

}

public DayDate getFollowingDayOfWeek(Day targetDayOfWeek) {

int offsetToTarget = targetDayOfWeek.index - getDayOfWeek().index;

if (offsetToTarget <= 0)

Then Make It Right 281

The next function is getNearestDayOfWeek (lines 695–726), which we corrected back

on page 270. But the changes I made back then aren't consistent with the current pattern in

the last two functions [G11]. So I made it consistent and used some EXPLAINING TEMPORARY

VARIABLES [G19] to clarify the algorithm.

The getEndOfCurrentMonth method (lines 728–740) is a little strange because it is an

instance method that envies [G14] its own class by taking a DayDate argument. I made it a

true instance method and clarified a few names.

Refactoring weekInMonthToString (lines 742–761) turned out to be very interesting

indeed. Using the refactoring tools of my IDE, I first moved the method to the WeekInMonth

enum that I created back on page 275. Then I renamed the method to toString. Next, I

changed it from a static method to an instance method. All the tests still passed. (Can you

guess where I am going?)

Next, I deleted the method entirely! Five asserts failed (lines 411–415, Listing B-4,

page 374). I changed these lines to use the names of the enumerators (FIRST,

SECOND, . . .). All the tests passed. Can you see why? Can you also see why each of these

steps was necessary? The refactoring tool made sure that all previous callers of

weekInMonthToString now called toString on the weekInMonth enumerator because all enumerators

implement toString to simply return their names. . . .

Unfortunately, I was a bit too clever. As elegant as that wonderful chain of refactorings

was, I finally realized that the only users of this function were the tests I had just modified,

so I deleted the tests.

Fool me once, shame on you. Fool me twice, shame on me! So after determining that

nobody other than the tests called relativeToString (lines 765–781), I simply deleted the

function and its tests.

offsetToTarget += 7;

return plusDays(offsetToTarget);

}

public DayDate getNearestDayOfWeek(final Day targetDay) {

int offsetToThisWeeksTarget = targetDay.index - getDayOfWeek().index;

int offsetToFutureTarget = (offsetToThisWeeksTarget + 7) % 7;

int offsetToPreviousTarget = offsetToFutureTarget - 7;

if (offsetToFutureTarget > 3)

return plusDays(offsetToPreviousTarget);

else

return plusDays(offsetToFutureTarget);

}

public DayDate getEndOfMonth() {

Month month = getMonth();

int year = getYear();

int lastDay = lastDayOfMonth(month, year);

return DayDateFactory.makeDate(lastDay, month, year);

}

282 Chapter 16: Refactoring SerialDate

We have finally made it to the abstract methods of this abstract class. And the first one

is as appropriate as they come: toSerial (lines 838–844). Back on page 279 I had changed

the name to toOrdinal. Having looked at it in this context, I decided the name should be

changed to getOrdinalDay.

The next abstract method is toDate (lines 838–844). It converts a DayDate to a

java.util.Date. Why is this method abstract? If we look at its implementation in

SpreadsheetDate (lines 198–207, Listing B-5, page 382), we see that it doesn't depend on

anything in the implementation of that class [G6]. So I pushed it up.

The getYYYY, getMonth, and getDayOfMonth methods are nicely abstract. However, the

getDayOfWeek method is another one that should be pulled up from SpreadSheetDate

because it doesn't depend on anything that can't be found in DayDate [G6]. Or does it?

If you look carefully (line 247, Listing B-5, page 382), you'll see that the algorithm

implicitly depends on the origin of the ordinal day (in other words, the day of the week of

day 0). So even though this function has no physical dependencies that couldn't be moved

to DayDate, it does have a logical dependency.

Logical dependencies like this bother me [G22]. If something logical depends on

the implementation, then something physical should too. Also, it seems to me that the

algorithm itself could be generic with a much smaller portion of it dependent on the

implementation [G6].

So I created an abstract method in DayDate named getDayOfWeekForOrdinalZero and

implemented it in SpreadsheetDate to return Day.SATURDAY. Then I moved the getDayOfWeek

method up to DayDate and changed it to call getOrdinalDay and getDayOfWeekForOrdinal-

Zero.

As a side note, look carefully at the comment on line 895 through line 899. Was this

repetition really necessary? As usual, I deleted this comment along with all the others.

The next method is compare (lines 902–913). Again, this method is inappropriately

abstract [G6], so I pulled the implementation up into DayDate. Also, the name does not

communicate enough [N1]. This method actually returns the difference in days since the

argument. So I changed the name to daysSince. Also, I noted that there weren't any tests

for this method, so I wrote them.

The next six functions (lines 915–980) are all abstract methods that should be implemented

in DayDate. So I pulled them all up from SpreadsheetDate.

The last function, isInRange (lines 982–995) also needs to be pulled up and refactored.

The switch statement is a bit ugly [G23] and can be replaced by moving the cases

into the DateInterval enum.

public Day getDayOfWeek() {

Day startingDay = getDayOfWeekForOrdinalZero();

int startingOffset = startingDay.index - Day.SUNDAY.index;

return Day.make((getOrdinalDay() + startingOffset) % 7 + 1);

}

Then Make It Right 283

That brings us to the end of DayDate. So now we'll make one more pass over the whole

class to see how well it flows.

First, the opening comment is long out of date, so I shortened and improved it [C2].

Next, I moved all the remaining enums out into their own files [G12].

Next, I moved the static variable (dateFormatSymbols) and three static methods

(getMonthNames, isLeapYear, lastDayOfMonth) into a new class named DateUtil [G6].

I moved the abstract methods up to the top where they belong [G24].

I changed Month.make to Month.fromInt [N1] and did the same for all the other enums.

I also created a toInt() accessor for all the enums and made the index field private.

There was some interesting duplication [G5] in plusYears and plusMonths that I was

able to eliminate by extracting a new method named correctLastDayOfMonth, making the

all three methods much clearer.

I got rid of the magic number 1 [G25], replacing it with Month.JANUARY.toInt() or

Day.SUNDAY.toInt(), as appropriate. I spent a little time with SpreadsheetDate, cleaning up

the algorithms a bit. The end result is contained in Listing B-7, page 394, through

Listing B-16, page 405.

public enum DateInterval {

OPEN {

public boolean isIn(int d, int left, int right) {

return d > left && d < right;

}

},

CLOSED_LEFT {

public boolean isIn(int d, int left, int right) {

return d >= left && d < right;

}

},

CLOSED_RIGHT {

public boolean isIn(int d, int left, int right) {

return d > left && d <= right;

}

},

CLOSED {

public boolean isIn(int d, int left, int right) {

return d >= left && d <= right;

}

};

public abstract boolean isIn(int d, int left, int right);

}

public boolean isInRange(DayDate d1, DayDate d2, DateInterval interval) {

int left = Math.min(d1.getOrdinalDay(), d2.getOrdinalDay());

int right = Math.max(d1.getOrdinalDay(), d2.getOrdinalDay());

return interval.isIn(getOrdinalDay(), left, right);

}

284 Chapter 16: Refactoring SerialDate

Interestingly the code coverage in DayDate has decreased to 84.9 percent! This is not

because less functionality is being tested; rather it is because the class has shrunk so much

that the few uncovered lines have a greater weight. DayDate now has 45 out of 53 executable

statements covered by tests. The uncovered lines are so trivial that they weren't worth

testing.

Conclusion

So once again we've followed the Boy Scout Rule. We've checked the code in a bit cleaner

than when we checked it out. It took a little time, but it was worth it. Test coverage was

increased, some bugs were fixed, the code was clarified and shrunk. The next person to

look at this code will hopefully find it easier to deal with than we did. That person will also

probably be able to clean it up a bit more than we did.

Bibliography

[GOF]: Design Patterns: Elements of Reusable Object Oriented Software, Gamma et al.,

Addison-Wesley, 1996.

[Simmons04]: Hardcore Java, Robert Simmons, Jr., O'Reilly, 2004.

[Refactoring]: Refactoring: Improving the Design of Existing Code, Martin Fowler et al.,

Addison-Wesley, 1999.

[Beck97]: Smalltalk Best Practice Patterns, Kent Beck, Prentice Hall, 1997.

285

17

Smells and Heuristics

In his wonderful book Refactoring,1 Martin Fowler identified many different "Code

Smells." The list that follows includes many of Martin's smells and adds many more of my

own. It also includes other pearls and heuristics that I use to practice my trade.

1. [Refactoring].

286 Chapter 17: Smells and Heuristics

I compiled this list by walking through several different programs and refactoring

them. As I made each change, I asked myself why I made that change and then wrote the

reason down here. The result is a rather long list of things that smell bad to me when I read

code.

This list is meant to be read from top to bottom and also to be used as a reference.

There is a cross-reference for each heuristic that shows you where it is referenced in the

rest of the text in "Appendix C" on page 409.

Comments

C1: Inappropriate Information

It is inappropriate for a comment to hold information better held in a different kind of system

such as your source code control system, your issue tracking system, or any other

record-keeping system. Change histories, for example, just clutter up source files with

volumes of historical and uninteresting text. In general, meta-data such as authors, lastmodified-

date, SPR number, and so on should not appear in comments. Comments should

be reserved for technical notes about the code and design.

C2: Obsolete Comment

A comment that has gotten old, irrelevant, and incorrect is obsolete. Comments get old

quickly. It is best not to write a comment that will become obsolete. If you find an obsolete

comment, it is best to update it or get rid of it as quickly as possible. Obsolete comments

tend to migrate away from the code they once described. They become floating islands of

irrelevance and misdirection in the code.

C3: Redundant Comment

A comment is redundant if it describes something that adequately describes itself. For

example:

i++; // increment i

Another example is a Javadoc that says nothing more than (or even less than) the function

signature:

/**

* @param sellRequest

* @return

* @throws ManagedComponentException

*/

public SellResponse beginSellItem(SellRequest sellRequest)

throws ManagedComponentException

Comments should say things that the code cannot say for itself.

Environment 287

C4: Poorly Written Comment

A comment worth writing is worth writing well. If you are going to write a comment,

take the time to make sure it is the best comment you can write. Choose your words

carefully. Use correct grammar and punctuation. Don't ramble. Don't state the obvious.

Be brief.

C5: Commented-Out Code

It makes me crazy to see stretches of code that are commented out. Who knows how old it

is? Who knows whether or not it's meaningful? Yet no one will delete it because everyone

assumes someone else needs it or has plans for it.

That code sits there and rots, getting less and less relevant with every passing day. It

calls functions that no longer exist. It uses variables whose names have changed. It follows

conventions that are long obsolete. It pollutes the modules that contain it and distracts the

people who try to read it. Commented-out code is an abomination.

When you see commented-out code, delete it! Don't worry, the source code control

system still remembers it. If anyone really needs it, he or she can go back and check out a

previous version. Don't suffer commented-out code to survive.

Environment

E1: Build Requires More Than One Step

Building a project should be a single trivial operation. You should not have to check many

little pieces out from source code control. You should not need a sequence of arcane commands

or context dependent scripts in order to build the individual elements. You should

not have to search near and far for all the various little extra JARs, XML files, and other

artifacts that the system requires. You should be able to check out the system with one simple

command and then issue one other simple command to build it.

svn get mySystem

cd mySystem

ant all

E2: Tests Require More Than One Step

You should be able to run all the unit tests with just one command. In the best case you

can run all the tests by clicking on one button in your IDE. In the worst case you should

be able to issue a single simple command in a shell. Being able to run all the tests is so

fundamental and so important that it should be quick, easy, and obvious to do.

288 Chapter 17: Smells and Heuristics

Functions

F1: Too Many Arguments

Functions should have a small number of arguments. No argument is best, followed by

one, two, and three. More than three is very questionable and should be avoided with prejudice.

(See "Function Arguments" on page 40.)

F2: Output Arguments

Output arguments are counterintuitive. Readers expect arguments to be inputs, not outputs.

If your function must change the state of something, have it change the state of the

object it is called on. (See "Output Arguments" on page 45.)

F3: Flag Arguments

Boolean arguments loudly declare that the function does more than one thing. They are

confusing and should be eliminated. (See "Flag Arguments" on page 41.)

F4: Dead Function

Methods that are never called should be discarded. Keeping dead code around is wasteful.

Don't be afraid to delete the function. Remember, your source code control system still

remembers it.

General

G1: Multiple Languages in One Source File

Today's modern programming environments make it possible to put many different languages

into a single source file. For example, a Java source file might contain snippets of XML,

HTML, YAML, JavaDoc, English, JavaScript, and so on. For another example, in addition to

HTML a JSP file might contain Java, a tag library syntax, English comments, Javadocs,

XML, JavaScript, and so forth. This is confusing at best and carelessly sloppy at worst.

The ideal is for a source file to contain one, and only one, language. Realistically, we

will probably have to use more than one. But we should take pains to minimize both the

number and extent of extra languages in our source files.

G2: Obvious Behavior Is Unimplemented

Following "The Principle of Least Surprise,"2 any function or class should implement the

behaviors that another programmer could reasonably expect. For example, consider a

function that translates the name of a day to an enum that represents the day.

2. Or "The Principle of Least Astonishment": http://en.wikipedia.org/wiki/

Principle_of_least_astonishment

General 289

Day day = DayDate.StringToDay(String dayName);

We would expect the string "Monday" to be translated to Day.MONDAY. We would also expect

the common abbreviations to be translated, and we would expect the function to ignore

case.

When an obvious behavior is not implemented, readers and users of the code can no

longer depend on their intuition about function names. They lose their trust in the original

author and must fall back on reading the details of the code.

G3: Incorrect Behavior at the Boundaries

It seems obvious to say that code should behave correctly. The problem is that we seldom

realize just how complicated correct behavior is. Developers often write functions that

they think will work, and then trust their intuition rather than going to the effort to prove

that their code works in all the corner and boundary cases.

There is no replacement for due diligence. Every boundary condition, every corner

case, every quirk and exception represents something that can confound an elegant and

intuitive algorithm. Don't rely on your intuition. Look for every boundary condition and

write a test for it.

G4: Overridden Safeties

Chernobyl melted down because the plant manager overrode each of the safety mechanisms

one by one. The safeties were making it inconvenient to run an experiment. The

result was that the experiment did not get run, and the world saw it's first major civilian

nuclear catastrophe.

It is risky to override safeties. Exerting manual control over serialVersionUID may be

necessary, but it is always risky. Turning off certain compiler warnings (or all warnings!)

may help you get the build to succeed, but at the risk of endless debugging sessions. Turning

off failing tests and telling yourself you'll get them to pass later is as bad as pretending

your credit cards are free money.

G5: Duplication

This is one of the most important rules in this book, and you should take it very seriously.

Virtually every author who writes about software design mentions this rule. Dave Thomas

and Andy Hunt called it the DRY3 principle (Don't Repeat Yourself). Kent Beck made it

one of the core principles of Extreme Programming and called it: "Once, and only once."

Ron Jeffries ranks this rule second, just below getting all the tests to pass.

Every time you see duplication in the code, it represents a missed opportunity for

abstraction. That duplication could probably become a subroutine or perhaps another

class outright. By folding the duplication into such an abstraction, you increase the vocabulary

of the language of your design. Other programmers can use the abstract facilities

3. [PRAG].

290 Chapter 17: Smells and Heuristics

you create. Coding becomes faster and less error prone because you have raised the

abstraction level.

The most obvious form of duplication is when you have clumps of identical code that

look like some programmers went wild with the mouse, pasting the same code over and

over again. These should be replaced with simple methods.

A more subtle form is the switch/case or if/else chain that appears again and again in

various modules, always testing for the same set of conditions. These should be replaced

with polymorphism.

Still more subtle are the modules that have similar algorithms, but that don't share

similar lines of code. This is still duplication and should be addressed by using the TEMPLATE

METHOD,4 or STRATEGY5 pattern.

Indeed, most of the design patterns that have appeared in the last fifteen years are simply

well-known ways to eliminate duplication. So too the Codd Normal Forms are a strategy

for eliminating duplication in database schemae. OO itself is a strategy for organizing

modules and eliminating duplication. Not surprisingly, so is structured programming.

I think the point has been made. Find and eliminate duplication wherever you can.

G6: Code at Wrong Level of Abstraction

It is important to create abstractions that separate higher level general concepts from lower

level detailed concepts. Sometimes we do this by creating abstract classes to hold the

higher level concepts and derivatives to hold the lower level concepts. When we do this,

we need to make sure that the separation is complete. We want all the lower level concepts

to be in the derivatives and all the higher level concepts to be in the base class.

For example, constants, variables, or utility functions that pertain only to the detailed

implementation should not be present in the base class. The base class should know nothing

about them.

This rule also pertains to source files, components, and modules. Good software

design requires that we separate concepts at different levels and place them in different

containers. Sometimes these containers are base classes or derivatives and sometimes they

are source files, modules, or components. Whatever the case may be, the separation needs

to be complete. We don't want lower and higher level concepts mixed together.

Consider the following code:

public interface Stack {

Object pop() throws EmptyException;

void push(Object o) throws FullException;

double percentFull();

4. [GOF].

5. [GOF].

General 291

class EmptyException extends Exception {}

class FullException extends Exception {}

}

The percentFull function is at the wrong level of abstraction. Although there are

many implementations of Stack where the concept of fullness is reasonable, there are other

implementations that simply could not know how full they are. So the function would be

better placed in a derivative interface such as BoundedStack.

Perhaps you are thinking that the implementation could just return zero if the stack

were boundless. The problem with that is that no stack is truly boundless. You cannot

really prevent an OutOfMemoryException by checking for

stack.percentFull() < 50.0.

Implementing the function to return 0 would be telling a lie.

The point is that you cannot lie or fake your way out of a misplaced abstraction. Isolating

abstractions is one of the hardest things that software developers do, and there is no

quick fix when you get it wrong.

G7: Base Classes Depending on Their Derivatives

The most common reason for partitioning concepts into base and derivative classes is so

that the higher level base class concepts can be independent of the lower level derivative

class concepts. Therefore, when we see base classes mentioning the names of their derivatives,

we suspect a problem. In general, base classes should know nothing about their

derivatives.

There are exceptions to this rule, of course. Sometimes the number of derivatives is

strictly fixed, and the base class has code that selects between the derivatives. We see this a

lot in finite state machine implementations. However, in that case the derivatives and base

class are strongly coupled and always deploy together in the same jar file. In the general

case we want to be able to deploy derivatives and bases in different jar files.

Deploying derivatives and bases in different jar files and making sure the base jar files

know nothing about the contents of the derivative jar files allow us to deploy our systems

in discrete and independent components. When such components are modified, they can

be redeployed without having to redeploy the base components. This means that the

impact of a change is greatly lessened, and maintaining systems in the field is made much

simpler.

G8: Too Much Information

Well-defined modules have very small interfaces that allow you to do a lot with a little.

Poorly defined modules have wide and deep interfaces that force you to use many different

gestures to get simple things done. A well-defined interface does not offer very many functions

to depend upon, so coupling is low. A poorly defined interface provides lots of functions

that you must call, so coupling is high.

292 Chapter 17: Smells and Heuristics

Good software developers learn to limit what they expose at the interfaces of their

classes and modules. The fewer methods a class has, the better. The fewer variables a function

knows about, the better. The fewer instance variables a class has, the better.

Hide your data. Hide your utility functions. Hide your constants and your temporaries.

Don't create classes with lots of methods or lots of instance variables. Don't create lots of

protected variables and functions for your subclasses. Concentrate on keeping interfaces

very tight and very small. Help keep coupling low by limiting information.

G9: Dead Code

Dead code is code that isn't executed. You find it in the body of an if statement that checks

for a condition that can't happen. You find it in the catch block of a try that never throws.

You find it in little utility methods that are never called or switch/case conditions that

never occur.

The problem with dead code is that after awhile it starts to smell. The older it is, the

stronger and sourer the odor becomes. This is because dead code is not completely

updated when designs change. It still compiles, but it does not follow newer conventions or

rules. It was written at a time when the system was different. When you find dead code, do

the right thing. Give it a decent burial. Delete it from the system.

G10: Vertical Separation

Variables and function should be defined close to where they are used. Local variables

should be declared just above their first usage and should have a small vertical scope. We

don't want local variables declared hundreds of lines distant from their usages.

Private functions should be defined just below their first usage. Private functions

belong to the scope of the whole class, but we'd still like to limit the vertical distance

between the invocations and definitions. Finding a private function should just be a matter

of scanning downward from the first usage.

G11: Inconsistency

If you do something a certain way, do all similar things in the same way. This goes back

to the principle of least surprise. Be careful with the conventions you choose, and once

chosen, be careful to continue to follow them.

If within a particular function you use a variable named response to hold an

HttpServletResponse, then use the same variable name consistently in the other functions

that use HttpServletResponse objects. If you name a method processVerificationRequest,

then use a similar name, such as processDeletionRequest, for the methods that process

other kinds of requests.

Simple consistency like this, when reliably applied, can make code much easier to

read and modify.

General 293

G12: Clutter

Of what use is a default constructor with no implementation? All it serves to do is clutter

up the code with meaningless artifacts. Variables that aren't used, functions that are never

called, comments that add no information, and so forth. All these things are clutter and

should be removed. Keep your source files clean, well organized, and free of clutter.

G13: Artificial Coupling

Things that don't depend upon each other should not be artificially coupled. For example,

general enums should not be contained within more specific classes because this forces the

whole application to know about these more specific classes. The same goes for general

purpose static functions being declared in specific classes.

In general an artificial coupling is a coupling between two modules that serves no

direct purpose. It is a result of putting a variable, constant, or function in a temporarily

convenient, though inappropriate, location. This is lazy and careless.

Take the time to figure out where functions, constants, and variables ought to be

declared. Don't just toss them in the most convenient place at hand and then leave them

there.

G14: Feature Envy

This is one of Martin Fowler's code smells.6 The methods of a class should be interested in

the variables and functions of the class they belong to, and not the variables and functions

of other classes. When a method uses accessors and mutators of some other object to

manipulate the data within that object, then it envies the scope of the class of that other

object. It wishes that it were inside that other class so that it could have direct access to the

variables it is manipulating. For example:

public class HourlyPayCalculator {

public Money calculateWeeklyPay(HourlyEmployee e) {

int tenthRate = e.getTenthRate().getPennies();

int tenthsWorked = e.getTenthsWorked();

int straightTime = Math.min(400, tenthsWorked);

int overTime = Math.max(0, tenthsWorked - straightTime);

int straightPay = straightTime * tenthRate;

int overtimePay = (int)Math.round(overTime*tenthRate*1.5);

return new Money(straightPay + overtimePay);

}

}

The calculateWeeklyPay method reaches into the HourlyEmployee object to get the data on

which it operates. The calculateWeeklyPay method envies the scope of HourlyEmployee. It

"wishes" that it could be inside HourlyEmployee.

6. [Refactoring].

294 Chapter 17: Smells and Heuristics

All else being equal, we want to eliminate Feature Envy because it exposes the internals

of one class to another. Sometimes, however, Feature Envy is a necessary evil. Consider the

following:

public class HourlyEmployeeReport {

private HourlyEmployee employee ;

public HourlyEmployeeReport(HourlyEmployee e) {

this.employee = e;

}

String reportHours() {

return String.format(

"Name: %s\tHours:%d.%1d

",

employee.getName(),

employee.getTenthsWorked()/10,

employee.getTenthsWorked()%10);

}

}

Clearly, the reportHours method envies the HourlyEmployee class. On the other hand, we

don't want HourlyEmployee to have to know about the format of the report. Moving that format

string into the HourlyEmployee class would violate several principles of object oriented

design.7 It would couple HourlyEmployee to the format of the report, exposing it to changes

in that format.

G15: Selector Arguments

There is hardly anything more abominable than a dangling false argument at the end of a

function call. What does it mean? What would it change if it were true? Not only is the

purpose of a selector argument difficult to remember, each selector argument combines

many functions into one. Selector arguments are just a lazy way to avoid splitting a large

function into several smaller functions. Consider:

public int calculateWeeklyPay(boolean overtime) {

int tenthRate = getTenthRate();

int tenthsWorked = getTenthsWorked();

int straightTime = Math.min(400, tenthsWorked);

int overTime = Math.max(0, tenthsWorked - straightTime);

int straightPay = straightTime * tenthRate;

double overtimeRate = overtime ? 1.5 : 1.0 * tenthRate;

int overtimePay = (int)Math.round(overTime*overtimeRate);

return straightPay + overtimePay;

}

You call this function with a true if overtime is paid as time and a half, and with a

false if overtime is paid as straight time. It's bad enough that you must remember what

calculateWeeklyPay(false) means whenever you happen to stumble across it. But the

7. Specifically, the Single Responsibility Principle, the Open Closed Principle, and the Common Closure Principle. See [PPP].

General 295

real shame of a function like this is that the author missed the opportunity to write the

following:

public int straightPay() {

return getTenthsWorked() * getTenthRate();

}

public int overTimePay() {

int overTimeTenths = Math.max(0, getTenthsWorked() - 400);

int overTimePay = overTimeBonus(overTimeTenths);

return straightPay() + overTimePay;

}

private int overTimeBonus(int overTimeTenths) {

double bonus = 0.5 * getTenthRate() * overTimeTenths;

return (int) Math.round(bonus);

}

Of course, selectors need not be boolean. They can be enums, integers, or any other

type of argument that is used to select the behavior of the function. In general it is better to

have many functions than to pass some code into a function to select the behavior.

G16: Obscured Intent

We want code to be as expressive as possible. Run-on expressions, Hungarian notation,

and magic numbers all obscure the author's intent. For example, here is the overTimePay

function as it might have appeared:

public int m_otCalc() {

return iThsWkd * iThsRte +

(int) Math.round(0.5 * iThsRte *

Math.max(0, iThsWkd - 400)

);

}

Small and dense as this might appear, it's also virtually impenetrable. It is worth taking

the time to make the intent of our code visible to our readers.

G17: Misplaced Responsibility

One of the most important decisions a software developer can make is where to put code.

For example, where should the PI constant go? Should it be in the Math class? Perhaps it

belongs in the Trigonometry class? Or maybe in the Circle class?

The principle of least surprise comes into play here. Code should be placed where a

reader would naturally expect it to be. The PI constant should go where the trig functions

are declared. The OVERTIME_RATE constant should be declared in the HourlyPay-

Calculator class.

Sometimes we get "clever" about where to put certain functionality. We'll put it in a

function that's convenient for us, but not necessarily intuitive to the reader. For example,

perhaps we need to print a report with the total of hours that an employee worked. We

296 Chapter 17: Smells and Heuristics

could sum up those hours in the code that prints the report, or we could try to keep a running

total in the code that accepts time cards.

One way to make this decision is to look at the names of the functions. Let's say that

our report module has a function named getTotalHours. Let's also say that the module that

accepts time cards has a saveTimeCard function. Which of these two functions, by it's name,

implies that it calculates the total? The answer should be obvious.

Clearly, there are sometimes performance reasons why the total should be calculated

as time cards are accepted rather than when the report is printed. That's fine, but the names

of the functions ought to reflect this. For example, there should be a computeRunning-

TotalOfHours function in the timecard module.

G18: Inappropriate Static

Math.max(double a, double b) is a good static method. It does not operate on a single

instance; indeed, it would be silly to have to say new Math().max(a,b) or even a.max(b).

All the data that max uses comes from its two arguments, and not from any "owning"

object. More to the point, there is almost no chance that we'd want Math.max to be

polymorphic.

Sometimes, however, we write static functions that should not be static. For example,

consider:

HourlyPayCalculator.calculatePay(employee, overtimeRate).

Again, this seems like a reasonable static function. It doesn't operate on any particular

object and gets all it's data from it's arguments. However, there is a reasonable chance that

we'll want this function to be polymorphic. We may wish to implement several different

algorithms for calculating hourly pay, for example, OvertimeHourlyPayCalculator and

StraightTimeHourlyPayCalculator. So in this case the function should not be static. It

should be a nonstatic member function of Employee.

In general you should prefer nonstatic methods to static methods. When in doubt,

make the function nonstatic. If you really want a function to be static, make sure that there

is no chance that you'll want it to behave polymorphically.

G19: Use Explanatory Variables

Kent Beck wrote about this in his great book Smalltalk Best Practice Patterns8 and again

more recently in his equally great book Implementation Patterns.9 One of the more powerful

ways to make a program readable is to break the calculations up into intermediate values

that are held in variables with meaningful names.

8. [Beck97], p. 108.

9. [Beck07].

General 297

Consider this example from FitNesse:

Matcher match = headerPattern.matcher(line);

if(match.find())

{

String key = match.group(1);

String value = match.group(2);

headers.put(key.toLowerCase(), value);

}

The simple use of explanatory variables makes it clear that the first matched group is

the key, and the second matched group is the value.

It is hard to overdo this. More explanatory variables are generally better than fewer. It

is remarkable how an opaque module can suddenly become transparent simply by breaking

the calculations up into well-named intermediate values.

G20: Function Names Should Say What They Do

Look at this code:

Date newDate = date.add(5);

Would you expect this to add five days to the date? Or is it weeks, or hours? Is the date

instance changed or does the function just return a new Date without changing the old one?

You can't tell from the call what the function does.

If the function adds five days to the date and changes the date, then it should be called

addDaysTo or increaseByDays. If, on the other hand, the function returns a new date that is

five days later but does not change the date instance, it should be called daysLater or

daysSince.

If you have to look at the implementation (or documentation) of the function to know

what it does, then you should work to find a better name or rearrange the functionality so

that it can be placed in functions with better names.

G21: Understand the Algorithm

Lots of very funny code is written because people don't take the time to understand the

algorithm. They get something to work by plugging in enough if statements and flags,

without really stopping to consider what is really going on.

Programming is often an exploration. You think you know the right algorithm for

something, but then you wind up fiddling with it, prodding and poking at it, until you get it

to "work." How do you know it "works"? Because it passes the test cases you can think of.

There is nothing wrong with this approach. Indeed, often it is the only way to get a

function to do what you think it should. However, it is not sufficient to leave the quotation

marks around the word "work."

298 Chapter 17: Smells and Heuristics

Before you consider yourself to be done with a function, make sure you understand

how it works. It is not good enough that it passes all the tests. You must know10 that the

solution is correct.

Often the best way to gain this knowledge and understanding is to refactor the function

into something that is so clean and expressive that it is obvious how it works.

G22: Make Logical Dependencies Physical

If one module depends upon another, that dependency should be physical, not just logical.

The dependent module should not make assumptions (in other words, logical dependencies)

about the module it depends upon. Rather it should explicitly ask that module for all

the information it depends upon.

For example, imagine that you are writing a function that prints a plain text report of

hours worked by employees. One class named HourlyReporter gathers all the data into a

convenient form and then passes it to HourlyReportFormatter to print it. (See Listing 17-1.)

10. There is a difference between knowing how the code works and knowing whether the algorithm will do the job required of it.

Being unsure that an algorithm is appropriate is often a fact of life. Being unsure what your code does is just laziness.

Listing 17-1

HourlyReporter.java

public class HourlyReporter {

private HourlyReportFormatter formatter;

private List<LineItem> page;

private final int PAGE_SIZE = 55;

public HourlyReporter(HourlyReportFormatter formatter) {

this.formatter = formatter;

page = new ArrayList<LineItem>();

}

public void generateReport(List<HourlyEmployee> employees) {

for (HourlyEmployee e : employees) {

addLineItemToPage(e);

if (page.size() == PAGE_SIZE)

printAndClearItemList();

}

if (page.size() > 0)

printAndClearItemList();

}

private void printAndClearItemList() {

formatter.format(page);

page.clear();

}

private void addLineItemToPage(HourlyEmployee e) {

LineItem item = new LineItem();

item.name = e.getName();

item.hours = e.getTenthsWorked() / 10;

General 299

This code has a logical dependency that has not been physicalized. Can you spot it? It

is the constant PAGE_SIZE. Why should the HourlyReporter know the size of the page? Page

size should be the responsibility of the HourlyReportFormatter.

The fact that PAGE_SIZE is declared in HourlyReporter represents a misplaced

responsibility [G17] that causes HourlyReporter to assume that it knows what the page size

ought to be. Such an assumption is a logical dependency. HourlyReporter depends on the

fact that HourlyReportFormatter can deal with page sizes of 55. If some implementation of

HourlyReportFormatter could not deal with such sizes, then there would be an error.

We can physicalize this dependency by creating a new method in HourlyReport-

Formatter named getMaxPageSize(). HourlyReporter will then call that function rather than

using the PAGE_SIZE constant.

G23: Prefer Polymorphism to If/Else or Switch/Case

This might seem a strange suggestion given the topic of Chapter 6. After all, in that chapter I

make the point that switch statements are probably appropriate in the parts of the system

where adding new functions is more likely than adding new types.

First, most people use switch statements because it's the obvious brute force solution,

not because it's the right solution for the situation. So this heuristic is here to remind us to

consider polymorphism before using a switch.

Second, the cases where functions are more volatile than types are relatively rare. So

every switch statement should be suspect.

I use the following "ONE SWITCH" rule: There may be no more than one switch statement

for a given type of selection. The cases in that switch statement must create polymorphic

objects that take the place of other such switch statements in the rest of the system.

G24: Follow Standard Conventions

Every team should follow a coding standard based on common industry norms. This coding

standard should specify things like where to declare instance variables; how to name

classes, methods, and variables; where to put braces; and so on. The team should not need

a document to describe these conventions because their code provides the examples.

item.tenths = e.getTenthsWorked() % 10;

page.add(item);

}

public class LineItem {

public String name;

public int hours;

public int tenths;

}

}

Listing 17-1 (continued)

HourlyReporter.java

300 Chapter 17: Smells and Heuristics

Everyone on the team should follow these conventions. This means that each team

member must be mature enough to realize that it doesn't matter a whit where you put your

braces so long as you all agree on where to put them.

If you would like to know what conventions I follow, you'll see them in the refactored

code in Listing B-7 on page 394, through Listing B-14.

G25: Replace Magic Numbers with Named Constants

This is probably one of the oldest rules in software development. I remember reading it in the

late sixties in introductory COBOL, FORTRAN, and PL/1 manuals. In general it is a bad

idea to have raw numbers in your code. You should hide them behind well-named constants.

For example, the number 86,400 should be hidden behind the constant

SECONDS_PER_DAY. If you are printing 55 lines per page, then the constant 55 should be hidden

behind the constant LINES_PER_PAGE.

Some constants are so easy to recognize that they don't always need a named constant

to hide behind so long as they are used in conjunction with very self-explanatory code. For

example:

double milesWalked = feetWalked/5280.0;

int dailyPay = hourlyRate * 8;

double circumference = radius * Math.PI * 2;

Do we really need the constants FEET_PER_MILE, WORK_HOURS_PER_DAY, and TWO in the

above examples? Clearly, the last case is absurd. There are some formulae in which constants

are simply better written as raw numbers. You might quibble about the

WORK_HOURS_PER_DAY case because the laws or conventions might change. On the other

hand, that formula reads so nicely with the 8 in it that I would be reluctant to add 17 extra

characters to the readers' burden. And in the FEET_PER_MILE case, the number 5280 is so

very well known and so unique a constant that readers would recognize it even if it stood

alone on a page with no context surrounding it.

Constants like 3.141592653589793 are also very well known and easily recognizable.

However, the chance for error is too great to leave them raw. Every time someone sees

3.1415927535890793, they know that it is p, and so they fail to scrutinize it. (Did you

catch the single-digit error?) We also don't want people using 3.14, 3.14159, 3.142, and so

forth. Therefore, it is a good thing that Math.PI has already been defined for us.

The term "Magic Number" does not apply only to numbers. It applies to any token

that has a value that is not self-describing. For example:

assertEquals(7777, Employee.find("John Doe").employeeNumber());

There are two magic numbers in this assertion. The first is obviously 7777, though

what it might mean is not obvious. The second magic number is "John Doe," and again the

intent is not clear.

It turns out that "John Doe" is the name of employee #7777 in a well-known test database

created by our team. Everyone in the team knows that when you connect to this

General 301

database, it will have several employees already cooked into it with well-known values

and attributes. It also turns out that "John Doe" represents the sole hourly employee in

that test database. So this test should really read:

assertEquals(

HOURLY_EMPLOYEE_ID,

Employee.find(HOURLY_EMPLOYEE_NAME).employeeNumber());

G26: Be Precise

Expecting the first match to be the only match to a query is probably naive. Using floating

point numbers to represent currency is almost criminal. Avoiding locks and/or transaction

management because you don't think concurrent update is likely is lazy at best. Declaring

a variable to be an ArrayList when a List will due is overly constraining. Making all variables

protected by default is not constraining enough.

When you make a decision in your code, make sure you make it precisely. Know why

you have made it and how you will deal with any exceptions. Don't be lazy about the precision

of your decisions. If you decide to call a function that might return null, make sure

you check for null. If you query for what you think is the only record in the database,

make sure your code checks to be sure there aren't others. If you need to deal with currency,

use integers11 and deal with rounding appropriately. If there is the possibility of

concurrent update, make sure you implement some kind of locking mechanism.

Ambiguities and imprecision in code are either a result of disagreements or laziness.

In either case they should be eliminated.

G27: Structure over Convention

Enforce design decisions with structure over convention. Naming conventions are good,

but they are inferior to structures that force compliance. For example, switch/cases with

nicely named enumerations are inferior to base classes with abstract methods. No one is

forced to implement the switch/case statement the same way each time; but the base

classes do enforce that concrete classes have all abstract methods implemented.

G28: Encapsulate Conditionals

Boolean logic is hard enough to understand without having to see it in the context of an if

or while statement. Extract functions that explain the intent of the conditional.

For example:

if (shouldBeDeleted(timer))

is preferable to

if (timer.hasExpired() && !timer.isRecurrent())

11. Or better yet, a Money class that uses integers.

302 Chapter 17: Smells and Heuristics

G29: Avoid Negative Conditionals

Negatives are just a bit harder to understand than positives. So, when possible, conditionals

should be expressed as positives. For example:

if (buffer.shouldCompact())

is preferable to

if (!buffer.shouldNotCompact())

G30: Functions Should Do One Thing

It is often tempting to create functions that have multiple sections that perform a series of

operations. Functions of this kind do more than one thing, and should be converted into

many smaller functions, each of which does one thing.

For example:

public void pay() {

for (Employee e : employees) {

if (e.isPayday()) {

Money pay = e.calculatePay();

e.deliverPay(pay);

}

}

}

This bit of code does three things. It loops over all the employees, checks to see whether

each employee ought to be paid, and then pays the employee. This code would be better

written as:

public void pay() {

for (Employee e : employees)

payIfNecessary(e);

}

private void payIfNecessary(Employee e) {

if (e.isPayday())

calculateAndDeliverPay(e);

}

private void calculateAndDeliverPay(Employee e) {

Money pay = e.calculatePay();

e.deliverPay(pay);

}

Each of these functions does one thing. (See "Do One Thing" on page 35.)

G31: Hidden Temporal Couplings

Temporal couplings are often necessary, but you should not hide the coupling. Structure

the arguments of your functions such that the order in which they should be called is obvious.

Consider the following:

General 303

public class MoogDiver {

Gradient gradient;

List<Spline> splines;

public void dive(String reason) {

saturateGradient();

reticulateSplines();

diveForMoog(reason);

}

...

}

The order of the three functions is important. You must saturate the gradient before you

can reticulate the splines, and only then can you dive for the moog. Unfortunately, the code

does not enforce this temporal coupling. Another programmer could call reticulate-

Splines before saturateGradient was called, leading to an UnsaturatedGradientException.

A better solution is:

public class MoogDiver {

Gradient gradient;

List<Spline> splines;

public void dive(String reason) {

Gradient gradient = saturateGradient();

List<Spline> splines = reticulateSplines(gradient);

diveForMoog(splines, reason);

}

...

}

This exposes the temporal coupling by creating a bucket brigade. Each function produces a

result that the next function needs, so there is no reasonable way to call them out of order.

You might complain that this increases the complexity of the functions, and you'd be

right. But that extra syntactic complexity exposes the true temporal complexity of the situation.

Note that I left the instance variables in place. I presume that they are needed by private

methods in the class. Even so, I want the arguments in place to make the temporal

coupling explicit.

G32: Don't Be Arbitrary

Have a reason for the way you structure your code, and make sure that reason is communicated

by the structure of the code. If a structure appears arbitrary, others will feel empowered

to change it. If a structure appears consistently throughout the system, others will use it

and preserve the convention. For example, I was recently merging changes to FitNesse and

discovered that one of our committers had done this:

public class AliasLinkWidget extends ParentWidget

{

public static class VariableExpandingWidgetRoot {

...

...

}

304 Chapter 17: Smells and Heuristics

The problem with this was that VariableExpandingWidgetRoot had no need to be

inside the scope of AliasLinkWidget. Moreover, other unrelated classes made use of

AliasLinkWidget.VariableExpandingWidgetRoot. These classes had no need to know

about AliasLinkWidget.

Perhaps the programmer had plopped the VariableExpandingWidgetRoot into

AliasWidget as a matter of convenience, or perhaps he thought it really needed to be

scoped inside AliasWidget. Whatever the reason, the result wound up being arbitrary. Public

classes that are not utilities of some other class should not be scoped inside another

class. The convention is to make them public at the top level of their package.

G33: Encapsulate Boundary Conditions

Boundary conditions are hard to keep track of. Put the processing for them in one place.

Don't let them leak all over the code. We don't want swarms of +1s and -1s scattered hither

and yon. Consider this simple example from FIT:

if(level + 1 < tags.length)

{

parts = new Parse(body, tags, level + 1, offset + endTag);

body = null;

}

Notice that level+1 appears twice. This is a boundary condition that should be encapsulated

within a variable named something like nextLevel.

int nextLevel = level + 1;

if(nextLevel < tags.length)

{

parts = new Parse(body, tags, nextLevel, offset + endTag);

body = null;

}

G34: Functions Should Descend Only One Level of Abstraction

The statements within a function should all be written at the same level of abstraction,

which should be one level below the operation described by the name of the function. This

may be the hardest of these heuristics to interpret and follow. Though the idea is plain

enough, humans are just far too good at seamlessly mixing levels of abstraction. Consider,

for example, the following code taken from FitNesse:

public String render() throws Exception

{

StringBuffer html = new StringBuffer("<hr");

if(size > 0)

html.append(" size=\"").append(size + 1).append("\"");

html.append(">");

return html.toString();

}

General 305

A moment's study and you can see what's going on. This function constructs the HTML

tag that draws a horizontal rule across the page. The height of that rule is specified in the

size variable.

Now look again. This method is mixing at least two levels of abstraction. The first is

the notion that a horizontal rule has a size. The second is the syntax of the HR tag itself.

This code comes from the HruleWidget module in FitNesse. This module detects a row of

four or more dashes and converts it into the appropriate HR tag. The more dashes, the

larger the size.

I refactored this bit of code as follows. Note that I changed the name of the size field

to reflect its true purpose. It held the number of extra dashes.

public String render() throws Exception

{

HtmlTag hr = new HtmlTag("hr");

if (extraDashes > 0)

hr.addAttribute("size", hrSize(extraDashes));

return hr.html();

}

private String hrSize(int height)

{

int hrSize = height + 1;

return String.format("%d", hrSize);

}

This change separates the two levels of abstraction nicely. The render function simply constructs

an HR tag, without having to know anything about the HTML syntax of that tag.

The HtmlTag module takes care of all the nasty syntax issues.

Indeed, by making this change I caught a subtle error. The original code did not put

the closing slash on the HR tag, as the XHTML standard would have it. (In other words, it

emitted <hr> instead of <hr/>.) The HtmlTag module had been changed to conform to

XHTML long ago.

Separating levels of abstraction is one of the most important functions of refactoring,

and it's one of the hardest to do well. As an example, look at the code below. This

was my first attempt at separating the abstraction levels in the HruleWidget.render

method.

public String render() throws Exception

{

HtmlTag hr = new HtmlTag("hr");

if (size > 0) {

hr.addAttribute("size", ""+(size+1));

}

return hr.html();

}

My goal, at this point, was to create the necessary separation and get the tests to pass.

I accomplished that goal easily, but the result was a function that still had mixed levels

of abstraction. In this case the mixed levels were the construction of the HR tag and the

306 Chapter 17: Smells and Heuristics

interpretation and formatting of the size variable. This points out that when you break a

function along lines of abstraction, you often uncover new lines of abstraction that were

obscured by the previous structure.

G35: Keep Configurable Data at High Levels

If you have a constant such as a default or configuration value that is known and expected

at a high level of abstraction, do not bury it in a low-level function. Expose it as an argument

to that low-level function called from the high-level function. Consider the following

code from FitNesse:

public static void main(String[] args) throws Exception

{

Arguments arguments = parseCommandLine(args);

...

}

public class Arguments

{

public static final String DEFAULT_PATH = ".";

public static final String DEFAULT_ROOT = "FitNesseRoot";

public static final int DEFAULT_PORT = 80;

public static final int DEFAULT_VERSION_DAYS = 14;

...

}

The command-line arguments are parsed in the very first executable line of FitNesse. The

default values of those arguments are specified at the top of the Argument class. You don't

have to go looking in low levels of the system for statements like this one:

if (arguments.port == 0) // use 80 by default

The configuration constants reside at a very high level and are easy to change. They get

passed down to the rest of the application. The lower levels of the application do not own

the values of these constants.

G36: Avoid Transitive Navigation

In general we don't want a single module to know much about its collaborators. More specifically,

if A collaborates with B, and B collaborates with C, we don't want modules that use

A to know about C. (For example, we don't want a.getB().getC().doSomething();.)

This is sometimes called the Law of Demeter. The Pragmatic Programmers call it

"Writing Shy Code."12 In either case it comes down to making sure that modules know

only about their immediate collaborators and do not know the navigation map of the whole

system.

If many modules used some form of the statement a.getB().getC(), then it would be

difficult to change the design and architecture to interpose a Q between B and C. You'd

12. [PRAG], p. 138.

Java 307

have to find every instance of a.getB().getC() and convert it to a.getB().getQ().getC().

This is how architectures become rigid. Too many modules know too much about the

architecture.

Rather we want our immediate collaborators to offer all the services we need. We

should not have to roam through the object graph of the system, hunting for the method we

want to call. Rather we should simply be able to say:

myCollaborator.doSomething().

Java

J1: Avoid Long Import Lists by Using Wildcards

If you use two or more classes from a package, then import the whole package with

import package.*;

Long lists of imports are daunting to the reader. We don't want to clutter up the tops of our

modules with 80 lines of imports. Rather we want the imports to be a concise statement

about which packages we collaborate with.

Specific imports are hard dependencies, whereas wildcard imports are not. If you specifically

import a class, then that class must exist. But if you import a package with a wildcard,

no particular classes need to exist. The import statement simply adds the package to

the search path when hunting for names. So no true dependency is created by such

imports, and they therefore serve to keep our modules less coupled.

There are times when the long list of specific imports can be useful. For example, if

you are dealing with legacy code and you want to find out what classes you need to build

mocks and stubs for, you can walk down the list of specific imports to find out the true

qualified names of all those classes and then put the appropriate stubs in place. However,

this use for specific imports is very rare. Furthermore, most modern IDEs will allow you

to convert the wildcarded imports to a list of specific imports with a single command. So

even in the legacy case it's better to import wildcards.

Wildcard imports can sometimes cause name conflicts and ambiguities. Two classes

with the same name, but in different packages, will need to be specifically imported, or at

least specifically qualified when used. This can be a nuisance but is rare enough that using

wildcard imports is still generally better than specific imports.

J2: Don't Inherit Constants

I have seen this several times and it always makes me grimace. A programmer puts some

constants in an interface and then gains access to those constants by inheriting that interface.

Take a look at the following code:

public class HourlyEmployee extends Employee {

private int tenthsWorked;

private double hourlyRate;

308 Chapter 17: Smells and Heuristics

public Money calculatePay() {

int straightTime = Math.min(tenthsWorked, TENTHS_PER_WEEK);

int overTime = tenthsWorked - straightTime;

return new Money(

hourlyRate * (tenthsWorked + OVERTIME_RATE * overTime)

);

}

...

}

Where did the constants TENTHS_PER_WEEK and OVERTIME_RATE come from? They might have

come from class Employee; so let's take a look at that:

public abstract class Employee implements PayrollConstants {

public abstract boolean isPayday();

public abstract Money calculatePay();

public abstract void deliverPay(Money pay);

}

Nope, not there. But then where? Look closely at class Employee. It implements

PayrollConstants.

public interface PayrollConstants {

public static final int TENTHS_PER_WEEK = 400;

public static final double OVERTIME_RATE = 1.5;

}

This is a hideous practice! The constants are hidden at the top of the inheritance hierarchy.

Ick! Don't use inheritance as a way to cheat the scoping rules of the language. Use a static

import instead.

import static PayrollConstants.*;

public class HourlyEmployee extends Employee {

private int tenthsWorked;

private double hourlyRate;

public Money calculatePay() {

int straightTime = Math.min(tenthsWorked, TENTHS_PER_WEEK);

int overTime = tenthsWorked - straightTime;

return new Money(

hourlyRate * (tenthsWorked + OVERTIME_RATE * overTime)

);

}

...

}

J3: Constants versus Enums

Now that enums have been added to the language (Java 5), use them! Don't keep using the

old trick of public static final ints. The meaning of ints can get lost. The meaning of

enums cannot, because they belong to an enumeration that is named.

What's more, study the syntax for enums carefully. They can have methods and fields.

This makes them very powerful tools that allow much more expression and flexibility than

ints. Consider this variation on the payroll code:

Names 309

public class HourlyEmployee extends Employee {

private int tenthsWorked;

HourlyPayGrade grade;

public Money calculatePay() {

int straightTime = Math.min(tenthsWorked, TENTHS_PER_WEEK);

int overTime = tenthsWorked - straightTime;

return new Money(

grade.rate() * (tenthsWorked + OVERTIME_RATE * overTime)

);

}

...

}

public enum HourlyPayGrade {

APPRENTICE {

public double rate() {

return 1.0;

}

},

LEUTENANT_JOURNEYMAN {

public double rate() {

return 1.2;

}

},

JOURNEYMAN {

public double rate() {

return 1.5;

}

},

MASTER {

public double rate() {

return 2.0;

}

};

public abstract double rate();

}

Names

N1: Choose Descriptive Names

Don't be too quick to choose a name. Make sure the name is descriptive. Remember that

meanings tend to drift as software evolves, so frequently reevaluate the appropriateness of

the names you choose.

This is not just a "feel-good" recommendation. Names in software are 90 percent of

what make software readable. You need to take the time to choose them wisely and keep

them relevant. Names are too important to treat carelessly.

Consider the code below. What does it do? If I show you the code with well-chosen

names, it will make perfect sense to you, but like this it's just a hodge-podge of symbols

and magic numbers.

310 Chapter 17: Smells and Heuristics

public int x() {

int q = 0;

int z = 0;

for (int kk = 0; kk < 10; kk++) {

if (l[z] == 10)

{

q += 10 + (l[z + 1] + l[z + 2]);

z += 1;

}

else if (l[z] + l[z + 1] == 10)

{

q += 10 + l[z + 2];

z += 2;

} else {

q += l[z] + l[z + 1];

z += 2;

}

}

return q;

}

Here is the code the way it should be written. This snippet is actually less complete

than the one above. Yet you can infer immediately what it is trying to do, and you could

very likely write the missing functions based on that inferred meaning. The magic numbers

are no longer magic, and the structure of the algorithm is compellingly descriptive.

public int score() {

int score = 0;

int frame = 0;

for (int frameNumber = 0; frameNumber < 10; frameNumber++) {

if (isStrike(frame)) {

score += 10 + nextTwoBallsForStrike(frame);

frame += 1;

} else if (isSpare(frame)) {

score += 10 + nextBallForSpare(frame);

frame += 2;

} else {

score += twoBallsInFrame(frame);

frame += 2;

}

}

return score;

}

The power of carefully chosen names is that they overload the structure of the code

with description. That overloading sets the readers' expectations about what the other

functions in the module do. You can infer the implementation of isStrike() by looking at

the code above. When you read the isStrike method, it will be "pretty much what you

expected."13

private boolean isStrike(int frame) {

return rolls[frame] == 10;

}

13. See Ward Cunningham's quote on page 11.

Names 311

N2: Choose Names at the Appropriate Level of Abstraction

Don't pick names that communicate implementation; choose names the reflect the level of

abstraction of the class or function you are working in. This is hard to do. Again, people

are just too good at mixing levels of abstractions. Each time you make a pass over your

code, you will likely find some variable that is named at too low a level. You should take

the opportunity to change those names when you find them. Making code readable

requires a dedication to continuous improvement. Consider the Modem interface below:

public interface Modem {

boolean dial(String phoneNumber);

boolean disconnect();

boolean send(char c);

char recv();

String getConnectedPhoneNumber();

}

At first this looks fine. The functions all seem appropriate. Indeed, for many applications

they are. But now consider an application in which some modems aren't connected by

dialling. Rather they are connected permanently by hard wiring them together (think of the

cable modems that provide Internet access to most homes nowadays). Perhaps some are

connected by sending a port number to a switch over a USB connection. Clearly the notion

of phone numbers is at the wrong level of abstraction. A better naming strategy for this

scenario might be:

public interface Modem {

boolean connect(String connectionLocator);

boolean disconnect();

boolean send(char c);

char recv();

String getConnectedLocator();

}

Now the names don't make any commitments about phone numbers. They can still be used

for phone numbers, or they could be used for any other kind of connection strategy.

N3: Use Standard Nomenclature Where Possible

Names are easier to understand if they are based on existing convention or usage. For example,

if you are using the DECORATOR pattern, you should use the word Decorator in the names

of the decorating classes. For example, AutoHangupModemDecorator might be the name of a

class that decorates a Modem with the ability to automatically hang up at the end of a session.

Patterns are just one kind of standard. In Java, for example, functions that convert

objects to string representations are often named toString. It is better to follow conventions

like these than to invent your own.

Teams will often invent their own standard system of names for a particular project.

Eric Evans refers to this as a ubiquitous language for the project.14 Your code should use

14. [DDD].

312 Chapter 17: Smells and Heuristics

the terms from this language extensively. In short, the more you can use names that are

overloaded with special meanings that are relevant to your project, the easier it will be for

readers to know what your code is talking about.

N4: Unambiguous Names

Choose names that make the workings of a function or variable unambiguous. Consider

this example from FitNesse:

private String doRename() throws Exception

{

if(refactorReferences)

renameReferences();

renamePage();

pathToRename.removeNameFromEnd();

pathToRename.addNameToEnd(newName);

return PathParser.render(pathToRename);

}

The name of this function does not say what the function does except in broad and vague

terms. This is emphasized by the fact that there is a function named renamePage inside the

function named doRename! What do the names tell you about the difference between the

two functions? Nothing.

A better name for that function is renamePageAndOptionallyAllReferences. This may

seem long, and it is, but it's only called from one place in the module, so it's explanatory

value outweighs the length.

N5: Use Long Names for Long Scopes

The length of a name should be related to the length of the scope. You can use very short

variable names for tiny scopes, but for big scopes you should use longer names.

Variable names like i and j are just fine if their scope is five lines long. Consider this

snippet from the old standard "Bowling Game":

private void rollMany(int n, int pins)

{

for (int i=0; i<n; i++)

g.roll(pins);

}

This is perfectly clear and would be obfuscated if the variable i were replaced with something

annoying like rollCount. On the other hand, variables and functions with short names

lose their meaning over long distances. So the longer the scope of the name, the longer and

more precise the name should be.

N6: Avoid Encodings

Names should not be encoded with type or scope information. Prefixes such as m_ or f

are useless in today's environments. Also project and/or subsystem encodings such as

Tests 313

vis_ (for visual imaging system) are distracting and redundant. Again, today's environments

provide all that information without having to mangle the names. Keep your

names free of Hungarian pollution.

N7: Names Should Describe Side-Effects

Names should describe everything that a function, variable, or class is or does. Don't hide

side effects with a name. Don't use a simple verb to describe a function that does more

than just that simple action. For example, consider this code from TestNG:

public ObjectOutputStream getOos() throws IOException {

if (m_oos == null) {

m_oos = new ObjectOutputStream(m_socket.getOutputStream());

}

return m_oos;

}

This function does a bit more than get an "oos"; it creates the "oos" if it hasn't been created

already. Thus, a better name might be createOrReturnOos.

Tests

T1: Insufficient Tests

How many tests should be in a test suite? Unfortunately, the metric many programmers use

is "That seems like enough." A test suite should test everything that could possibly break.

The tests are insufficient so long as there are conditions that have not been explored by the

tests or calculations that have not been validated.

T2: Use a Coverage Tool!

Coverage tools reports gaps in your testing strategy. They make it easy to find modules,

classes, and functions that are insufficiently tested. Most IDEs give you a visual indication,

marking lines that are covered in green and those that are uncovered in red. This makes it

quick and easy to find if or catch statements whose bodies haven't been checked.

T3: Don't Skip Trivial Tests

They are easy to write and their documentary value is higher than the cost to produce

them.

T4: An Ignored Test Is a Question about an Ambiguity

Sometimes we are uncertain about a behavioral detail because the requirements are

unclear. We can express our question about the requirements as a test that is commented

out, or as a test that annotated with @Ignore. Which you choose depends upon whether the

ambiguity is about something that would compile or not.

314 Chapter 17: Smells and Heuristics

T5: Test Boundary Conditions

Take special care to test boundary conditions. We often get the middle of an algorithm

right but misjudge the boundaries.

T6: Exhaustively Test Near Bugs

Bugs tend to congregate. When you find a bug in a function, it is wise to do an exhaustive

test of that function. You'll probably find that the bug was not alone.

T7: Patterns of Failure Are Revealing

Sometimes you can diagnose a problem by finding patterns in the way the test cases fail.

This is another argument for making the test cases as complete as possible. Complete test

cases, ordered in a reasonable way, expose patterns.

As a simple example, suppose you noticed that all tests with an input larger than five

characters failed? Or what if any test that passed a negative number into the second argument

of a function failed? Sometimes just seeing the pattern of red and green on the test

report is enough to spark the "Aha!" that leads to the solution. Look back at page 267 to

see an interesting example of this in the SerialDate example.

T8: Test Coverage Patterns Can Be Revealing

Looking at the code that is or is not executed by the passing tests gives clues to why the

failing tests fail.

T9: Tests Should Be Fast

A slow test is a test that won't get run. When things get tight, it's the slow tests that will be

dropped from the suite. So do what you must to keep your tests fast.

Conclusion

This list of heuristics and smells could hardly be said to be complete. Indeed, I'm not sure

that such a list can ever be complete. But perhaps completeness should not be the goal,

because what this list does do is imply a value system.

Indeed, that value system has been the goal, and the topic, of this book. Clean code is

not written by following a set of rules. You don't become a software craftsman by learning

a list of heuristics. Professionalism and craftsmanship come from values that drive

disciplines.

Bibliography 315

Bibliography

[Refactoring]: Refactoring: Improving the Design of Existing Code, Martin Fowler et al.,

Addison-Wesley, 1999.

[PRAG]: The Pragmatic Programmer, Andrew Hunt, Dave Thomas, Addison-Wesley,

2000.

[GOF]: Design Patterns: Elements of Reusable Object Oriented Software, Gamma et al.,

Addison-Wesley, 1996.

[Beck97]: Smalltalk Best Practice Patterns, Kent Beck, Prentice Hall, 1997.

[Beck07]: Implementation Patterns, Kent Beck, Addison-Wesley, 2008.

[PPP]: Agile Software Development: Principles, Patterns, and Practices, Robert C. Martin,

Prentice Hall, 2002.

[DDD]: Domain Driven Design, Eric Evans, Addison-Wesley, 2003.

This page intentionally left blank

317

Appendix A

Concurrency II

by Brett L. Schuchert

This appendix supports and amplifies the Concurrency chapter on page 177. It is written

as a series of independent topics and you can generally read them in any order. There is

some duplication between sections to allow for such reading.

Client/Server Example

Imagine a simple client/server application. A server sits and waits listening on a socket for

a client to connect. A client connects and sends a request.

The Server

Here is a simplified version of a server application. Full source for this example is available

starting on page 343, Client/Server Nonthreaded.

ServerSocket serverSocket = new ServerSocket(8009);

while (keepProcessing) {

try {

Socket socket = serverSocket.accept();

process(socket);

} catch (Exception e) {

handle(e);

}

}

318 Appendix A: Concurrency II

This simple application waits for a connection, processes an incoming message, and then

again waits for the next client request to come in. Here's client code that connects to this

server:

private void connectSendReceive(int i) {

try {

Socket socket = new Socket("localhost", PORT);

MessageUtils.sendMessage(socket, Integer.toString(i));

MessageUtils.getMessage(socket);

socket.close();

} catch (Exception e) {

e.printStackTrace();

}

}

How well does this client/server pair perform? How can we formally describe that performance?

Here's a test that asserts that the performance is "acceptable":

@Test(timeout = 10000)

public void shouldRunInUnder10Seconds() throws Exception {

Thread[] threads = createThreads();

startAllThreadsw(threads);

waitForAllThreadsToFinish(threads);

}

The setup is left out to keep the example simple (see "ClientTest.java" on page 344). This

test asserts that it should complete within 10,000 milliseconds.

This is a classic example of validating the throughput of a system. This system should

complete a series of client requests in ten seconds. So long as the server can process each

individual client request in time, the test will pass.

What happens if the test fails? Short of developing some kind of event polling loop,

there is not much to do within a single thread that will make this code any faster. Will

using multiple threads solve the problem? It might, but we need to know where the time is

being spent. There are two possibilities:

• I/O—using a socket, connecting to a database, waiting for virtual memory swapping,

and so on.

• Processor—numerical calculations, regular expression processing, garbage collection,

and so on.

Systems typically have some of each, but for a given operation one tends to dominate. If

the code is processor bound, more processing hardware can improve throughput, making

our test pass. But there are only so many CPU cycles available, so adding threads to a

processor-bound problem will not make it go faster.

On the other hand, if the process is I/O bound, then concurrency can increase efficiency.

When one part of the system is waiting for I/O, another part can use that wait time

to process something else, making more effective use of the available CPU.

Client/Server Example 319

Adding Threading

Assume for the moment that the performance test fails. How can we improve the throughput

so that the performance test passes? If the process method of the server is I/O bound,

then here is one way to make the server use threads (just change the processMessage):

void process(final Socket socket) {

if (socket == null)

return;

Runnable clientHandler = new Runnable() {

public void run() {

try {

String message = MessageUtils.getMessage(socket);

MessageUtils.sendMessage(socket, "Processed: " + message);

closeIgnoringException(socket);

} catch (Exception e) {

e.printStackTrace();

}

}

};

Thread clientConnection = new Thread(clientHandler);

clientConnection.start();

}

Assume that this change causes the test to pass;1 the code is complete, correct?

Server Observations

The updated server completes the test successfully in just over one second. Unfortunately,

this solution is a bit naive and introduces some new problems.

How many threads might our server create? The code sets no limit, so the we could

feasibly hit the limit imposed by the Java Virtual Machine (JVM). For many simple systems

this may suffice. But what if the system is meant to support many users on the public

net? If too many users connect at the same time, the system might grind to a halt.

But set the behavioral problem aside for the moment. The solution shown has problems

of cleanliness and structure. How many responsibilities does the server code have?

• Socket connection management

• Client processing

• Threading policy

• Server shutdown policy

Unfortunately, all these responsibilities live in the process function. In addition, the

code crosses many different levels of abstraction. So, small as the process function is, it

needs to be repartitioned.

1. You can verify that for yourself by trying out the before and after code. Review the nonthreaded code starting on page 343.

Review the threaded code starting on page 346.

320 Appendix A: Concurrency II

The server has several reasons to change; therefore it violates the Single Responsibility

Principle. To keep concurrent systems clean, thread management should be kept to a few,

well-controlled places. What's more, any code that manages threads should do nothing

other than thread management. Why? If for no other reason than that tracking down concurrency

issues is hard enough without having to unwind other nonconcurrency issues at

the same time.

If we create a separate class for each of the responsibilities listed above, including the

thread management responsibility, then when we change the thread management strategy,

the change will impact less overall code and will not pollute the other responsibilities. This

also makes it much easier to test all the other responsibilities without having to worry

about threading. Here is an updated version that does just that:

public void run() {

while (keepProcessing) {

try {

ClientConnection clientConnection = connectionManager.awaitClient();

ClientRequestProcessor requestProcessor

= new ClientRequestProcessor(clientConnection);

clientScheduler.schedule(requestProcessor);

} catch (Exception e) {

e.printStackTrace();

}

}

connectionManager.shutdown();

}

This now focuses all things thread-related into one place, clientScheduler. If there are

concurrency problems, there is just one place to look:

public interface ClientScheduler {

void schedule(ClientRequestProcessor requestProcessor);

}

The current policy is easy to implement:

public class ThreadPerRequestScheduler implements ClientScheduler {

public void schedule(final ClientRequestProcessor requestProcessor) {

Runnable runnable = new Runnable() {

public void run() {

requestProcessor.process();

}

};

Thread thread = new Thread(runnable);

thread.start();

}

}

Having isolated all the thread management into a single place, it is much easier to change

the way we control threads. For example, moving to the Java 5 Executor framework

involves writing a new class and plugging it in (Listing A-1).

Possible Paths of Execution 321

Conclusion

Introducing concurrency in this particular example demonstrates a way to improve the

throughput of a system and one way of validating that throughput through a testing framework.

Focusing all concurrency code into a small number of classes is an example of

applying the Single Responsibility Principle. In the case of concurrent programming, this

becomes especially important because of its complexity.

Possible Paths of Execution

Review the method incrementValue, a one-line Java method with no looping or branching:

public class IdGenerator {

int lastIdUsed;

public int incrementValue() {

return ++lastIdUsed;

}

}

Ignore integer overflow and assume that only one thread has access to a single instance

of IdGenerator. In this case there is a single path of execution and a single guaranteed

result:

• The value returned is equal to the value of lastIdUsed, both of which are one greater

than just before calling the method.

Listing A-1

ExecutorClientScheduler.java

import java.util.concurrent.Executor;

import java.util.concurrent.Executors;

public class ExecutorClientScheduler implements ClientScheduler {

Executor executor;

public ExecutorClientScheduler(int availableThreads) {

executor = Executors.newFixedThreadPool(availableThreads);

}

public void schedule(final ClientRequestProcessor requestProcessor) {

Runnable runnable = new Runnable() {

public void run() {

requestProcessor.process();

}

};

executor.execute(runnable);

}

}

322 Appendix A: Concurrency II

What happens if we use two threads and leave the method unchanged? What are the

possible outcomes if each thread calls incrementValue once? How many possible paths of

execution are there? First, the outcomes (assume lastIdUsed starts with a value of 93):

• Thread 1 gets the value of 94, thread 2 gets the value of 95, and lastIdUsed is now 95.

• Thread 1 gets the value of 95, thread 2 gets the value of 94, and lastIdUsed is now 95.

• Thread 1 gets the value of 94, thread 2 gets the value of 94, and lastIdUsed is now 94.

The final result, while surprising, is possible. To see how these different results are possible,

we need to understand the number of possible paths of execution and how the Java

Virtual Machine executes them.

Number of Paths

To calculate the number of possible execution paths, we'll start with the generated bytecode.

The one line of java (return ++lastIdUsed;) becomes eight byte-code instructions. It

is possible for the two threads to interleave the execution of these eight instructions the

way a card dealer interleaves cards as he shuffles a deck.2 Even with only eight cards in

each hand, there are a remarkable number of shuffled outcomes.

For this simple case of N instructions in a sequence, no looping or conditionals, and T

threads, the total number of possible execution paths is equal to

2. This is a bit of a simplification. However, for the purpose of this discussion, we can use this simplifying model.

Calculating the Possible Orderings

This comes from an email from Uncle Bob to Brett:

With N steps and T threads there are T *N total steps. Prior to each step

there is a context switch that chooses between the T threads. Each path can

thus be represented as a string of digits denoting the context switches.

Given steps A and B and threads 1 and 2, the six possible paths are 1122,

1212, 1221, 2112, 2121, and 2211. Or, in terms of steps it is A1B1A2B2,

A1A2B1B2, A1A2B2B1, A2A1B1B2, A2A1B2B1, and A2B2A1B1. For

three threads the sequence is 112233, 112323, 113223, 113232, 112233,

121233, 121323, 121332, 123132, 123123, . . . .

One characteristic of these strings is that there must always be N

instances of each T. So the string 111111 is invalid because it has six

instances of 1 and zero instances of 2 and 3.

(NT)!

N!T

--------------

Possible Paths of Execution 323

For our simple case of one line of Java code, which equates to eight lines of byte-code

and two threads, the total number of possible paths of execution is 12,870. If the type of

lastIdUsed is a long, then every read/write becomes two operations instead of one, and the

number of possible orderings becomes 2,704,156.

What happens if we make one change to this method?

public synchronized void incrementValue() {

++lastIdUsed;

}

The number of possible execution pathways becomes two for two threads and N! in the

general case.

Digging Deeper

What about the surprising result that two threads could both call the method once (before

we added synchronized) and get the same numeric result? How is that possible? First

things first.

What is an atomic operation? We can define an atomic operation as any operation that

is uninterruptable. For example, in the following code, line 5, where 0 is assigned to

lastid, is atomic because according to the Java Memory model, assignment to a 32-bit

value is uninterruptable.

So we want the permutations of N 1's, N 2's, . . . and N T's. This is

really just the permutations of N * T things taken N * T at a time, which is

(N * T )!, but with all the duplicates removed. So the trick is to count the

duplicates and subtract that from (N * T )!.

Given two steps and two threads, how many duplicates are there? Each

four-digit string has two 1s and two 2s. Each of those pairs could be

swapped without changing the sense of the string. You could swap the 1s or

the 2s both, or neither. So there are four isomorphs for each string, which

means that there are three duplicates. So three out of four of the options are

duplicates; alternatively one of four of the permutations are NOT duplicates.

4! * .25 = 6. So this reasoning seems to work.

How many duplicates are there? In the case where N = 2 and T = 2, I

could swap the 1s, the 2s, or both. In the case where N = 2 and T = 3, I

could swap the 1s, the 2s, the 3s, 1s and 2s, 1s and 3s, or 2s and 3s. Swapping

is just the permutations of N. Let's say there are P permutations of N.

The number of different ways to arrange those permutations are P**T.

So the number of possible isomorphs is N!**T. And so the number of

paths is (T*N)!/(N!**T). Again, in our T = 2, N = 2 case we get 6 (24/4).

For N = 2 and T = 3 we get 720/8 = 90.

For N = 3 and T = 3 we get 9!/6^3 = 1680.

Calculating the Possible Orderings (continued)

324 Appendix A: Concurrency II

01: public class Example {

02: int lastId;

03:

04: public void resetId() {

05: value = 0;

06: }

07:

08: public int getNextId() {

09: ++value;

10: }

11:}

What happens if we change type of lastId from int to long? Is line 5 still atomic?

Not according to the JVM specification. It could be atomic on a particular processor,

but according to the JVM specification, assignment to any 64-bit value requires two

32-bit assignments. This means that between the first 32-bit assignment and the second

32-bit assignment, some other thread could sneak in and change one of the values.

What about the pre-increment operator, ++, on line 9? The pre-increment operator can

be interrupted, so it is not atomic. To understand, let's review the byte-code of both of these

methods in detail.

Before we go any further, here are three definitions that will be important:

• Frame—Every method invocation requires a frame. The frame includes the return

address, any parameters passed into the method and the local variables defined in the

method. This is a standard technique used to define a call stack, which is used by

modern languages to allow for basic function/method invocation and to allow for

recursive invocation.

• Local variable—Any variables defined in the scope of the method. All nonstatic methods

have at least one variable, this, which represents the current object, the object

that received the most recent message (in the current thread), which caused the

method invocation.

• Operand stack—Many of the instructions in the Java Virtual Machine take parameters.

The operand stack is where those parameters are put. The stack is a standard

last-in, first-out (LIFO) data structure.

Here is the byte-code generated for resetId():

Mnemonic Description Operand

Stack After

ALOAD 0 Load the 0th variable onto the operand stack.

What is the 0th variable? It is this., the current

object. When the method was called, the

receiver of the message, an instance of Example,

was pushed into the local variable array of the

frame created for method invocation. This is

always the first variable put in every instance

method.

this

Possible Paths of Execution 325

These three instructions are guaranteed to be atomic because, although the thread

executing them could be interrupted after any one of them, the information for the

PUTFIELD instruction (the constant value 0 on the top of the stack and the reference to

this one below the top, along with the field value) cannot be touched by another thread.

So when the assignment occurs, we are guaranteed that the value 0 will be stored in the

field value. The operation is atomic. The operands all deal with information local to the

method, so there is no interference between multiple threads.

So if these three instructions are executed by ten threads, there are 4.38679733629e+24

possible orderings. However, there is only one possible outcome, so the different orderings

are irrelevant. It just so happens that the same outcome is guaranteed for longs in this case

as well. Why? All ten threads are assigning a constant value. Even if they interleave with

each other, the end result is the same.

With the ++ operation in the getNextId method, there are going to be problems.

Assume that lastId holds 42 at the beginning of this method. Here is the byte-code for this

new method:

ICONST_0 Put the constant value 0 onto the operand stack. this, 0

PUTFIELD lastId Store the top value on the stack (which is 0) into

the field value of the object referred to by the

object reference one away from the top of the

stack, this.

<empty>

Mnemonic Description Operand

Stack After

ALOAD 0 Load this onto the operand stack this

DUP Copy the top of the stack. We now have two

copies of this on the operand stack.

this, this

GETFIELD lastId Retrieve the value of the field lastId from the

object pointed to on the top of the stack (this) and

store that value back on to the stack.

this, 42

ICONST_1 Push the integer constant 1 on the stack. this, 42, 1

IADD Integer add the top two values on the operand

stack and store the result back on to the operand

stack.

this, 43

DUP_X1 Duplicate the value 43 and put it before this. 43, this, 43

PUTFIELD value Store the top value on the operand stack, 43, into

the field value of the current object, represented by

the next-to-top value on the operand stack, this.

43

IRETURN return the top (and only) value on the stack. <empty>

Mnemonic Description Operand

Stack After

326 Appendix A: Concurrency II

Imagine the case where the first thread completes the first three instructions, up to and

including GETFIELD, and then it is interrupted. A second thread takes over and performs

the entire method, incrementing lastId by one; it gets 43 back. Then the first thread picks up

where it left off; 42 is still on the operand stack because that was the value of lastId when it

executed GETFIELD. It adds one to get 43 again and stores the result. The value 43 is

returned to the first thread as well. The result is that one of the increments is lost because the

first thread stepped on the second thread after the second thread interrupted the first thread.

Making the getNexId() method synchronized fixes this problem.

Conclusion

An intimate understanding of byte-code is not necessary to understand how threads can

step on each other. If you can understand this one example, it should demonstrate the possibility

of multiple threads stepping on each other, which is enough knowledge.

That being said, what this trivial example demonstrates is a need to understand the

memory model enough to know what is and is not safe. It is a common misconception that

the ++ (pre- or post-increment) operator is atomic, and it clearly is not. This means you

need to know:

• Where there are shared objects/values

• The code that can cause concurrent read/update issues

• How to guard such concurrent issues from happening

Knowing Your Library

Executor Framework

As demonstrated in the ExecutorClientScheduler.java on page 321, the Executor framework

introduced in Java 5 allows for sophisticated execution using thread pools. This is a

class in the java.util.concurrent package.

If you are creating threads and are not using a thread pool or are using a hand-written

one, you should consider using the Executor. It will make your code cleaner, easier to follow,

and smaller.

The Executor framework will pool threads, resize automatically, and recreate threads

if necessary. It also supports futures, a common concurrent programming construct. The

Executor framework works with classes that implement Runnable and also works with

classes that implement the Callable interface. A Callable looks like a Runnable, but it can

return a result, which is a common need in multithreaded solutions.

A future is handy when code needs to execute multiple, independent operations and

wait for both to finish:

public String processRequest(String message) throws Exception {

Callable<String> makeExternalCall = new Callable<String>() {

Knowing Your Library 327

public String call() throws Exception {

String result = "";

// make external request

return result;

}

};

Future<String> result = executorService.submit(makeExternalCall);

String partialResult = doSomeLocalProcessing();

return result.get() + partialResult;

}

In this example, the method starts executing the makeExternalCall object. The method continues

other processing. The final line calls result.get(), which blocks until the future

completes.

Nonblocking Solutions

The Java 5 VM takes advantage of modern processor design, which supports reliable,

nonblocking updates. Consider, for example, a class that uses synchronization (and therefore

blocking) to provide a thread-safe update of a value:

public class ObjectWithValue {

private int value;

public void synchronized incrementValue() { ++value; }

public int getValue() { return value; }

}

Java 5 has a series of new classes for situations like this: AtomicBoolean,

AtomicInteger, and AtomicReference are three examples; there are several more. We can

rewrite the above code to use a nonblocking approach as follows:

public class ObjectWithValue {

private AtomicInteger value = new AtomicInteger(0);

public void incrementValue() {

value.incrementAndGet();

}

public int getValue() {

return value.get();

}

}

Even though this uses an object instead of a primitive and sends messages like

incrementAndGet() instead of ++, the performance of this class will nearly always beat the

previous version. In some cases it will only be slightly faster, but the cases where it will be

slower are virtually nonexistent.

How is this possible? Modern processors have an operation typically called Compare

and Swap (CAS). This operation is analogous to optimistic locking in databases, whereas

the synchronized version is analogous to pessimistic locking.

328 Appendix A: Concurrency II

The synchronized keyword always acquires a lock, even when a second thread is not

trying to update the same value. Even though the performance of intrinsic locks has

improved from version to version, they are still costly.

The nonblocking version starts with the assumption that multiple threads generally do

not modify the same value often enough that a problem will arise. Instead, it efficiently

detects whether such a situation has occurred and retries until the update happens successfully.

This detection is almost always less costly than acquiring a lock, even in moderate to

high contention situations.

How does the Virtual Machine accomplish this? The CAS operation is atomic. Logically,

the CAS operation looks something like the following:

int variableBeingSet;

void simulateNonBlockingSet(int newValue) {

int currentValue;

do {

currentValue = variableBeingSet

} while(currentValue != compareAndSwap(currentValue, newValue));

}

int synchronized compareAndSwap(int currentValue, int newValue) {

if(variableBeingSet == currentValue) {

variableBeingSet = newValue;

return currentValue;

}

return variableBeingSet;

}

When a method attempts to update a shared variable, the CAS operation verifies that

the variable getting set still has the last known value. If so, then the variable is changed. If

not, then the variable is not set because another thread managed to get in the way. The

method making the attempt (using the CAS operation) sees that the change was not made

and retries.

Nonthread-Safe Classes

There are some classes that are inherently not thread safe. Here are a few examples:

• SimpleDateFormat

• Database Connections

• Containers in java.util

• Servlets

Note that some collection classes have individual methods that are thread-safe. However,

any operation that involves calling more than one method is not. For example, if you do

not want to replace something in a HashTable because it is already there, you might write

the following code:

if(!hashTable.containsKey(someKey)) {

hashTable.put(someKey, new SomeValue());

}

Dependencies Between Methods Can Break Concurrent Code 329

Each individual method is thread-safe. However, another thread might add a value in

between the containsKey and put calls. There are several options to fix this problem.

• Lock the HashTable first, and make sure all other users of the HashTable do the same—

client-based locking:

synchronized(map) {

if(!map.conainsKey(key))

map.put(key,value);

}

• Wrap the HashTable in its own object and use a different API—server-based locking

using an ADAPTER:

public class WrappedHashtable<K, V> {

private Map<K, V> map = new Hashtable<K, V>();

public synchronized void putIfAbsent(K key, V value) {

if (map.containsKey(key))

map.put(key, value);

}

}

• Use the thread-safe collections:

ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<Integer,

String>();

map.putIfAbsent(key, value);

The collections in java.util.concurrent have operations like putIfAbsent() to accommodate

such operations.

Dependencies Between Methods

Can Break Concurrent Code

Here is a trivial example of a way to introduce dependencies between methods:

public class IntegerIterator implements Iterator<Integer>

private Integer nextValue = 0;

public synchronized boolean hasNext() {

return nextValue < 100000;

}

public synchronized Integer next() {

if (nextValue == 100000)

throw new IteratorPastEndException();

return nextValue++;

}

public synchronized Integer getNextValue() {

return nextValue;

}

}

Here is some code to use this IntegerIterator:

IntegerIterator iterator = new IntegerIterator();

while(iterator.hasNext()) {

330 Appendix A: Concurrency II

int nextValue = iterator.next();

// do something with nextValue

}

If one thread executes this code, there will be no problem. But what happens if two threads

attempt to share a single instance of IngeterIterator with the intent that each thread will

process the values it gets, but that each element of the list is processed only once? Most of

the time, nothing bad happens; the threads happily share the list, processing the elements

they are given by the iterator and stopping when the iterator is complete. However, there is

a small chance that, at the end of the iteration, the two threads will interfere with each

other and cause one thread to go beyond the end of the iterator and throw an exception.

Here's the problem: Thread 1 asks the question hasNext(), which returns true. Thread

1 gets preempted and then Thread 2 asks the same question, which is still true. Thread 2

then calls next(), which returns a value as expected but has a side effect of making

hasNext() return false. Thread 1 starts up again, thinking hasNext() is still true, and then

calls next(). Even though the individual methods are synchronized, the client uses two

methods.

This is a real problem and an example of the kinds of problems that crop up in concurrent

code. In this particular situation this problem is especially subtle because the only

time where this causes a fault is when it happens during the final iteration of the iterator.

If the threads happen to break just right, then one of the threads could go beyond the end

of the iterator. This is the kind of bug that happens long after a system has been in production,

and it is hard to track down.

You have three options:

• Tolerate the failure.

• Solve the problem by changing the client: client-based locking

• Solve the problem by changing the server, which additionally changes the client:

server-based locking

Tolerate the Failure

Sometimes you can set things up such that the failure causes no harm. For example, the

above client could catch the exception and clean up. Frankly, this is a bit sloppy. It's rather

like cleaning up memory leaks by rebooting at midnight.

Client-Based Locking

To make IntegerIterator work correctly with multiple threads, change this client (and

every other client) as follows:

IntegerIterator iterator = new IntegerIterator();

while (true) {

int nextValue;

Dependencies Between Methods Can Break Concurrent Code 331

synchronized (iterator) {

if (!iterator.hasNext())

break;

nextValue = iterator.next();

}

doSometingWith(nextValue);

}

Each client introduces a lock via the synchronized keyword. This duplication violates the

DRY principle, but it might be necessary if the code uses non-thread-safe third-party tools.

This strategy is risky because all programmers who use the server must remember to

lock it before using it and unlock it when done. Many (many!) years ago I worked on a

system that employed client-based locking on a shared resource. The resource was used in

hundreds of different places throughout the code. One poor programmer forgot to lock the

resource in one of those places.

The system was a multi-terminal time-sharing system running accounting software

for Local 705 of the trucker's union. The computer was in a raised-floor, environmentcontrolled

room 50 miles north of the Local 705 headquarters. At the headquarters they

had dozens of data entry clerks typing union dues postings into the terminals. The terminals

were connected to the computer using dedicated phone lines and 600bps half-duplex

modems. (This was a very, very long time ago.)

About once per day, one of the terminals would "lock up." There was no rhyme or reason

to it. The lock up showed no preference for particular terminals or particular times. It

was as though there were someone rolling dice choosing the time and terminal to lock up.

Sometimes more than one terminal would lock up. Sometimes days would go by without

any lock-ups.

At first the only solution was a reboot. But reboots were tough to coordinate. We had

to call the headquarters and get everyone to finish what they were doing on all the terminals.

Then we could shut down and restart. If someone was doing something important

that took an hour or two, the locked up terminal simply had to stay locked up.

After a few weeks of debugging we found that the cause was a ring-buffer counter that

had gotten out of sync with its pointer. This buffer controlled output to the terminal. The

pointer value indicated that the buffer was empty, but the counter said it was full. Because

it was empty, there was nothing to display; but because it was also full, nothing could be

added to the buffer to be displayed on the screen.

So we knew why the terminals were locking, but we didn't know why the ring buffer

was getting out of sync. So we added a hack to work around the problem. It was possible to

read the front panel switches on the computer. (This was a very, very, very long time ago.)

We wrote a little trap function that detected when one of these switches was thrown and

then looked for a ring buffer that was both empty and full. If one was found, it reset that

buffer to empty. Voila! The locked-up terminal(s) started displaying again.

So now we didn't have to reboot the system when a terminal locked up. The Local

would simply call us and tell us we had a lock-up, and then we just walked into the computer

room and flicked a switch.

332 Appendix A: Concurrency II

Of course sometimes they worked on the weekends, and we didn't. So we added a

function to the scheduler that checked all the ring buffers once per minute and reset any

that were both empty and full. This caused the displays to unclog before the Local could

even get on the phone.

It was several more weeks of poring over page after page of monolithic assembly language

code before we found the culprit. We had done the math and calculated that the frequency

of the lock-ups was consistent with a single unprotected use of the ring buffer. So

all we had to do was find that one faulty usage. Unfortunately, this was so very long ago

that we didn't have search tools or cross references or any other kind of automated help.

We simply had to pore over listings.

I learned an important lesson that cold Chicago winter of 1971. Client-based locking

really blows.

Server-Based Locking

The duplication can be removed by making the following changes to IntegerIterator:

public class IntegerIteratorServerLocked {

private Integer nextValue = 0;

public synchronized Integer getNextOrNull() {

if (nextValue < 100000)

return nextValue++;

else

return null;

}

}

And the client code changes as well:

while (true) {

Integer nextValue = iterator.getNextOrNull();

if (next == null)

break;

// do something with nextValue

}

In this case we actually change the API of our class to be multithread aware.3 The client

needs to perform a null check instead of checking hasNext().

In general you should prefer server-based locking for these reasons:

• It reduces repeated code—Client-based locking forces each client to lock the server

properly. By putting the locking code into the server, clients are free to use the object

and not worry about writing additional locking code.

3. In fact, the Iterator interface is inherently not thread-safe. It was never designed to be used by multiple threads, so this

should come as no surprise.

Increasing Throughput 333

• It allows for better performance—You can swap out a thread-safe server for a nonthread

safe one in the case of single-threaded deployment, thereby avoiding all

overhead.

• It reduces the possibility of error—All it takes is for one programmer to forget to lock

properly.

• It enforces a single policy—The policy is in one place, the server, rather than many

places, each client.

• It reduces the scope of the shared variables—The client is not aware of them or how

they are locked. All of that is hidden in the server. When things break, the number of

places to look is smaller.

What if you do not own the server code?

• Use an ADAPTER to change the API and add locking

public class ThreadSafeIntegerIterator {

private IntegerIterator iterator = new IntegerIterator();

public synchronized Integer getNextOrNull() {

if(iterator.hasNext())

return iterator.next();

return null;

}

}

• OR better yet, use the thread-safe collections with extended interfaces

Increasing Throughput

Let's assume that we want to go out on the net and read the contents of a set of pages from

a list of URLs. As each page is read, we will parse it to accumulate some statistics. Once

all the pages are read, we will print a summary report.

The following class returns the contents of one page, given a URL.

public class PageReader {

//...

public String getPageFor(String url) {

HttpMethod method = new GetMethod(url);

try {

httpClient.executeMethod(method);

String response = method.getResponseBodyAsString();

return response;

} catch (Exception e) {

handle(e);

} finally {

method.releaseConnection();

}

}

}

334 Appendix A: Concurrency II

The next class is the iterator that provides the contents of the pages based on an iterator of

URLs:

public class PageIterator {

private PageReader reader;

private URLIterator urls;

public PageIterator(PageReader reader, URLIterator urls) {

this.urls = urls;

this.reader = reader;

}

public synchronized String getNextPageOrNull() {

if (urls.hasNext())

getPageFor(urls.next());

else

return null;

}

public String getPageFor(String url) {

return reader.getPageFor(url);

}

}

An instance of the PageIterator can be shared between many different threads, each

one using it's own instance of the PageReader to read and parse the pages it gets from the

iterator.

Notice that we've kept the synchronized block very small. It contains just the critical

section deep inside the PageIterator. It is always better to synchronize as little as possible

as opposed to synchronizing as much as possible.

Single-Thread Calculation of Throughput

Now lets do some simple calculations. For the purpose of argument, assume the following:

• I/O time to retrieve a page (average): 1 second

• Processing time to parse page (average): .5 seconds

• I/O requires 0 percent of the CPU while processing requires 100 percent.

For N pages being processed by a single thread, the total execution time is 1.5 seconds

* N. Figure A-1 shows a snapshot of 13 pages or about 19.5 seconds.

Figure A-1

Single thread

Deadlock 335

Multithread Calculation of Throughput

If it is possible to retrieve pages in any order and process the pages independently, then it

is possible to use multiple threads to increase throughput. What happens if we use three

threads? How many pages can we acquire in the same time?

As you can see in Figure A-2, the multithreaded solution allows the process-bound

parsing of the pages to overlap with the I/O-bound reading of the pages. In an idealized

world this means that the processor is fully utilized. Each one-second page read is overlapped

with two parses. Thus, we can process two pages per second, which is three times

the throughput of the single-threaded solution.

Deadlock

Imagine a Web application with two shared resource pools of some finite size:

• A pool of database connections for local work in process storage

• A pool of MQ connections to a master repository

Assume there are two operations in this application, create and update:

• Create—Acquire connection to master repository and database. Talk to service master

repository and then store work in local work in process database.

Figure A-2

Three concurrent threads

336 Appendix A: Concurrency II

• Update—Acquire connection to database and then master repository. Read from work

in process database and then send to the master repository

What happens when there are more users than the pool sizes? Consider each pool has

a size of ten.

• Ten users attempt to use create, so all ten database connections are acquired, and each

thread is interrupted after acquiring a database connection but before acquiring a connection

to the master repository.

• Ten users attempt to use update, so all ten master repository connections are acquired,

and each thread is interrupted after acquiring the master repository but before acquiring

a database connection.

• Now the ten "create" threads must wait to acquire a master repository connection, but

the ten "update" threads must wait to acquire a database connection.

• Deadlock. The system never recovers.

This might sound like an unlikely situation, but who wants a system that freezes solid

every other week? Who wants to debug a system with symptoms that are so difficult to

reproduce? This is the kind of problem that happens in the field, then takes weeks to solve.

A typical "solution" is to introduce debugging statements to find out what is happening.

Of course, the debug statements change the code enough so that the deadlock happens

in a different situation and takes months to again occur.4

To really solve the problem of deadlock, we need to understand what causes it. There

are four conditions required for deadlock to occur:

• Mutual exclusion

• Lock & wait

• No preemption

• Circular wait

Mutual Exclusion

Mutual exclusion occurs when multiple threads need to use the same resources and those

resources

• Cannot be used by multiple threads at the same time.

• Are limited in number.

A common example of such a resource is a database connection, a file open for write, a

record lock, or a semaphore.

4. For example, someone adds some debugging output and the problem "disappears." The debugging code "fixes" the problem

so it remains in the system.

Deadlock 337

Lock & Wait

Once a thread acquires a resource, it will not release the resource until it has acquired all

of the other resources it requires and has completed its work.

No Preemption

One thread cannot take resources away from another thread. Once a thread holds a

resource, the only way for another thread to get it is for the holding thread to release it.

Circular Wait

This is also referred to as the deadly embrace. Imagine two threads, T1 and T2, and two

resources, R1 and R2. T1 has R1, T2 has R2. T1 also requires R2, and T2 also requires R1.

This gives something like Figure A-3:

All four of these conditions must hold for deadlock to be possible. Break any one of these

conditions and deadlock is not possible.

Breaking Mutual Exclusion

One strategy for avoiding deadlock is to sidestep the mutual exclusion condition. You

might be able to do this by

• Using resources that allow simultaneous use, for example, AtomicInteger.

• Increasing the number of resources such that it equals or exceeds the number of competing

threads.

• Checking that all your resources are free before seizing any.

Unfortunately, most resources are limited in number and don't allow simultaneous

use. And it's not uncommon for the identity of the second resource to be predicated on the

results of operating on the first. But don't be discouraged; there are three conditions left.

Figure A-3

338 Appendix A: Concurrency II

Breaking Lock & Wait

You can also eliminate deadlock if you refuse to wait. Check each resource before you

seize it, and release all resources and start over if you run into one that's busy.

This approach introduces several potential problems:

• Starvation—One thread keeps being unable to acquire the resources it needs (maybe it

has a unique combination of resources that seldom all become available).

• Livelock—Several threads might get into lockstep and all acquire one resource and

then release one resource, over and over again. This is especially likely with simplistic

CPU scheduling algorithms (think embedded devices or simplistic hand-written

thread balancing algorithms).

Both of these can cause poor throughput. The first results in low CPU utilization,

whereas the second results in high and useless CPU utilization.

As inefficient as this strategy sounds, it's better than nothing. It has the benefit that it

can almost always be implemented if all else fails.

Breaking Preemption

Another strategy for avoiding deadlock is to allow threads to take resources away from

other threads. This is usually done through a simple request mechanism. When a thread

discovers that a resource is busy, it asks the owner to release it. If the owner is also waiting

for some other resource, it releases them all and starts over.

This is similar to the previous approach but has the benefit that a thread is allowed to

wait for a resource. This decreases the number of startovers. Be warned, however, that

managing all those requests can be tricky.

Breaking Circular Wait

This is the most common approach to preventing deadlock. For most systems it requires

no more than a simple convention agreed to by all parties.

In the example above with Thread 1 wanting both Resource 1 and Resource 2 and

Thread 2 wanting both Resource 2 and then Resource 1, simply forcing both Thread 1 and

Thread 2 to allocate resources in the same order makes circular wait impossible.

More generally, if all threads can agree on a global ordering of resources and if they

all allocate resources in that order, then deadlock is impossible. Like all the other strategies,

this can cause problems:

• The order of acquisition might not correspond to the order of use; thus a resource

acquired at the start might not be used until the end. This can cause resources to be

locked longer than strictly necessary.

Testing Multithreaded Code 339

• Sometimes you cannot impose an order on the acquisition of resources. If the ID of

the second resource comes from an operation performed on the first, then ordering is

not feasible.

So there are many ways to avoid deadlock. Some lead to starvation, whereas others

make heavy use of the CPU and reduce responsiveness. TANSTAAFL!5

Isolating the thread-related part of your solution to allow for tuning and experimentation

is a powerful way to gain the insights needed to determine the best strategies.

Testing Multithreaded Code

How can we write a test to demonstrate the following code is broken?

01: public class ClassWithThreadingProblem {

02: int nextId;

03:

04: public int takeNextId() {

05: return nextId++;

06: }

07:}

Here's a description of a test that will prove the code is broken:

• Remember the current value of nextId.

• Create two threads, both of which call takeNextId() once.

• Verify that nextId is two more than what we started with.

• Run this until we demonstrate that nextId was only incremented by one instead

of two.

Listing A-2 shows such a test:

5. There ain't no such thing as a free lunch.

Listing A-2

ClassWithThreadingProblemTest.java

01: package example;

02:

03: import static org.junit.Assert.fail;

04:

05: import org.junit.Test;

06:

07: public class ClassWithThreadingProblemTest {

08: @Test

09: public void twoThreadsShouldFailEventually() throws Exception {

10: final ClassWithThreadingProblem classWithThreadingProblem

= new ClassWithThreadingProblem();

11:

340 Appendix A: Concurrency II

12: Runnable runnable = new Runnable() {

13: public void run() {

14: classWithThreadingProblem.takeNextId();

15: }

16: };

17:

18: for (int i = 0; i < 50000; ++i) {

19: int startingId = classWithThreadingProblem.lastId;

20: int expectedResult = 2 + startingId;

21:

22: Thread t1 = new Thread(runnable);

23: Thread t2 = new Thread(runnable);

24: t1.start();

25: t2.start();

26: t1.join();

27: t2.join();

28:

29: int endingId = classWithThreadingProblem.lastId;

30:

31: if (endingId != expectedResult)

32: return;

33: }

34:

35: fail("Should have exposed a threading issue but it did not.");

36: }

37: }

Line Description

10 Create a single instance of ClassWithThreadingProblem. Note, we must use

the final keyword because we use it below in an anonymous inner class.

12–16 Create an anonymous inner class that uses the single instance of

ClassWithThreadingProblem.

18 Run this code "enough" times to demonstrate that the code failed, but not

so much that the test "takes too long." This is a balancing act; we don't

want to wait too long to demonstrate failure. Picking this number is hard—

although later we'll see that we can greatly reduce this number.

19 Remember the starting value. This test is trying to prove that the code in

ClassWithThreadingProblem is broken. If this test passes, it proved that the

code was broken. If this test fails, the test was unable to prove that the code

is broken.

20 We expect the final value to be two more than the current value.

22–23 Create two threads, both of which use the object we created in lines 12–16.

This gives us the potential of two threads trying to use our single instance

of ClassWithThreadingProblem and interfering with each other.

Listing A-2 (continued)

ClassWithThreadingProblemTest.java

Testing Multithreaded Code 341

This test certainly sets up the conditions for a concurrent update problem. However,

the problem occurs so infrequently that the vast majority of times this test won't detect it.

Indeed, to truly detect the problem we need to set the number of iterations to over one

million. Even then, in ten executions with a loop count of 1,000,000, the problem occurred

only once. That means we probably ought to set the iteration count to well over one hundred

million to get reliable failures. How long are we prepared to wait?

Even if we tuned the test to get reliable failures on one machine, we'll probably have

to retune the test with different values to demonstrate the failure on another machine,

operating system, or version of the JVM.

And this is a simple problem. If we cannot demonstrate broken code easily with this

problem, how will we ever detect truly complex problems?

So what approaches can we take to demonstrate this simple failure? And, more importantly,

how can we write tests that will demonstrate failures in more complex code? How

will we be able to discover if our code has failures when we do not know where to look?

Here are a few ideas:

• Monte Carlo Testing. Make tests flexible, so they can be tuned. Then run the test over

and over—say on a test server—randomly changing the tuning values. If the tests ever

fail, the code is broken. Make sure to start writing those tests early so a continuous

integration server starts running them soon. By the way, make sure you carefully log

the conditions under which the test failed.

• Run the test on every one of the target deployment platforms. Repeatedly. Continuously.

The longer the tests run without failure, the more likely that

– The production code is correct or

– The tests aren't adequate to expose problems.

• Run the tests on a machine with varying loads. If you can simulate loads close to a

production environment, do so.

24–25 Make our two threads eligible to run.

26–27 Wait for both threads to finish before we check the results.

29 Record the actual final value.

31–32 Did our endingId differ from what we expected? If so, return end the test—

we've proven that the code is broken. If not, try again.

35 If we got to here, our test was unable to prove the production code was broken

in a "reasonable" amount of time; our code has failed. Either the code

is not broken or we didn't run enough iterations to get the failure condition

to occur.

Line Description

342 Appendix A: Concurrency II

Yet, even if you do all of these things, you still don't stand a very good chance of finding

threading problems with your code. The most insidious problems are the ones that

have such a small cross section that they only occur once in a billion opportunities. Such

problems are the terror of complex systems.

Tool Support for Testing Thread-Based Code

IBM has created a tool called ConTest.6 It instruments classes to make it more likely that

non-thread-safe code fails.

We do not have any direct relationship with IBM or the team that developed ConTest.

A colleague of ours pointed us to it. We noticed vast improvement in our ability to find

threading issues after a few minutes of using it.

Here's an outline of how to use ConTest:

• Write tests and production code, making sure there are tests specifically designed to

simulate multiple users under varying loads, as mentioned above.

• Instrument test and production code with ConTest.

• Run the tests.

When we instrumented code with ConTest, our success rate went from roughly one failure

in ten million iterations to roughly one failure in thirty iterations. Here are the loop values

for several runs of the test after instrumentation: 13, 23, 0, 54, 16, 14, 6, 69, 107, 49, 2. So

clearly the instrumented classes failed much earlier and with much greater reliability.

Conclusion

This chapter has been a very brief sojourn through the large and treacherous territory of

concurrent programming. We barely scratched the surface. Our emphasis here was on disciplines

to help keep concurrent code clean, but there is much more you should learn if

you are going to be writing concurrent systems. We recommend you start with Doug Lea's

wonderful book Concurrent Programming in Java: Design Principles and Patterns.7

In this chapter we talked about concurrent update, and the disciplines of clean synchronization

and locking that can prevent it. We talked about how threads can enhance the

throughput of an I/O-bound system and showed the clean techniques for achieving such

improvements. We talked about deadlock and the disciplines for preventing it in a clean

6. http://www.haifa.ibm.com/projects/verification/contest/index.html

7. See [Lea99] p. 191.

Tutorial: Full Code Examples 343

way. Finally, we talked about strategies for exposing concurrent problems by instrumenting

your code.

Tutorial: Full Code Examples

Client/Server Nonthreaded

Listing A-3

Server.java

package com.objectmentor.clientserver.nonthreaded;

import java.io.IOException;

import java.net.ServerSocket;

import java.net.Socket;

import java.net.SocketException;

import common.MessageUtils;

public class Server implements Runnable {

ServerSocket serverSocket;

volatile boolean keepProcessing = true;

public Server(int port, int millisecondsTimeout) throws IOException {

serverSocket = new ServerSocket(port);

serverSocket.setSoTimeout(millisecondsTimeout);

}

public void run() {

System.out.printf("Server Starting

");

while (keepProcessing) {

try {

System.out.printf("accepting client

");

Socket socket = serverSocket.accept();

System.out.printf("got client

");

process(socket);

} catch (Exception e) {

handle(e);

}

}

}

private void handle(Exception e) {

if (!(e instanceof SocketException)) {

e.printStackTrace();

}

}

public void stopProcessing() {

keepProcessing = false;

closeIgnoringException(serverSocket);

}

344 Appendix A: Concurrency II

void process(Socket socket) {

if (socket == null)

return;

try {

System.out.printf("Server: getting message

");

String message = MessageUtils.getMessage(socket);

System.out.printf("Server: got message: %s

", message);

Thread.sleep(1000);

System.out.printf("Server: sending reply: %s

", message);

MessageUtils.sendMessage(socket, "Processed: " + message);

System.out.printf("Server: sent

");

closeIgnoringException(socket);

} catch (Exception e) {

e.printStackTrace();

}

}

private void closeIgnoringException(Socket socket) {

if (socket != null)

try {

socket.close();

} catch (IOException ignore) {

}

}

private void closeIgnoringException(ServerSocket serverSocket) {

if (serverSocket != null)

try {

serverSocket.close();

} catch (IOException ignore) {

}

}

}

Listing A-4

ClientTest.java

package com.objectmentor.clientserver.nonthreaded;

import java.io.IOException;

import java.net.ServerSocket;

import java.net.Socket;

import java.net.SocketException;

import common.MessageUtils;

public class Server implements Runnable {

ServerSocket serverSocket;

volatile boolean keepProcessing = true;

Listing A-3 (continued)

Server.java

Tutorial: Full Code Examples 345

public Server(int port, int millisecondsTimeout) throws IOException {

serverSocket = new ServerSocket(port);

serverSocket.setSoTimeout(millisecondsTimeout);

}

public void run() {

System.out.printf("Server Starting

");

while (keepProcessing) {

try {

System.out.printf("accepting client

");

Socket socket = serverSocket.accept();

System.out.printf("got client

");

process(socket);

} catch (Exception e) {

handle(e);

}

}

}

private void handle(Exception e) {

if (!(e instanceof SocketException)) {

e.printStackTrace();

}

}

public void stopProcessing() {

keepProcessing = false;

closeIgnoringException(serverSocket);

}

void process(Socket socket) {

if (socket == null)

return;

try {

System.out.printf("Server: getting message

");

String message = MessageUtils.getMessage(socket);

System.out.printf("Server: got message: %s

", message);

Thread.sleep(1000);

System.out.printf("Server: sending reply: %s

", message);

MessageUtils.sendMessage(socket, "Processed: " + message);

System.out.printf("Server: sent

");

closeIgnoringException(socket);

} catch (Exception e) {

e.printStackTrace();

}

}

private void closeIgnoringException(Socket socket) {

if (socket != null)

try {

socket.close();

Listing A-4 (continued)

ClientTest.java

346 Appendix A: Concurrency II

Client/Server Using Threads

Changing the server to use threads simply requires a change to the process message (new

lines are emphasized to stand out):

void process(final Socket socket) {

if (socket == null)

return;

Runnable clientHandler = new Runnable() {

public void run() {

} catch (IOException ignore) {

}

}

private void closeIgnoringException(ServerSocket serverSocket) {

if (serverSocket != null)

try {

serverSocket.close();

} catch (IOException ignore) {

}

}

}

Listing A-5

MessageUtils.java

package common;

import java.io.IOException;

import java.io.InputStream;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

import java.io.OutputStream;

import java.net.Socket;

public class MessageUtils {

public static void sendMessage(Socket socket, String message)

throws IOException {

OutputStream stream = socket.getOutputStream();

ObjectOutputStream oos = new ObjectOutputStream(stream);

oos.writeUTF(message);

oos.flush();

}

public static String getMessage(Socket socket) throws IOException {

InputStream stream = socket.getInputStream();

ObjectInputStream ois = new ObjectInputStream(stream);

return ois.readUTF();

}

}

Listing A-4 (continued)

ClientTest.java

Tutorial: Full Code Examples 347

try {

System.out.printf("Server: getting message

");

String message = MessageUtils.getMessage(socket);

System.out.printf("Server: got message: %s

", message);

Thread.sleep(1000);

System.out.printf("Server: sending reply: %s

", message);

MessageUtils.sendMessage(socket, "Processed: " + message);

System.out.printf("Server: sent

");

closeIgnoringException(socket);

} catch (Exception e) {

e.printStackTrace();

}

}

};

Thread clientConnection = new Thread(clientHandler);

clientConnection.start();

}

This page intentionally left blank

Bạn đang đọc truyện trên: Truyen247.Pro

Tags: #khoahoc