Stephen Freeman Rotating Header Image

An example of an unhedged software call option

At a client, we’ve been reworking some particularly hairy calculation code. For better or worse, the convention is that we call a FooFetcher to get hold of a Foo when we need one. Here’s an example that returns Transfers, which are payments to and from an account. In this case, we’re mostly getting hold of Transfers directly because can identify them1.

public interface TransferFetcher {
  Transfer      fetchFor(TransferId id);
  Transfer      fetchOffsetFor(Transfer transfer);
  Set fetchOutstandingFor(Client client, CustomerReference reference);
  Transfer      fetchFor(CustomerReference reference);

This looks like a reasonable design—all the methods are to do with retrieving Transfers—but it’s odd that only one of them returns a collection of Transfers. That’s a clue.

When we looked at the class, we discovered that the fetchOutstandingFor() method has a different implementation from the other methods and pulls in several dependencies that only it needs. In addition, unlike the other methods, it has only one caller (apart from its tests, of course). It doesn’t really fit in the Fetcher implementation which is now inconsistent.

It’s easy to imagine how this method got added. The programmers needed to get a feature written, and the code already had a dependency that was concerned with Transfers. It was quicker to add a method to the existing Fetcher, even if that meant making it much more complicated, than to introduce a new collaborator. They sold a Call Option—they cashed in the immediate benefit at the cost of weakening the model. The team would be ahead so long as no-one needed to change that code.

The option got called on us. As part of our reworking, we needed to change how Transfer objects were constructed so we could handle a new kind of transaction. The structure we planned meant changing another object, say Accounts, to depend on a TransferFetcher, but the current implementation of TransferFetcher depended on Accounts to implement fetchOutstandingFor(). We had a dependency loop. We should have taken a diversion and moved the behaviour of fetchOutstandingFor() into an appropriate object, but then we had our own delivery pressures. In the end, we found a workaround that allowed us to finish the task we were in the middle of, with a note to come back and fix the Fetcher.

The cost of recovery includes not just the effort of investigating and applying a solution (which would have been less when the code was introduced) but also the drag on motivation. It’s a huge gumption trap to be making steady progress towards a goal and then be knocked off course by an unnecessary design flaw. The research described in The Progress Principal suggests that small blockers like this have a disproportionate impact compared to their size. Time to break for a cup of tea.

I believe that software quality is a cumulative property. It’s the accumulation of many small good or bad design decisions that either make a codebase productive to work with or just too expensive to maintain.

…and, right on cue, Rangwald talks about The Tyranny of the Urgent.

1) The details of the domain have been changed to protect the innocent, so please don’t worry too much about the detail.

Thanks to @aparker42 for his comments

One Comment

  1. Akash Chopra says:

    Thanks for a very thought-provoking article.

    I agree with your point about software quality being cumulative. It reinforces my view that tech debt is an unhedged *put* option, because the downside is not unlimited. Consider a hypothetical clean codebase where we take on some tech debt; it is very unlikely that this single choice will cost us a complete rewrite in the future, hence it is a put option. Now, when we then take on a more tech debt, we change the market we are trading in, and this may cause the assets underlying the options to become correlated. This means that future market moves are more likely to cause both options to be exercised. The cumulative effect is to turn the whole codebase into an unhedged call option.

    What is the software property that causes the correlation? I’ve been half-heartedly been trying to model this process, and the best I can come up with is that it is dependencies between components, but I can’t find a way to measure the effect (an abstract dependency is better than a concrete one, but it is *still* a dependency that can cause correlated changes when the interface has to change).

    I don’t know if this has any practical significance, as few codebases are in the clean state necessary to make new tech debt an unhedged put option…but your article makes me want to think about it again!

Leave a Reply

Your email address will not be published. Required fields are marked *