Tải bản đầy đủ
Chapter 24: We Feel Overwhelmed. It Isn’t Going to Get Any Better

Chapter 24: We Feel Overwhelmed. It Isn’t Going to Get Any Better

Tải bản đầy đủ

320

We Feel
Overwhelmed

W E F EEL O VERWHELMED . I T I SN ’ T G OING

TO

G ET A NY B ETTER

the developers are working on the old system. The system is in service, so they
receive requests for bug fixes and occasionally new features. The business looks
soberly at each new feature and decides whether it needs to be in the old system
or whether the client can wait for the new system. In many cases, the client
can’t wait, so the change goes in both. The green-field team has to do doubleduty, trying to replace a system that is constantly changing. As the months go
by it becomes clearer that they are not going to be able to replace the old system, the system you’re maintaining. The pressure increases. They work days,
nights, and weekends. In many cases, the rest of the organization discovers that
the work that you are doing is critical and that you are tending the investment
that everyone will have to rely on in the future.
The grass isn’t really much greener in green-field development.
The key to thriving in legacy code is finding what motivates you. Although
many of us programmers are solitary creatures, there really isn’t much that can
replace working in a good environment with people you respect who know
how to have fun at work. I’ve made some of my best friends at work and, to
this day, they are the people I talk to when I’ve learned something new or fun
while programming.
Another thing that helps is to connect with the larger community. These
days, getting in touch with other programmers to learn and share more about
the craft is easier than it ever was. You can subscribe to mailing lists on the
Internet, attend conferences, and take advantage of all the resources that you
can use to network, share strategies and techniques, and generally stay on top
of software development.
Even when you have a bunch of people on a project who care about the
work and care about making things better, another form of dejection can set in.
Sometimes people are dejected because their code base is so large that they and
their team mates could work on it for 10 years but still not have made it more
than 10 percent better. Isn’t that a good reason to be dejected? Well, I’ve visited
teams with millions of lines of legacy code who looked at each day as a challenge and as a chance to make things better and have fun. I’ve also seen teams
with far better code bases who are dejected. The attitude we bring to the work
is important.
TDD some code outside of work. Program for fun a little bit. Start to feel the
difference between the little projects you make and the big project at work.
Chances are, your project at work can have the same feel if you can get the
pieces you work with to run into a fast test harness.
If morale is low on your team, and it’s low because of code quality, here’s
something that you can try: Pick the ugliest most obnoxious set of classes in the

From the Library of Brian Watterson

321
project, and get them under test. When you’ve tackled the worst problem as a
team, you’ll feel in control of your situation. I’ve seen it again and again.
As you start to take control of your code base, you’ll start to develop oases
of good code. Work can really be enjoyable in them.

We Feel
Overwhelmed

From the Library of Brian Watterson

This page intentionally left blank

From the Library of Brian Watterson

Part III

Dependency-Breaking
Techniques

DependencyBreaking
Techniques

From the Library of Brian Watterson

This page intentionally left blank

From the Library of Brian Watterson

Chapter 25
DependencyBreaking
Techniques

Dependency-Breaking
Techniques
In this chapter, I’ve written up a set of dependency-breaking techniques. This
list is not exhaustive; these are just some techniques that I’ve used with teams to
decouple classes well enough to get them under test. Technically, these techniques are refactorings—each of them preserves behavior. But unlike most
refactorings written up in the industry so far, these refactorings are intended to
be done without tests, to get tests in place. In most cases, if you follow the steps
carefully, the chance of mistakes is small. This doesn’t mean that they are completely safe. It is still possible to make mistakes when you perform them, so you
should exercise care when you use them. Before you use these refactorings, see
Chapter 23, How Do I Know That I’m Not Breaking Anything? The tips in
that chapter can help you use these techniques safely so that you can get tests in
place. When you do, you’ll be able to make more invasive changes with more
confidence that you aren’t breaking anything.
These techniques do not immediately make your design better. In fact, if you
have good design sense, some of these techniques will make you flinch. These
techniques can help you get methods, classes, and clusters of classes under test,
and your system will be more maintainable because of it. At that point, you can
use test-supported refactorings to make the design cleaner.
A few of the refactorings in this chapter were described by Martin Fowler in his book
Refactoring: Improving the Design of Existing Code (Addison-Wesley, 1999). I’ve
included them here with different steps. They’ve been tailored so that they can be
used safely without tests.

