May 2011 Archives

Leftover Soap and Team Size

| Comments

soap.jpg

I’ve noticed that when our family’s bar of Zest is reduced to a certain size, it becomes ineffective. There’s still soap in there, but it just doesn’t seem to produce any quality lather. I suspect the same is true of small software development teams.

A team of one or two programmers is small and nimble, but susceptible to external forces. If a team member gets sick, goes on vacation, or takes another job, your team is reduced by 50% or 100% for that time. That’s a lot. The remaining team member is almost certainly forced into some extremely inefficient task-switching, further reducing throughput.

I think it’s OK to dedicate a very small number of people to a task, as long as they’re part of a larger team with members who can help out in a pinch. Plus the team will naturally cross-train as they ask each other for help or advice.

I am by no means a user interface expert, but I like to have some clue as to what works and what doesn’t, and so I find the occasional nugget of practical design interesting.

Here’s a post that explains how your site’s call to action should go where it does the most good, in the terminal area (it’s science, yo):

A but­ton in the ter­mi­nal area is a com­pelling call to action because it’s placed at the end of the user’s view­ing pat­tern. When it’s at the end of their view­ing pat­tern, users don’t have to look around to find your call to action but­ton. Putting your call to action but­ton in any other area could get you clicks, but not as effec­tively as putting it in the ter­mi­nal area.

Why Users Click Right Call to Actions More Than Left Ones [UX Movement]

Bandage

Today Mike Pirnat and I were discussing a particularly nasty piece of legacy code we often have to interface with. I described one technique he had used as the Tourniquet Pattern.

This Tourniquet is when you declare a section of your system to be too complex to directly work with or improve safely, and so you seal it off behind cleaner interfaces.

When You Need A Tourniquet

A favorite interview question of mine is “Do you always refactor and fix legacy code when you come across it?”. This is by no means a clear issue. A person can get lost in refactoring, turning a one-day activity into three weeks of hacking, with no guaranteed results.

Some legacy systems, though, are so arcane, so toxic, that the safest thing is to get in, make changes, and get the eff out before anyone gets hurt.

This is the kind of system that may require the Tourniquet.

When You Don’t Need A Tourniquet

Just as in real life, if you need a tourniquet, things are dire. For the sake of the larger system, you’re cutting off a section of code.

You don’t need the Tourniquet if you’re writing new modules or if you have any practical way to refactor or rebuild the problem systems. Use it sparingly, and always as part of a larger improvement plan.

An Example: Man-Purse Purchasing System

Our man-purse store has a purchasing system we inherrited from the previous owner. It’s gnarly, and was written with total disregard for human life or maintainability. It’s interface looks like this:


class MPPS():

    def __init__(self, a, x, qw="default", roching="", extra=True):
         ...

    def buy_it_now(self, itema, itemb, opt_itemc, coupon="FX"):
        if self.isnt_debugging:
            self._do_spanish_purchase()
        else:
            if self._has_blue():
              ...
        # and so on for ~ 1000 lines or so

This kind of setup makes it almost impossible to cleanly add new features, especially when that module gets to be a few hundred or thousands of lines long (please, stop sighing in disgust, you know you’ve seen something like this).

To apply a Tourniquet, we might pull out the calls we know about and make them easier to call:


class PurchaseHandler(object):

    def __init__(self, legacy_purchaser, new_purchaser):
        self.legacy_purchaser = legacy_purchaser
        self.new_purchaser = new_purchaser

    def buy_hat(hat_sku):
        """a new feature! we sell hats now

        """
        self.new_purchaser.buy(hat_sku)

    def buy_spanish_purse(purse_sku):
        """an old feature, but with a cleaner interface.

        """ 
        self.legacy_purchaser.isnt_debugging = True
        self.legacy_purchaser.buy_it_now(purse_sku)

    def buy_other(self, a, x, qw="default", roching="", extra=True):
        """ support the old gross interface until we're done porting it all

        """
        return self.legacy_purchaser.buy_it_now(a, x, qw, roching, extra)

In this case we’ve picked out a case where the new feature is added, and we leave the rest of it alone, making better interfaces where possible, or just acting like the old module everywhere else.

Over time, the newer, cleaner methods should replace the older ones. The Tourniquet has given us a place to start to gradually and safely make things better.

Hey, You’re Just Hiding BAD CODE

Life is full of hard choices. If we had unlimited time, we could bite the bullet and re-engineer systems from the ground up as they mature. Sadly, we almost never have that luxury. This pattern admits that the code is bad, but at least provides a clean cut from which to build newer and better stuff.