A good, thought-provoking post from Actively Lazy today:

I think we massively underestimate the cost of coordination and communication when building software.

That's true. Full stop. This, the communication price, is exactly why folks are still quoting The Mythical Man-Month. Communication makes [hu]man-months mythical.

But what we do with this discovery is what's really important. It's easy to find one solution and think it's the only solution, as I believe Actively Lazy has done. Let's explore two.

Pair Programming

Here's Mr. Green's (Actively Lazy's author's) take home (emphasis mine):

From the outside its very easy to miss: a quick 5 minute conversation laden with jargon. And yet… this is where the magic happens: this is where the design comes from. But if that 5 minute conversation interrupted someone’s work, the next 45 minutes could be lost while they try and reload into memory what they were working on. Pile up a few of these interruptions in your day, and no wonder it feels like you’re swimming upstream. [anybody else hear Joel Spolsky now? -mfn]

Clearly, what we should have been doing but weren’t was pairing. That way there would only have been one narrative thread. One sequence of ideas being applied at a time. ... There is no cost of interruption when you’re both already there, immersed in the problem. ...

So in fact: two people can be more productive than one. Two people pairing is definitely better than one person working on their own. ... It’s all based on a false assumption: that two people working on different machines are twice as productive as one person working alone. Once you realise that this assumption is fundamentally flawed, the “cost” of pairing evaporates. Instead pairing removes the cost of coordination between two developers: no interruptions, no divergent ideas, no merge conflicts.

My quoting makes the argument a little worse; you really should read the entire post in context. But you get the picture. He's arguing that pair programming pays the "coordination and communication" costs as-you-go so that you don't have to pay the piper in spades, to mix some metaphors, later.

Pair programming trades function for quality

When you work in pairs, you create less working code that is higher quality. This is nearly a truism. If you're somewhere code review's benefits aren't appreciated, run. I'm not saying you should require or even use a lot of code review -- my jury's still deliberating on its best use -- but reviewed code is of higher quality than code that isn't.

  • Best practices
    • Test Driven Development
    • Linted code
    • Reviewed code
  • Efficiency/Scalability
  • Normalization/smart reuse
  • Standardization
  • Proper error handling