325
From the Library of Brian Watterson

326

D EPENDENCY -B REAKING T ECHNIQUES

Adapt Parameter
Adapt Parameter

When I make changes to methods, I often run into dependency headaches
caused by method parameters. Sometimes I find it hard to create the parameter
I need; at other times, I need to test the effect of the method on the parameter.
In many cases, the class of the parameter doesn’t make it easy. If the class is one
that I can modify, I can use Extract Interface (362) to break the dependency.
Extract Interface is often the best choice when it comes to breaking parameter
dependencies.
In general, we want to do something simple to break dependencies that prevent testing, something that doesn’t have possibilities for errors. However, in
some cases, Extract Interface (362) doesn’t work very well. If the parameter’s
type is pretty low level, or specific to some implementation technology, extracting an interface could be counterproductive or impossible.
Use Adapt Parameter when you can’t use Extract Interface (362) on a parameter’s
class or when a parameter is difficult to fake.

Here is an example:
public class ARMDispatcher
{
public void populate(HttpServletRequest request) {
String [] values
= request.getParameterValues(pageStateName);
if (values != null && values.length > 0)
{
marketBindings.put(pageStateName + getDateStamp(),
values[0]);
}
...
}
...
}

In this class, the populate method accepts an HttpServletRequest as a parameter.
HttpServletRequest is an interface that is part of Sun’s J2EE standard for Java. If
we were going to test populate the way it looks now, we’d have to create a class
that implements HttpServletRequest and provide some way to fill it with the
parameter values it needs to return under test. The current Java SDK
documentation shows that there are about 23 method declarations on
HttpServletRequest, and that doesn’t count the declarations from its
superinterface that we’d have to implement. It would be great to use Extract
Interface (362) to make a narrower interface that supplies only the methods we
need, but we can’t extract an interface from another interface. In Java, we would

From the Library of Brian Watterson

A DAPT P ARAMETER

need to have HttpServletRequest extend the one we are extracting, and we can’t
modify a standard interface that way. Fortunately, we do have other options.
Several mock object libraries are available for J2EE. If we download one of
them, we can use a mock for HttpServletRequest and do the testing we need to
do. This can be a real time saver; if we go this route, we won’t have to spend
time making a fake servlet request by hand. So, it looks like we have a solution—or do we?
When I’m breaking dependencies, I always try to look ahead and see what
the result will look like. Then I can decide whether I can live with the aftermath. In this case, our production code will look pretty much the same, and we
will have done a lot of work to keep HttpServletRequest, an API interface, in
place. Is there a way to make the code look better and make the dependency
breaking easier? Actually, there is. We can wrap the parameter that is coming in
and break our dependency on the API interface entirely. When we’ve done that,
the code will look like this:

327

Adapt Parameter

public class ARMDispatcher
public void populate(ParameterSource source) {
String values = source.getParameterForName(pageStateName);
if (value != null) {
marketBindings.put(pageStateName + getDateStamp(),
value);
}
...
}
}

What have we done here? We’ve introduced a new interface named
ParameterSource. At this point, the only method that it has is one named
getParameterForName. Unlike the HttpServletRequest getParmeterValue method, the
getParameterForName returns only one String. We wrote the method that way

because we care about only the first parameter in this context.
Move toward interfaces that communicate responsibilities rather than implementation details. This makes code easier to read and easier to maintain.

