Curbing long tail design

One of the perks of my job (and talking to a lot of folks) is that I get to see a lot of people’s actual code. Not gists, blog examples, or GitHub playgrounds, but real, actual, production code. Some code is good, some bad, and some awful. What is universal is that everyone thinks that everything sucks.

But it doesn’t have to be this way. Often times, the reasons the codebase lost its way was not because any one piece is awful, but the overall design lacks any sort of cohesiveness.

Instead of having one design, the codebase has many competing designs. Whenever a developer adds a new feature, they have to hunt for the last “good” feature. Design follows a long road:


We start out with the first few features as V1.0 of the design and architecture. As we learn more, we augment our design to V1.1. Many more features are added with this design, until we make another breakthrough in the design with V1.2. One developer read something cool on a blog, augmented the design in one spot, and now we have V1.2.1. It continues along this path until we have a long tail design:


A new developer coming to this team has no chance! There are many problems with evolving a system this way.

First, we don’t really know what the “right” design is. If we need to augment an old feature, is that the “right” design? Do we just pick the last new feature built, and copy that design for our next new feature?

Second, the ability to innovate in our designs really comes from having a broad view of the system. If I have eight different ways of doing something, I can’t really innovate. If I had many examples of a design in use, it’s a lot easier to see where the design works well, where it doesn’t, and what needs to change. Long-tail designs stagnate because we lose that opportunity to see the design in use across the entire system.

It’s an absolutely insidious problem. Many times when developer complain about messy code, it’s really because of a lack of consistency in the design. Adding a new feature to a system should require a reassessment or CSI investigation into the current architecture. Design should be boring, refactoring is where the fun comes in. But fun takes discipline, and so does an evolutionary design.

Law of two

A system should either have one consistent design, or two designs, the previous and the next design. As you look at a number of features with one design, you’ll gain an insight and want to try out a new design. But this is where things get tricky – you’ll actually need to try out the design to see if it works. And that design might take several tries to get right.

Instead of allowing more and more features to iterate over the previous design, which leads to long-tail design, you’ll instead only allow at most two designs in your system. Before doing any more innovation on your new design, move all the existing features to the new design. If you’re working in small steps, this shouldn’t be much of a problem. If the design is a little more involved, it might take a month or two of incremental work to move your features over.

What I’ve found is that my designs tend to get much better the more examples I have to work with. By only allowing at most two designs, I can maximize the potential of my next innovation. But with many competing designs, it dilutes my design, and I have a lot harder time seeing what my next step should be.

Evolutionary design and architecture is hard, but hard more from a discipline stance. You have to be disciplined not to “outrun your supply line” with your design. It can be a bit frustrating, you KNOW the next step to take, but in the end, it’s just not worth the risk of having the long-tail design.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in Architecture, Design. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • jdn

    Have you had any luck trying to do this on a team with more than X number of people, where X could be as low as 3?

    • jbogard

      Yeah, this strategy really came into focus for me when I was on a team of 12. We HAD to do this otherwise it was pretty chaotic. Another key here – someone has to be responsible for the design. You do have to have some leadership to decide what the next “right” way should be. Getting to that decision might be democratic, but ultimately, the team has to decide on a way forward.

      • jdn

        Were you able to do that in an Agile setting, and if so, how? I can imagine that having some *one* being responsible could be a point of contention.

        • jbogard

          Yeah, it was an agile setting. It was just basically a tech lead that was responsible. Usually what I see when there’s not one person responsible, no one is responsible and you have sort of an anarchic model. Instead of bad/old designs getting removed, they stuck around. Evolutionary means the strong survive, AND the weak die :)

  • Jeremy Rawls

    Great writeup!

    What is your approach to handling the time management aspect of the new design when it comes to project management and task estimation? Do you typically let them know up front that your estimates may seem a little high because of the new direction or do you camouflage the overhead under different tasks?

    • jbogard

      Good question. Sometimes we have a specific role set aside – tech lead – that is responsible for overall quality of the codebase. These sort of activities, actually finding those new designs/patterns/architectures and implementing them, would be their job.

      • with the tech lead does the overarching refactor approach (which i totally endorse by the way), how do you sell it to teams that say, one person shouldn’t be responsible for all that know how, it shouldn’t be their vision and work alone, we should card it out, and then pair programme or have every developer work on some part of the refactor so that the design/architecture and refactoring exercise and knowledge is owned by everyone. they fear that if the onus is on one person, and when (eventually) they walk out, then others don’t necessarily share the same insight to carry that work forward to a logical conclusion.

  • Agree.

    I think sometimes the source of the design divergence is honest “learning over time” in which the team simply wasn’t aware of some feature or technique. The trick with “going back and fixing everything” is this can be a tough sell to PMs who “just want this new feature done”. Personally, I look at software as a living organism that needs to be groomed and cared for. This involves “design maintenance”.

    Being a contractor or consultant helps with discipline because you always know that “another chance” is in the future. I’ve seen FTEs play around with different techniques in a code base out of sheer boredom or curiosity. They have no hope that there will ever be an app wide refactor.

    I think this is a compelling argument for smaller, focused apps. Older apps that are working, but reflect an outdated design methodology can be left alone without creating a competing design in the same solution. It also makes it easier to keep to the Law of Two.

    • An app wide refactor is a tough sell to anyone with an app that has grown since inception. Especially if it works and is hard to test. Do you really want to change the app and possible add defects? I know that is my fear. I also know in the long run it will be for the better. But if an old app hasn’t had much design maintenance the task is huge.

      • I had a wise dev manager who cautioned against such rewrites. The end result is the app “works exactly the same as it did before or is broken”. Not a great sales pitch. :-D

  • BatteryBackupUnit

    If i understand you correctly, you should have a maximum of two implemented designs for any given problem.
    However, your software may contain n problems, so you will end up having from n to 2n designs.
    n may be in the hundreds.
    So the questions for me becomes: what is the definition of a problem; how do i determine whether one is different from another?
    Given two _similar_ problems, would you rather implement one design with extension points or create two designs? …

    • jbogard

      Design/architectural problem, not business problem. Typically a given app will have a limited number of design/architectural concepts (probably less than 10). Those are the things I’m talking about, those cross-cutting concerns that fall under “architecture”.

      • BatteryBackupUnit

        I was not thinking of business problems.
        But to my understanding our application features substantially more than 10 architectural & design problems (i.E. i don’t share the same understanding of “problem”).
        So that is why i wonder what that unit, which should feature 1 to 2 design variants, exactly is.

        • jbogard

          Some examples:
          - Querying data (repositories, data access objects, Active Record, query objects)
          - Manipulating data (services, entities, commands)
          - JS Templates
          - JS MVC frameworks
          - Authentication
          - Authorization

          And on and on. I could probably elaborate what I meant by problem, designs and architectures.

  • Pingback: Windows Store App Developer Links – 2013-10-04 | Dan Rigby()

  • Ariel

    Are you advocating against “each service should be a self contained unit of technologies”, and require a monogamous tech selection?

    • jbogard

      No, this would be decisions made inside a service.

  • I support your idea!

  • Pingback: Evolutionary architecture boundaries | Jimmy Bogard's Blog()

  • Nack
  • Pingback: Combating the lava-layer anti-pattern with rolling refactoring | Jimmy Bogard's Blog()