At the same time, that "high quality code" may not necessarily do the job better-qua-[characteristic X]. Just to get your started, here are a couple of common Characteristic Xs (feels like we're making Powerpuff Girls)[1]:

  • Be delivered on time (this is the biggest argument contra-quality
  • Be significantly faster (when n=1, does the fact some schmoe used a bubble-sort matter?)

This is why iteration is such a catch-phrase for coding (though also see its abuse, here).[2] Get 'er done, then, if you have time to notice (or are forced to notice by poor performance), but only if you have time to notice, make it better.

Quality vs. Functionality

So is pair programming the answer to removing communication problems? Sort of. Depends on why you think you need to communicate! One key here is that Green only has two developers:

Me and one other developer have been working on a new, greenfield project.

If you only have two devs, of course pair programming kills the problem of deferred communication. But if you have 40 devs, you've now got 20 pairs that could need to sync back up.

Twenty pairs is still much, much better than 40 individuals. Twenty pairs should also be much, much better than 20 individuals. The same way letting someone use your alpha version for 15 minutes uncovers bugs simply because they have a different mental model than yours, letting another strong developer take a look at your code will unearth some low hanging fruit (man, I'm mixing metaphors today. Uncover some barely buried tubers?) quickly. And even that single filter means your code will require less change to integrate with the rest of the team.

The key take-home is that pair programming makes for higher quality software, not more of it. There's more than a single metric for evaluating software's worth.

The other side of code is functionality, often reduced to the reasonably useful metric, lines of code (loc).

Aside: If used correctly, lines of code can give you a general idea of how productive your devs have been. Though insert the usual caveats:

  • loc is really only useful comparing a specific dev's output to her or his own
  • a stark increase in loc for a dev often means they're pumping out code too fast, and feel stressed
  • brilliant solutions don't always produce lots of code; usually it's the reverse

And there's a performance bar that tells us if code is good enough. That is, pair programmed code may be higher quality, but it may also not be significantly faster or use significantly less memory, etc. The biggest problem in evaluating how much quality you need is that often you don't know how significant a bad LINQ statement might be until you run it at scale.

I think the suggestion is that you have to bias your development culture so that it always strives for high quality with respect to best practices. You want good scalability? Don't overuse ORMs. Don't push logic into the client. Write good SQL. Take time to plan your schema. But I don't know that pair programming, sacrificing half your speed for code quality, is necessary if you have this high-quality culture instilled with a team of well-hired developers.

You get the point. The bottom line: Your code is higher quality if it's reviewed for standard practices, errors, and efficiency/scalability first, even if that review time reduces how much functionality you can build. But remember that functionality is why you're in business: you shouldn't sacrifice function for idealistic quality. In other words, you need to discover...

The nasty truth: There is "good enough"

Imperfect code can still provide acceptable functionality. News flash: There is no perfect code. [Most] Any solution is beyond a certain level of complexity is subjective.

That there are many subjective Right Answers to complex problems, and it's precisely because you can only reasonably select one of them that you ask for advice before tackling them. That is, subjectivity is why you design code before coding. Even when you're going to tackle a major problem alone, you "pre-[re]view" with someone else (don't you?!!) your selection from all the different ways you [both] can think of to solve it.

The reason you consult someone else first is because you know the problem you're working on needs an extra helping of quality mixed in with the quantity. You slow down, sacrificing not just your but your teammate's output, to ensure your output's quality. Your know your solution -- or at least its design -- needs a second set of eyes. In a sense, though you may not have touched a keyboard [much], you are already pair programming.

The conflation of these two code metrics -- functionality & quality -- is what provokes Mr. Green to say...

Unwinding a few days, we probably would have got more done with just one person working. That way there would only be one narrative thread through the code, one sequence of refactoring steps at a time.

Wait, what – say that again: we would have got more done last week if only one person had been working on it.

Sure, one person working alone could bring the code back to a single narrative, but it'd be one person's narrative. You've right back into communication debt. Hopefully it's not as bad as it was before you'd code reviewed with your coworker, but the new communication debt again exists. This single person's progress is another unreviewed revision.

And perhaps that's good enough.

A different lesson: Seperation of concerns

The real key is that you can't have X people, where X is determined by your company's management, working on the same code at the same time without factoring in the communication costs for getting X folk on the same page.

If you don't have time to factor in communication and code review, the new code will be worse in all of those standard ways -- best practices, efficiency, normalization/smart reuse, standardization, error handling. Worse, each person will find themselves coding around or being hamstrung by changes to their narrative made by X-1 other coders. Codebases shared by X devs without coordination is worse than having X coders doing their own thing. You do not get X-times (or "Xx" -- I regret my use of "X" at this point) the work, even if each dev is working on their own seemingly independent story!

To get "Xx" functionality (and that's what the company wants, man. When you feel pressure to go faster, and are considering adding more devs to finish sooner, it's because you want functionality), you have to separate concerns perfectly.

  • You must create interfaces ahead of time, at least one per "communication unit".
  • You must hit these interfaces.
    • Rather, if you change them, you have to change your schedule drastically.
    • You just changed two team's narratives!
  • You must create test cases for these interfaces with realistic values.
  • The interfaces must be natural breaks; they must be truly separate concerns.
    • You can't have any interaction between them.
  • They require an emphasis on defensive code.
    • There can be no unintended interactions between the opposite sides of the interface.

The interface is a contract. And objective contracts (strangely possible in code; it's magic) are the most efficient means of communication for software projects.

Now you're creating functionality as quickly as is possible. And if done correctly, you can get a lot closer to the myth. But there's a huge, obvious downside...

Communication Debt added to your Technical

And remember, if X is large and your stories are done by one person per story, your "communication debt" will be just as huge. You will have technical debt, and you will have a huge learning curve for the new dev if the person working on the code changes. And if the meatware half of the cyborg leaves your company before the knowledge transfer/code review takes place, you're in trouble.

The good news? This debt will be firewalled by the interface. That's as far as the bad can go, if you have smart TDD.

If X is large and your stories are done by two people, the debt will be more than halved, but your functional output could be more than halved as well!

Quality code is hard, (c) 1842.

[1] If you want an example of folks arguing against higher quality code for short-term preference Characteristic X, see the constant complaining about JSLint rules wherever JSLint is used. Let me summarize two-thirds of the answers on the JSLint tag on StackOverflow: "If you don't like rule X, you should use JSHint and turn it off." JSLint is a form of code review that emphasizes a set of "best practices". If you're left to your own devices, you might not always follow them. Following them might not make for better code in every situation, but you will have good, standardized code with fewer errors. JSLinted code is higher quality code, though conforming takes more time than not.

As I've said before, "If I had to inherit legacy code sight unseen, and could only require that it be JSLinted or JSHinted (pick one), I'd pick JSLinted code every time," and that's because it's higher quality.

[2] Hey, look! I've talked about Agile (here regarding documentation) as early as 2004! Three cheers for ourselves!

Labels: , , ,