Here is a fake class that implements ParameterSource. We can use it in our test:
class FakeParameterSource implements ParameterSource
{
public String value;
public String getParameterForName(String name) {
return value;
}
}

From the Library of Brian Watterson

328

D EPENDENCY -B REAKING T ECHNIQUES

And the production parameter source looks like this:
class ServletParameterSource implements ParameterSource
{
private HttpServletRequest request;
Adapt Parameter
public ServletParameterSource(HttpServletRequest request) {
this.request = request;
}
String getParameterValue(String name) {
String [] values = request.getParameterValues(name);
if (values == null || values.length < 1)
return null;
return values[0];
}
}

Superficially, this might look like we’re making things pretty for pretty’s
sake, but one pervasive problem in legacy code bases is that there often aren’t
any layers of abstraction; the most important code in the system often sits intermingled with low-level API calls. We’ve already seen how this can make testing
difficult, but the problems go beyond testing. Code is harder to understand
when it is littered with wide interfaces containing dozens of unused methods.
When you create narrow abstractions targeted toward what you need, your
code communicates better and you are left with a better seam.
If we move toward using ParameterSource in the example, we end up decoupling the population logic from particular sources. We won’t be tied to specific
J2EE interfaces any longer.
Adapt Parameter is one case in which we don’t Preserve Signatures (312). Use extra care.

Adapt Parameter can be risky if the simplified interface that you are creating
for the parameter class is too different from the parameter’s current interface. If
we are not careful when we make those changes, we could end up introducing
subtle bugs. As always, remember that the goal is to break dependencies well
enough to get tests in place. Your bias should be toward making changes that
you feel more confident in rather than changes that give you the best structure.
Those can come after your tests. For instance, in this case we may want to alter
ParameterSource so that clients of it don’t have to check for null when they call its
methods (see Null Object Pattern (112) for details).
Safety first. Once you have tests in place, you can make invasive changes much more
confidently.

From the Library of Brian Watterson

A DAPT P ARAMETER

329

Steps
To use Adapt Parameter, perform the following steps:
1. Create the new interface that you will use in the method. Make it as simple and communicative as possible, but try not to create an interface that
will require more than trivial changes in the method.

Adapt Parameter

2. Create a production implementer for the new interface.
3. Create a fake implementer for the interface.
4. Write a simple test case, passing the fake to the method.
5. Make the changes you need to in the method to use the new parameter.
6. Run your test to verify that you are able to test the method using the fake.

From the Library of Brian Watterson

330

D EPENDENCY -B REAKING T ECHNIQUES

Break Out Method Object
Break Out
Method Object

Long methods are very tough to work with in many applications. Often if you
can instantiate the class that contains them and get them into a test harness, you
can start to write tests. In some cases, the work that it takes to make a class separately instantiable is large. It may even be overkill for the changes you need to
make. If the method that you need to work with is small and doesn’t use
instance data, use Expose Static Method (345) to get your changes under test.
On the other hand, if your method is large or does use instance data and methods, consider using Break Out Method Object. In a nutshell, the idea behind this
refactoring is to move a long method to a new class. Objects that you create
using that new class are called method objects because they embody the code of
a single method. After you’ve used Break Out Method Object, you can often
write tests for the new class easier than you could for the old method. Local variables in the old method can become instance variables in the new class. Often
that makes it easier to break dependencies and move the code to a better state.
Here is an example in C++ (large chunks of the class and method have been
removed to preserve trees):
class GDIBrush
{
public:
void draw(vector& renderingRoots,
ColorMatrix& colors,
vector& selection);
...
private:
void drawPoint(int x, int y, COLOR color);
...
};
void GDIBrush::draw(vector& renderingRoots,
ColorMatrix& colors,
vector& selection)
{
for(vector::iterator it = renderingRoots.begin();
it != renderingRoots.end();
++it) {
point p = *it;
...
drawPoint(p.x, p.y, colors[n]);
}
...
}

From the Library of Brian Watterson