Wed, 06 Apr 2005
Introducing Agile to a legacy project
I was talking with a potential client about what I might do for
them. Suddenly I realized I'd had this conversation before. So I
decided to write down my talking points.
I'm almost always contacted about testing in an Agile project.
My biases about that are described in "A Roadmap
for Testing on an Agile Project". A problem with that roadmap is
that it really assumes either a
greenfield project or an Agile project that's well underway. But some
people I talk to are just starting with Agile, working
on a legacy code base that really needs cleaning up, and don't have
much in the way of testing. That makes that roadmap less applicable.
My talking points follow. Notice how few of them are about
testing. When I
first started consulting with Agile projects, I tried - well, some -
to stick to my testing knitting. But either it made no
sense to wall off testing from other concerns or the existing wall was
clearly a problem. So now I stick my nose into all sorts of business.
Have at least half the programmers read Michael Feathers'
wonderful Working
Effectively with Legacy Code before bringing me in. It describes many tricks and ways of
thinking for getting your arms around legacy code - mainly by
wrapping it with tests. And if you bring Michael
in instead of me, that's fine.
Be wary of large-scale code cleanups. Ron Jeffries has an
excellent analogy: cleaning up legacy code is like cleaning up
a horrendously messy kitchen. You can set aside a day, work
like a dog, and leave it pristine. Great. What happens next? It gets
dirty again, dish by dish. You don't have the habits that help
you keep it clean.
The better alternative is to clean up gradually. Every day, wash the
dishes you dirty, plus a couple more. Over time, the kitchen
will get clean - and you'll have the habit of immediately
cleaning up your mess.
A dirty kitchen has an advantage over software projects: you can
see it getting cleaner. When projects declare that they're going
to spend the next N months making the code right, then get back
to adding features, that's N
months where no one outside the project can see anything
happening. That's an unstable situation, and those who pay
the bills quite often put a stop to the cleanup partway through -
which usually means the code is not a whole lot more tractable.
For that reason, I recommend weaving cleanup into the delivery
of business value. Suppose you're fixing a bug. You might have
occasion to look at several classes as you follow the execution
path that leads to the failure. Leave each of those classes
slightly better than you found it, even if the change has nothing
to do with fixing the bug. Over time, the system will get better,
and you'll have the right habits.
That latter point is important. Being a good Agile programmer means
learning how to incrementally grow a good design while at the
same time doing something else (like adding
features). You don't learn how to do that by doing something
different, like a rewrite.
Suppose you have a team that's not doing programmer testing,
especially not doing test-driven design. I've come to believe
the test-writing part of TDD is actually easier to learn than
the refactoring part. A surprising number of programmers have no
visceral dread of duplication, especially its more subtle
forms like boolean
parameters. Or they think of picking intention-revealing
names as just a help for someone later, not a tool for
thinking more clearly about the code in front of them. And so on.
So the team has to commit to learning those and other
similar things.
The project has to explicitly become a learning
project. I recommend
pair programming and colocation as ways of
spreading learning quickly. Use Big
Visible Charts to nudge people toward more learning. (For
example, consider putting up a list of Feathers' tricks and
having people initial those they've used.) When people do some
clever cleanup, they should announce it at the daily standup
and offer to demonstrate. Actually, I'd rather they be so
jazzed about what they've just done that they immediately grab
someone to show. Even working on legacy code, even in learning
mode, an Agile project should have a buzz.
When the project is ticking over smoothly, programmers will
test out of desire, not duty. They will be nervous enough
about not having a strong safety net of tests that they will make sure
it's always strong. But getting there can be tough. You're not
starting with a safety net, so the first tests you write won't
help much. And writing them will be slow and
painful because of all the dependencies. (See Feathers' book.)
It's easy to give up halfway. All I can recommend is to
consciously (if subjectively) track whether things are getting
better. If they're not, do something about it.
-
It probably makes sense to pursue programmer testing and
automated whole-product testing at the same time. Since the
whole-product tests test from the outside, they're less affected
by a rat's nest of dependencies. If they're faster to write,
they'll provide confidence sooner. But avoid having certain
programmers assigned to
whole-product test automation. Testing should belong to everyone
on the team, and everyone should be willing and able to add new
tests. All the programmers should be ready to extend the test
harness or other test support code.
Testers on the team should focus on stabilizing the product. It
is not enough for tests to be good at finding bugs; they must
also support the programmers. Mainly that means they should be
automated, can run on any machine, and run quickly. You want
the programmers to run them frequently to bolster their
confidence as they change code.
When the programmers sign up to work on a task, that means the
testers are also signed up to create tests that stand a good
chance of finding unintended consequences of the particular
changes the programmers are going to make. That will involve much
more communication than before, including pair work. Therefore, if
the programmers are in a bullpen, the testers should be there
too. Also, the testers should teach the programmers how to "think
like a tester" so that they can avoid bugs in the first place.
In this way, the testers serve the programmers (a role some are
uncomfortable with). In return, the programmers should be
prepared to make changes to the code that are directly in
service of making whole-product tests easier to write. (If
programmers are unwilling or grudgingly willing, the team has a
task: turning duty into desire.)
Where there's a Customer or product owner on the
project, the tester also serves her. One way is to help her
express what she wants in a clear form that can be turned into
checkable examples (that is, tests). The tester should also try to
think of questions the programmers aren't asking. That implies
boning up on the business domain. (Testers often already
know it well, if idiosyncratically). I often think of testers as
shepherds of the conversation between Customers and
programmers. It's a more social role than a lot of testers have
had before.
-
Sometimes teams are jumping into Agile to avoid the horror of
the previous release. The code was too buggy, or too late, or cost too much
per feature, or all three. In such a case, the programmer team is
probably not trusted by the business people. If so, trust is an
essential deliverable. It's not enough to be better; you have to be
visibly better soon. Delivering tested, working features
at frequent intervals is a key way to get trust back. Another way
is close
cooperation with a product owner that demonstrates that the team's
orientation is toward helping her meet her goals. But more
generally, the team should pay active attention to how well
they're doing at building trust, not just at building code.
That's all I can think of now. What else?
## Posted at 21:01 in category /agile
[permalink]
[top]
array[array.size], (car nil), and a certain sense of balance
Last night, I was disappointed by a bug. It was in some Python code
I was writing for the Agile Alliance web site. In Python, indexing
one past the end of the array throws an error. In Ruby, it
returns nil . Since I'm used to Ruby, I unconsciously
took advantage of its behavior.
To fix the problem, I added an if statement. A small
price to pay, a Pythonist might say, for conceptual clarity. How is
it sensible to talk about the value of something that isn't there?
Perhaps so. But I was at that moment reminded of a wonderful
email. I think it was from George Carrette to some Common Lisp
mailing list, circa 1984. He wrote of a dream in which he was
inspired to eschew all the hackish shortcuts of regular Lisp and
translate a program into Scheme. First he stopped taking advantage
of the way traditional Lisp lets you ask for the first element of an
empty list (that is, (car nil)
is nil ). Then he stopped treating "false" and "the
empty list" as the same thing. Etc.
As he went on, the code kept getting bigger and not so nice, but he
maintained the pretense of being an enthusiastic convert to logical
purity. It was a tour de force of sarcasm, I thought, and I'd love
to get a copy if anyone saved it.
That makes him seem just snarky, but a Google
search shows Carrette also involved as a contributor to the
Scheme intellectual family tree. The Great Lispers of the Past had
that ability to flip rapidly between the ideal and the practical:
understanding the power that comes from conceptual simplicity and
uniformity, but also realizing that there are non-logical special
cases with pragmatic value. Able to be at the most ethereal levels
(the Y
combinator) at one moment, in amongst PDP-10 assembly code the
next.
It's a balance I'd like to have.
(See also the Null
Object pattern. How often do null objects actually make sense
when you think about them, and how often are they just ways to
remove if statements?)
## Posted at 07:20 in category /misc
[permalink]
[top]
|
|