Note: This blog is no longer active. Please visit Jeff's current blog instead. Unfortunately, any new comments entered cannot be retained or displayed. If you feel strongly about a blog entry, please contact us.
I encountered a bit of code recently that screams out for refactoring. Problem is, I'm not sure of the most effective way to do this.
The context: this is a tic-tac-toe game that Paul Nelson and I paired on, in order to explore mobile device programming (Paul has an iPhone, I have a BlackBerry). Paul's not here right now to pair, unfortunately, hence the blog request (a really slow way of virtual pairing).
Here's the relevant code:
private boolean isRowWinner(int row) {
for (int column = 0; column < SIZE; column++)
if (grid[row][column] != currentPlayer)
return false;
return true;
}
private boolean isColumnWinner(int column) {
for (int row = 0; row < SIZE; row++)
if (grid[row][column] != currentPlayer)
return false;
return true;
}
private boolean isDescendingDiagonalWinner() {
for (int i = 0; i < SIZE; i++)
if (grid[i][i] != currentPlayer)
return false;
return true;
}
private boolean isAscendingDiagonalWinner() {
for (int i = 0; i < SIZE; i++)
if (grid[i][SIZE - 1 - i] != currentPlayer)
return false;
return true;
}
The field grid is simply a 3x3 matrix of Player references; Player is an enum; currentPlayer is a Player reference.
What's the most effective way to simplify these four methods, seemingly rampant with duplication? I'm not looking for a "clever" solution; I'm looking for one that still retains high levels of expressiveness.
UPDATE: tests and code
The Scrum Yahoo! group is again undergoing some minor turbulence about proper use of the list. Someone from the Scrum Alliance laid out the law with a number of rules about what could and could not be posted to the list. Many people reacted poorly--supposedly this is the poster's first post, and he's not even a Certified Scrum Master (CSM). (gasp!)
Oddly, a year or so ago when there was similar agonizing over the use of the list, it appeared as if L. Ken Schwaber "owned" the Yahoo! group and thus controlled (an important word in the Scrum community) appropriate content. Not that there's anything wrong with that. For those who like command & control, with the additional potential of being part of a good multi-level marketing machine, the Scrum system provides a wonderful starting point.
The nice thing is that you can still do Scrum and talk about Scrum without succumbing to the controlled community that is S©rum. As far as I know, there's no licensing scheme required to be able to say, "I'm doing Scrum." And that's exactly what I'd recommend. No annual maintenance fee required! No required trainer tiering! Yes, you need a Scrum Master in order to successfully execute Scrum. But I don't believe they must be "certified." (I could be wrong--perhaps I need a lawyer here!)
Where might you find a good Scrum Master? Well, you should probably read L. Ken Schwaber's book, Agile Project Management With Scrum (that link is my piece of the Scrum machine's gravy train, by the way). You'll quickly discover, through a number of case studies that L. relates, that Scrum and S©rum are mostly a matter of executing to common sense, best done by someone with good experience in people-oriented problem solving and leadership. Find someone who can do that well, and hire them. It really doesn't matter if they're a CSM.
"Plain Old Unit Testing," or "POUT," implies that developers are coding unit tests, but they are not doing test-driven development (TDD). In other words, they are following the classic pattern of writing the code, then coming back and coding unit tests to verify the correctness of the code. Instead of POUT, I prefer the acronym TAD, or Test-After Development, which is always a "TAD" too late.
Why does it matter whether tests are written first or last? Theoretically, someone doing TAD could easily produce as high-quality a system with as comprehensive test coverage as produced via TDD. In reality, however, it never happens.
Here are some of the reasons why TAD sucks:| Doesn't promote a testable design | Most of the time, testability is in concert with notions of "good," or sustainable, design (coupling and cohesion and all that). Usually, developers discover too late that their design isn't testable. Most often in this case, they quickly give up on trying to test the code, Over time, sure, developers figure out how to produce a more testable design out of the gate, but it's still never a certainty. The real problem is that almost no developer will go back and fix an untestable design in order to be able to write tests. |
| Is undisciplined | No rule says that a developer must write tests. Good developers might write enough (usually they do not), but there are plenty of not-so-good developers that will use the lack of a standard as a quick excuse to write as few tests as possible. |
| Falls victim to ego | "My sh*t don't stink, I don't need to bother with writing unit tests, so I'll do the fewest that I can get away with." Enough said. This attitude doesn't belong in a professional development team, but it's sadly common. |
| Appears unnecessary | "I already proved that the code works, I loaded it up in the app server and tried it myself. Why would I bother writing unit tests now?" |
| Clashes with deadlines | When it comes down to deadlines, managers will quickly say, "Never mind with the unit tests, we just have to ship this software." |
| Decreases average coverage | For many of the reasons already stated, the practical coverage ceiling for TAD is about 70%. This is based on lots of anecdotal evidence, often stated by TAD proponents themselves. I met with Agitar reps about 18 months ago; they confirmed that their TAD-tool (which generates unit tests automatically) has a comparable ceiling. No matter how TAD tests get there, the result is that up to 30% of your app remains unverified at the unit level. Maybe that's not such a big deal if you have higher-level (integration) tests, but... |
| Doesn't afford adequate refactoring | If up to 30% of your app is not well-covered, that means you cannot safely and effectively refactor almost a third of your application. A third of your system will progressively get worse almost by definition. |
| Results in difficult tests | Many TAD tests are closer to integration tests in nature. "Who cares," say some of the TAD proponents, "about the distinction between unit tests and integration tests?" I don't care about the semantic distinction either, but integration tests are harder to maintain, harder to comprehend, harder to use to pinpoint a problem, and slower. |
| Isn't very enjoyable | Few people enjoy writing unit tests; many people will claim that they love TDD. Honestly, it's not very satisfying to spend time on unit tests when you're pretty sure the code already works. |
| Questionable return on value | TAD is expensive. If you have to choose between TAD and more end-to-end functional tests, dump the TAD. |
| Does nothing to advance the craft | TAD is a haphazard approach. TAD proponents insist that we're all smart enough to figure out what should be verified at the unit level, and what can be safely ignored. Reality tells us otherwise, but still, that choice may be acceptable in some shops. However, the industry has been screaming for a more disciplined approach to software development that can consistently produce higher code quality. Perhaps TDD isn't it, but TAD most certainly ain't it. |
I love competition, and I suspect the majority of programmers do, too. I've met only a few very good programmers who weren't into games or other forms of competition. I've also known a few developers whose egos exuded in everything they did, whether or not they'd admit it. These are the kinds who want to argue virtually every point that is made, not that there's anything wrong with that. (Well, maybe there is--no one wants to feel like every one of their comments is possibly wrong.)
In listening to someone else's contrarian nature take over, I often sit there and think, "We should just argue our respective positions in code." Of course, I'm also thinking that "I can code this better than you can."
The site TopCoder provides a place where money can be put in someone's big mouth, but it's not what I'm looking for. TopCoder is more a highly competitive marketplace, a great idea that appeals to my capitalistic nature. But it doesn't appeal to the ego in me, nor does it appeal to my interest in TDD.
Rather, I'd prefer a competition that offers bragging rights. Not that I need to brag, but I would love some validation to my claims. I think as geeks we all like numbers, and I'd like to know if I am the best, or if I am only the 17th best.
So, a contest. Who is the most effective coder? Here are my thoughts for such a contest:
Could such a contest be used to determine a champion between TDD proponents and the rest of the world?
If I can get enough interest, I'll happily run such a contest and offer up some kind of prize, although I'd just as soon participate in one. Step one involves getting feedback--are there enough fearless programmers who would participate? How would the judges come to at least some consensus on judging criteria?
Are you the best? Do you have any ideas about how to judge such a competition? Let me know.
Seeing a first-time demo of TDD, students inevitably ask, "do you really code that way?" The answer is yes, even after almost ten years of doing TDD.
They are of course asking, do I always code the simplest possible solution first, instead of just introducing the obvious solution, the one that I know I'm absolutely going to need in another five minutes? And the answer is still yes.
For example, when testing a container, the first test inquires whether or not it's empty:
assertEquals(0, container.size());and the passing implementation is:
int size() {
return 0;
}
The next test usually involves adding an element to the container and asserting its size (which is of course only part of the complete verification):
container.add(element); assertEquals(1, container.size());Yes, I know I need a [hash map|array list|linked list|etc] in about 5 minutes. I still code:
void add(T element) {
count = 1;
}
int size() {
return count;
}
Not even a ++. That comes from writing a subsequent, failing assertion.
I offer many reasons:
Fine-grained incrementalism is, as many things in software development, a tradeoff. It results in continual waste product, and thus a slower initial pace. A hardcoded value is discarded, and replaced with a counter, which too is soon discarded. But on the other side of this trade is the significant benefit of continual and consistent forward progress--"slow and steady wins the race."
For the new TDD practitioner, and for even experienced TDD practitioners, it is very tedious and annoying. Often, developers simply discard rigid adherence to the practice. For an experienced TDD developer, that's their prerogative, and I'll admit that I have done so at times too.
For everyone else, I think it's essential to first learn the value of something before discarding it. So I'll define my use of "experienced TDD developer" in the prior paragraph as "someone who has not done enough fine-grained incremental development to understand how it helps." Perhaps I'm slow--I still prefer to work this way.
A final justification (aka "the new thought" that entered my head this morning):
Yes, it's slower, tedious, and produces continual waste, particularly as you build the rudiments of a class. But these complaints are focused at the early minutes in the lifetime of a class--a fraction of the effort and struggle involved with maintaining it. There's a benefit I didn't explicitly mention above, and that's the ability to more easily and safely maintain a system, because of the increased number of assertion increments. The small amount of initial tedium is worth it.
February 2004 March 2004 May 2004 September 2004 October 2004 January 2005 February 2005 September 2005 October 2005 November 2005 December 2005 January 2006 February 2006 March 2006 June 2006 August 2006 January 2007 February 2007 March 2007 April 2007 September 2007 October 2007 November 2007 December 2007 January 2008 February 2008 March 2008 April 2008 May 2008 June 2008 July 2008 August 2008 September 2008 October 2008 November 2008 December 2008 January 2009 February 2009 March 2009 April 2009 June 2009