Sunday, February 7, 2010

A Taxonomy of Software Developers

After spending years of my previous life at Microsoft as a Dev, Tech Lead, and Dev Lead, I've worked with a broad range of software developers from the US, China, India, and all over the world. I've also been involved in interviewing well over a hundred candidates, and many hiring (and some firing) decisions. From this I've come up a taxonomy describing the characteristics of the various software developers I've encountered, how to spot them, and what to do with them.

Typical Developers

The hallmark of a Typical Developer is a relatively narrow approach to problem solving. When fixing a bug, they concentrate on their immediate task with little regard to the larger project. When they declare the bug fixed, what that means is that the exact repro steps in the bug will no longer repro the issue. However, frequently in fixing the issue described in the bug, they have missed a larger root cause, or have broken something else in the system. This is illustrated in Fig. 1:

In most cases the code a Typical Developer writes is a very small net improvement for the overall project when viewed from a release management perspective. Sometimes the traction is zero if the issue that they created is just as severe as the issue they fixed. Sometimes the traction is slightly positive if the issue they created or the case they missed is easier to fix than the original issue.

When viewed from an engineering management perspective, however, the picture is very different. This is due to the nature of the approach Typical Developers take when actually writing code. A typical bug has the form "under condition X, the project behaves as Y, when it should behave as Z." The Typical Developer is very likely to fix the problem in this way:

