The Value of Symmetry

Monday, February 7, 2011

Pablo Picasso. Girl before a Mirror. Boisgeloup, March 1932Symmetry in code is valuable, and often subtle. When I find symmetry in code, I feel it's a positive sign that the design is working well.

I like temporal symmetry. When something opens at the very beginning of a request, I'll expect it to close at the very end of a request.

I like conceptual symmetry. Milan Negovan has a discussion about symmetry on an old blog post (Achieving Code Symmetry), and includes examples from Kent Beck's Implementation Patterns book.

I also like structural symmetry. It sounds silly to judge code based on it's visual representation in a text editor, but I was recently thrown by a bit of code that looked like this.

if (widget.IsRegular)
{
    service.Process(widget);
}
else
{
    var result = widget.Calculation();
    widget.ApplyResult(result);
    service.ProcessSpecial(widget);
}

When you get down to look at the detail of the code you might find it violates any number of principles and object oriented design goals, but with just a glance something appears amiss. One path appears more complex than the other. Why? The asymmetry breaks my concentration and makes the code harder to read.

After a bit of refactoring, the code could look like this:

if (widget.IsRegular)
{
    service.Process(widget);
}
else
{
    service.ProcessSpecial(widget);
}

Now there is a balance in the code, and any additional problems might be easier to see.


Comments
gravatar Bruno Martinez Monday, February 7, 2011
Care is needed to make sure symmetry doesn't in fact hide the copy&paste anti pattern.
gravatar Matt Briggs Monday, February 7, 2011
WAYYYYYYYY back in smalltalk design patterns, kent beck said to try to keep methods all at the same level of abstraction, this is a great example of it. Having high level methods on one side of an if branch, and low level methods on the other means that you aren't breaking your methods apart properly.
gravatar scott Monday, February 7, 2011
@Matt - ah!
gravatar Mike Minutillo Tuesday, February 8, 2011
I realize this is just an example but I'd be tempted to look at the original code and see that it is messing with widget a lot (and asking it things, which violates Tell, Don't Ask). I'd probably end up moving the whole method inside the Widget class and giving it the service as a parameter.

The second example hides that complexity and moves the widget-related code even further away from the widget class. For example, here is what it might end up looking like:

// ... Original Code ...
widget.ProcessWith(service);

class Widget {
public void ProcessWith(IService service){
// Was it necessary to expose the IsRegular flag? Isn't that internal state?
if(IsRegular) {
service.Process(this);
} else {
// This next line looks weird now. (And it should, it's likely a design smell)
// Why do we expose a calculation publicly just to update our internal state?
ApplyResult(Calculation());
service.ProcessSpecial(this);
}
}
}

Again, this may just be caused by the fact that this is a generic example and the original point is still valid, I'd just be wary that by moving complexity this way you might be sweeping it under a rug instead of improving it.
gravatar Jef Claes Tuesday, February 8, 2011
Totally agree. I love opening an existing solution and finding out the code by itself just looks good. Makes me optimistic of the underlying design to expect. You get the feeling the code has been written by someone who cares.
Casey Kramer Tuesday, February 8, 2011
@Matt - You'll find the same guidance from Neal Ford (in The Productive Programmer), and Bob Martin (in Clean Code). I got to attend a talk Neal gave based on his book, and he demonstrated how applying the refactoring can lead to additional opportunities for re-use that were not apparent before the method was refactored. Its one of the rules I've been trying to apply to all of the code I write anymore.
Dave Thursday, February 10, 2011
I don't get it. So ok it's clearer, but do I have to assume then that your service.ProcessSpecial(widget); will perform the
widget.ApplyResult(widget.Calculation());

??

It's like you're missing some explanation here.
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!