ArticleS. MichaelFeathers.
AnObjectIsaChoice [add child]

An Object is a Choice.


My favorite class design principle is The Single Responsibility Principle (SRP). The SRP states that a class should have only one reason to change. As principles go, SRP is tricky. It’s easy to see if you are breaking The Dependency Inversion Principle (DIP) or the Open/Closed Principle (OCP) but what are ‘reasons to change’ and how are we supposed to understand what they are in advance?

One heuristic that I use is to think of an object as a choice. Whenever we sit down to write a class we make a series of choices. We may choose to use one library over another or we may use one algorithm or another but the fact is, many of these choices are significant and the best thing that we can do is be conscious of the fact that we are making them. When we are, we often find places where objects help. Here’s an example.

A while back, I was working on a little code duplication detector for Java. It read Java source, line by line, and attempted to match it against snippets of source it had already seen. One of the key questions you have to ask yourself whenever you are doing this sort of matching is what criteria you’ll use for a match. It’s easy to demand exact matches when we want to detect duplication, but that’s too conservative: it misses code chunks that differ only by whitespace.

One way to do whitespace-invariant matching is to transform the lines as they come in. We can replace every run of whitespace with a single space. Once we've done that transformation on a line we can match it against other lines that we've transformed the same way.

Armed with that thought, I wrote a test:


public void testTransformLine() {
String source = "class Account {\tpublic void test(){}}";
assertEquals("class Account { public void test(){}}", DuplicationDetector.transformLine(source));
}



And some code to make it pass:


public class DuplicationDetector {
...
static String transformLine(String line) {
return line.replaceAll("\\s+", " ");
}
}


With that code in place, I ran my duplication detector on an old code base and found some duplication, but then I had some other questions. For instance: how much duplication could I find if I made the detector replace every identifier and keyword with the character 'x'? If I did that, I’d have a detector that ignored symbol information and looked directly at patterns in the source. What other algorithm could I use? I could just transform the identifiers to ‘x’s and leave the keywords alone. That would eliminate some ‘false positives’, patterns of duplication that we can’t really extract.

Clearly, the number of ways that we can transform the source when detecting duplication is endless: we can transform the source in any number of ways depending upon the type of duplication we’d like to highlight.

Whenever I'm this situation, I feel like I'm overwhelmed by choice. I feel like I’m making a choice, but I know that it can change, and more often than not that means I need another object.

In this case, a DuplicationDetector could use a LineTransformer class to do its work. Maybe we can have several different kinds of LineTransformers. We could have an ExactMatchTransformer that accepts a line and returns it with all of of its runs of whitespace converted to single spaces. And, we could have an IdentifierInvariantTransformer which takes a line and returns a copy of it that has all of its identifiers converted into 'x's.

Here's our DuplicationDetector with line transformation delegated to another class:

public class DuplicationDetector
{
private LineTransformer transformer;

public DuplicationDetector(LineTransformer transformer) {
this.transformer = transformer;
}

protected String transformLine(Line source) {
return transformer.transform(source);
}

...

}



If we are aware of the choices that we make as we make them, we can decide whether they are choices that can change or not. If they are, it’s worth thinking about creating objects that embody them.

If you've encountered the Strategy Pattern before, what I've been writing about is bound to sound a bit similar. Strategy is a design pattern which describes how to use objects to encode algorithms and make them swappable at run-time. If you have two ways of doing something you create an object for each of those ways, and make sure that the appropriate one is used at the appropriate time.

To me, it isn't much of a stretch to say that all classes are really strategies in a way. Whenever we make a reference refer to an object, we are making a choice, and it is a choice that we can change by making the reference refer to another object.. provided we've made objects that embody meaningful choices.


!commentForm
 Mon, 2 Jan 2006 17:43:13, Tom Rossen, Strategy Pipeline?
In this particular example, you might want to use a pipeline of transformers, e.g., to normalize whitespace and abstract identifiers. Not sure how this impacts your general argument, which is excellent.
 Mon, 2 Jan 2006 19:01:31, Michael Feathers,
Tom, thanks. I actually dropped the project because I found CPD, the duplication detector that comes with PMD. It does a great job.
 Tue, 3 Jan 2006 07:55:49, David Chelimsky, don't forget yagni
"If we are aware of the choices that we make as we make them, we can decide whether they are choices that can change or not."

Seems like YAGNI applies here, yes? There's a cost to Strategy in that you have more tests (additional interaction tests) and more objects in your system. If you've only got one strategy for a calculation, then why introduce the additional infrastructure? Perhaps the calculation is complex enough that you consider it an SRP violation even without considering YAGNI. It seems like in this case YAGNI and SRP are opposing forces, and they have to be balanced.

- I was trying to figure out how to work that in, but I gave it a miss. Actually, I never moved to the strategy in this example. I rewrote transformLine a few times as I moved towards more advanced duplication detection. If I wanted to be able to switch among them, I would have used the strategy, though. - Michael Feathers
 Tue, 3 Jan 2006 19:40:44, dhui,
I don't think YAGNI and SRP are opposing forces in the case. If the calculation is complex, you can consider it an SRP violation. But I don't think this means you must evolve to Strategy Pattern and all the additional infrastructure the pattern needs. I think you can just create another concrete class that do this specific calculation. YAGNI doesn't mean you aren't going to need put the right responsibility into right object. It really means you aren't going to need the extra flexibility that makes no sense currently.
 Tue, 3 Jan 2006 21:34:45, David Chelimsky, YAGNI vs SRP... vs DIP
OK, so you move the complex calculation to another concrete class to honor Single Responsibility. Now you're violating the Dependency Inversion Principle, so you implement Abstract Server, forcing the client to supply the concrete implementation of a new interface that you extract from the calculation object and kazaam - you have all of the infrastructure you need for Strategy - but YAGNI!

I think that all of the principles can represent opposing forces given the right context, and they always have to be balanced. There's no one that's always more important - there's just the one the one that's more important in the current context.
 Tue, 3 Jan 2006 22:50:13, dhui,
If there is no need of another way to calculate, I don't think DIP makes sense here. I think the force of making a new interface comes from test. If I want to make test easy I'll have to supply a interface of the concrete implementation.

I think SRP is the fundamental principle. If you do not comply with SRP, you will have a hard time to comply other principles. First, you should make the object right, I dont't think it is in conflict with YAGNI.

- I agree. I'd hold off an interface until I need it. I also agree that SRP is really the fundamental principle from an engineering point of view. I think that from the human point of view, the fundamental goal is always clarity. -- MichaelFeathers

 Wed, 4 Jan 2006 15:37:49, nraynaud, YAGNI
I should confess, that I don't do meny thing I tell others to do.

I don't extract an interface in a case like this one until I've to create a second concrete implementation of the transformer. Actually, I never create an interface unless :
- I've to create a second concrete implementation (but it comes early with the needs for fake object).
- The only implementor is "far" from the interface definition. As an example, I create listener interfaces for my event source since listerners are usually a mean to cross the border between packages and concerns (Gui listening to business objects is an example, I won't couple thing so far from each other).


I know this behavior destroys my metrics (coupling at least), but it saves me some time, code, and creating an interface is only a matter of right menu and checkboxes.
 Wed, 4 Jan 2006 19:22:48, dhui,
>>- I agree. I'd hold off an interface until I need it. I also agree that SRP is really the fundamental >>principle from an engineering point of view. I think that from the human point of view, the fundamental >>goal is always clarity. -- MichaelFeathers

I agree!