// adding parameter isX to handle a special case
void doBehavior(boolean isX) {
  // usually we want to do Y, but in this special case we should do Z.
  if (isX == true) {
  } else {

The Typical Developer simply figures out how to directly apply logic to the code that determines behavior, then make the code behave differently based on that. This is reasonable, but if it's the only way the developer can think of to change behavior, after a while working in the same code it begins to look something like this:

void doBehavior(boolean alternate, String data, File output, Enum enum) {
  if (enum == STATE_A) {
    doBehaviorA(data, alternate);
  } else if (enum == STATE_B && !(alternate || data == null)) {
  } else {
    switch(enum) {
      case STATE_B:
      case STATE_D:
        doBehaviorA(data, !alternate);
        // FALLTHROUGH!
      case STATE_C:
        if (alternate) {
          doBehavior(!alternate, null, null, enum);
        // We should never get here!

When I see code after months of a Typical Developer working on it, this is my reaction:

The Typical Developer will never take a step back and think "Hmm, we're getting a lot of these kinds of issues. Maybe the structure of our code is wrong, and we should refactor it to accommodate all the known requirements and make it easier to changes."

Now the project is in trouble. The team may be able to release the current version (often there is no alternative) after exhaustive manual testing, but the team can never be confident that they fully tested all the scenarios. The first priority after releasing will be to remove all the code written by the Typical Developer and write it from scratch.

Another characteristic of Typical Developers is insufficient testing. Often the code they write will be difficult or impossible to unit test. If unit testing is a requirement, they'll write tests which are just as bad as their code. In other words the tests will be unreliable, require big changes to get passing when a small code change is made, and not test anything important. Furthermore the same narrow approach to development shows through in manual testing. The Typical Developer will follow the steps in the bug when testing their fix, and never stop to think "what other behavior could be impacted by my change?"

Typical Developers are quite willing to chalk up their constant regressions and low quality to factors like "I'm working in legacy code" or "I'm not familiar with this area" or "the tools aren't good enough." Though all of those things may be true, that is the nature of software development, and Typical Developers don't understand how to change their environment for the better.

The root cause behind these failings is most often that the Typical Developer is simply not cut out for real software development. Because the software industry is so deeply in need of talent, no matter how marginal, Typical Developers will always find work. Hiring managers are too willing to fill manpower gaps in order to ship on time. (In fairness, Microsoft managers are pretty good about avoiding this pitfall. However, there are times when it is considered OK to "take a bet" on a marginal candidate.)

A special type of Typical Developer is the brilliant person who simply doesn't care enough. They're in software development because it pays well and they can skate by with putting in 40hrs a week. These Typical Developers are especially annoying because they'll employ their brilliance only when justifying their lazy workarounds, and not on actual design and implementation.

What should managers do with Typical Developers? In most cases manage them out as quickly as they can. Though a Typical Developer may be of use in the final push of releasing a project, in the long run having them working on a project is a net negative. Even if Typical Developers came for free, I wouldn't hire them. It is exceedingly rare for a Typical Developer to become a Good Developer, though in rare circumstances I've seen it happen under the guidance of Great Managers.

Good Developers

Good Developers fix bugs and deliver features on time, tested, and adaptable to future requirements. This is illustrated in Fig. 2:

Once a Good Developer delivers a bugfix or feature, typically that's the last you hear of it. A Good Developer will not fall into the traps that a Typical Developer does. When they see a pattern emerging they identify it and take steps to solve the issue once and for all. They are not afraid of refactoring. They'll come into your office and say "Hey, it's not sustainable to do all these one-off fixes for this class of issue. I'm going to need a week to re-do the whole thing so we never have to worry about it again." And you say great, please do it!

Good Developers will encounter the same environmental issues Typical Developers do, eg, legacy code, or weak tools. Good Developers will not let this stand. They'll realize that if a tool is not good enough to do a job, then they have to improve the tool or build a new tool. Once they've done that, then they'll get back to work on the original problem.

Good Developers are Good Testers. Their code is written to be testable, and because they are able to take a larger view, they have a good idea of the impact of their changes and how they should be tested. Pride is also a factor here. Good Developers would be embarrassed and shamed if they delivered something that wasn't stable.

From a release management perspective, Good Developers are well liked, though their perceived throughput may not be high since they are spending time making the system as a whole better and not just fixing a bug as fast as they possibly can. Good managers recognize and nurture this. Bad managers push them to put in the quick fix and deal with the engineering consequences in-between releases. Good Developers will protest against this but often acquiesce. A Good Developer in the hands of a Good Manager can turn into a Great Developer.

Managers should work hard to keep Good Developers since they're so hard to find and hire. That does not mean forcing them to remain on the team, as doing so risks turning a Good Developer into the "brilliant" variety of Typical Developer described above. Reward Good Developers well and give them interesting things to work on.

Great Developers

Exceedingly rare, the hallmark of the Great Developer is the ability to solve problems you didn't know you had. This is illustrated in Fig. 3:

When tasked with work, a Great Developer will take a holistic view of their task and the project they're working on along with full cognizance of the priorities upper management has for this release and the next. A Great Developer will understand the impact of a feature while it's still in the spec-writing phase and point out factors the designers, PMs, and managers hadn't thought of.

When designing and implementing a feature, a Great Developer will take the time to design in solutions to problems that Good Developers and Typical Developers have run into, even though they're not obviously connected. A solution from a Great Developer will often change how a number of components work and interact, solving a whole swath of problems at a stroke.

Similar to Good Developers, a Great Developer will never let lack of tools support or unfamiliar code deter them. But they'll also re-engineer the tools and legacy environment to such a degree that they create something valuable not only to themselves but to many others as well.

Unlike Good Developers, a Great Developer can almost never be coerced into compromising long-term quality for expediency. They'll either tell you flat out "no, we need more time, period" or they'll grumble and come in on the weekend to implement the real fix themselves.

Sometimes mistaken for a Great Developer is the Good Developer in Disguise. These Good Developers have recognized the impact on others that a Great Developer has, and seek to emulate that by engaging almost exclusively in side projects related to tools improvement and "developer efficiency" initiatives. The Good Developer in Disguise has no actual time to do their own work, but fools management into believing that they're Great Developers. Truly Great Developers improve their environment as a mere side effect of them doing their own job the way they think it ought to be done.

It goes without saying that Great Developers should be even more jealously guarded than Good Developers, with the same caveat about not turning them into prisoners. The flip side is that Great Developers should not be allowed to go completely off on their own into the wilderness. No doubt they will build something amazing, but it runs the risk of being something amazing that you don't need. Better to give broad, high-level goals and let them do their thing.

Final Note

Although I named Typical Developers "typical," I mean that they're typical in terms of the overall industry. Although there were enough Typical Developers at Microsoft, most fell into the Good Developer category.


  1. The distinction you draw between "good developers" and "great developers" is mostly just one of degree, not of kind. Re-designing an area to staunch the flow of one-off bugs in an area is a basic form of "solving problems you didn't know you had" -- i.e., even though you know an area is a bug farm, it's hard to predict the exact nature of the next bug.

    You could say that the distinction between a great developer and a good one is that a great developer has better gap analysis than anyone else on his or her team. But that's a relative scale. If you took the best developer from every team and put them together, do they become merely good because they're now all equally capable of pointing out issues in advance? It's easy to imagine that a great developer has elevated their thought process to a whole different level to see these things, but more often it's just recollection of their experiences in a similar project, or perhaps simply looking at things from a slightly different perspective.

    I think there are a couple of qualitative differences between good and great developers. First, great developers value concision -- both in their code as well as written communication. They're always looking for the minimal way to unambiguously express their intentions without being inscrutably terse. Good and great developers can both solve the same problem or write the same design doc, but the great developer will do it half as many words (or LOC).

    A second difference is that a great developer knows their limitations -- they know what they don't know. A bright developer run amok is a description of a good developer, but not a great one. Good developers are suceptible to the second-system effect, where success (real or perceived) in one area emboldens them to exceed their abilities in the next. Great developers have been bitten by that bug before and are actually mildly resistant to building general solutions until they see three or more specific problems that can be solved by it.

    A third difference is that great developers like to describe their personal best qualities in a general way in blog posts and blog comments, sometimes devising hierarchies in such a way that leave themselves at the very top. :-) As Paul Graham once said (about programming languages, but could be adapted to programming abilities as well):

    "As long as our hypothetical Blub programmer is looking down the power continuum, he knows he's looking down. Languages less powerful than Blub are obviously less powerful, because they're missing some feature he's used to. But when our hypothetical Blub programmer looks in the other direction, up the power continuum, he doesn't realize he's looking up. What he sees are merely weird languages."

  2. Yep - great points, Chris. Overall I focused pretty heavily on raw problem solving ability and not so much on a lot of the other qualities that really differentiate a Great Developer from a Good one. Mostly this post was a vehicle for the diagrams I included, which greatly amuse me. :)

    I especially like the point about being concise without being overly terse. Very tough balance to achieve, but as Justice Potter Stewart said (in reference to perhaps the opposite of coding), “I know it when I see it.”