The Tourniquet Pattern Explained

| Comments

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.

blog comments powered by Disqus