First published February 2009.
Some folks say, “But I can’t make big changes in small, safe steps.” I always, always can. Sometimes I can’t think of exactly how right now. Then I take bigger steps & if I fail, I figure out how.
You don’t always have to take tiny steps, but they are always an option.
The system will eventually have to deal with an arbitrary number of some element but for the moment it only has to deal with one. What to do?
Introduction
To make the question concrete, suppose we are building a system that will have one transaction posted every year. The moment we absolutely need the system to handle more than one transaction is one year from now. What should the implementation and API be in the intervening year?
Applying the Leap strategy [ed: this is from my earlier version of Empirical Software Design, Responsive Design—Leap is going from one design to another in a single step] is tempting, because the end result is clear and leaping avoids the need for any refactoring:
Contract
List<Transaction> transactions;
Iterator<Transaction> getTransactions();
We know what the future holds, we program for it, what could go wrong?
Half and Half
Suppose that we ask our customer what reports should look like when they have more than one transaction in a contract and they can't tell us. They are too busy, they just don't know, or they want some experience with the system before deciding.
One strategy is to leap to the multi-transaction implementation and API, but let clients assume that there is a single transaction:
ContractReport
Transaction first= contract.getTransactions().next();
format(first);
In so doing we have left behind a trap. In a year, the second transaction will be posted and our report will no longer reflect the information we're holding. The contract report will run to completion but the answer will be quietly misleading.
The problem is that the client code has subtly different assumptions from the Contract API, assumptions that 1) aren't explicit, 2) can't be checked by a compiler, and 3) are easy to make in the heat of the moment. Get that report done, even if it only works with a single transaction!!!
We could put a bandaid on the client code to explicitly check our assumption that there is a single transaction:
ContractReport
Iterator<Transaction> all= contract.getTransactions();
Transaction first= all.next();
if (all.hasNext())
throw new Error("We can't yet report on contracts with more than one transaction");
format(first);
The guard clause prevents our code from silently producing incorrect answers but 1) we have to be wise and disciplined enough to add guards everywhere we make the single transaction assumption and 2) the guard doesn't give us any feedback until it triggers. It would be useful to get feedback earlier.
Succession
The Permaculture design principle of Succession [ed: will write more about the influence of Permaculture later] suggests an alternative solution. In agriculture, succession refers to a design that deliberately changes over time: plant fast growing manzanita to stabilize the soil while slower-growing (and more valuable) Douglas fir emerges. Even though the eventual goal is a forest of tall firs, planting something else first may be the more energy-efficient way to get there.
Applied to software design, succession refers to creating a design in stages. The first stage is not where you expect to end up, but it provides value. You have to pay the price to progress from stage to stage, but if jumping to the final stage is prohibitively expensive in time or money, or if you don't know enough to design the "final" stage, or if (as postulated above) the customers don't know enough to specify the system that would use the final stage, then succession provides a disciplined, reasonably efficient way forward.
Using succession in our example, we would design both the implementation and API in terms of a single transaction:
Contract
Transaction transaction;
Transaction getTransaction();
This design reflects our current reality: we have one transaction. The beauty of the succession-based design is harmony: the assumptions of the Contract and the assumptions of its clients always match. As far as everyone is concerned, there is only one transaction:
ContractReport
Transaction first= contract.getTransaction();
format(first);
And Then...
We pay the price for succession when the time comes to post the second transaction. Suitably in advance of this date, we need to upgrade Contract and all its clients (the customer needs to be prepared to tell us what multi-transaction reports look like), but we change both Contract and its clients as a unit. The assumptions of the API and the clients are always synchronized.
To reduce the risk and increase the efficiency of succession, I have templates in my head for common successions (I may have enough successions to begin a coherent and comprehensive catalog, but that's a topic for the future of Responsive Design). (When I'm thinking clearly, I even use my own templates.) The one-to-many succession works like this:
Add an API to make it look like there are multiple transactions:
Contract
Iterator<Transaction> getTransactions() {
return (new Transaction[] {transaction}).iterator();
}Add a parallel implementation to handle multiple transactions in parallel:
Contract
Transaction transaction;
List<Transaction> transactions;Everywhere the old transaction is set, set the list of transactions as well:
transaction= ...;
Now we are guaranteed that the list will always contain a single item, and that item will be identical to the contents of transaction, so we can...
transactions= Collections.singletonList(transaction);Replace every reference to transaction with the equivalent reference to the list:
Contract
Iterator<Transaction> getTransactions() {
return transactions.iterator();
}
All this "safe stepping" might seem like a lot of work. My experience is that if I practice I can go through the steps very quickly and with minimal chance of a mistake. Eventually many of the templates turn into automated refactorings, which is even faster and safer. When I rush and skip some steps (in the name of efficiency, natch) is when succession gets slow and unpredictable.
This bit me just last week. I was turning all uses of file names into uses of streams (it's generally a good thing to convert to streams as high in the call tree as is reasonable and write the rest of the code in terms of streams, but I had ignored this in the name of, you guessed it, efficiency). I kept plowing ahead with the next change I could see without regard to the template I use and I ended up having to throw away a half a day's work. When my priorities are straight, I prefer keeping to the templates rigidly so I can spend my time thinking about bigger issues.
Conclusion
Here are some connections between the topics in this paper and other work:
Succession in general is an example of the lean software development principle of pull. Starting with the customer's needs, we make just enough system to satisfy those needs. Not a haphazard hack satisfying those needs, a consistent, coherent design satisfying those needs, but one with no extra fat.
Succession, and "First One, Then Many" in particular, is also an example of the Responsive Design principle of simplification. We couldn't jump right to the design we could imagine, so we did something simpler that still provided value.
"First One, Then Many" is an example of the lean principle of completed work. By designing a multi-transaction API and using it in a single transaction way, I have left the system in a half done state. Breaking the work into self-consistent phases lets me complete half the work and then later the rest instead of half completing the whole job.
"First One, Then Many" comes with the cost of the transition between one and many, and this cost must come with a payoff for the succession to make sense. If I already know what the system should do with multiple elements, I generally just leap. If I am proceeding deliberately, either because I am not at the top of my game or I am coding with someone who isn't ready to leap with me, applying the succession makes sense. If the client doesn't know what to do with multiple elements, applying the succession makes sense. If the possibility of multiple elements is uncertain or far in the future, applying the succession may make sense. The cost of the succession needs to be weighed against the benefits of finishing sooner and the costs of inconsistent assumptions. In short, I find it better to stand on solid ground and take a confident step forward than try to stand with one foot in each of two boats.
Coda
I'm intrigued by the idea of a taxonomy of incremental design successions. I'll be watching my own design to see if "First One, Then Many" is the first of many. If you notice successions that repeat, please let me know.
In my humble opinion, this is the most valuable type of writing when it comes to software development. Let's hope there will be more of similarly insightful posts. Thank you.
What about a succession for “multitenancy”? Following with te example, the user initially wants to create the contracts for a single organization (“tenant”) but at some point will ask for contracts that are contained in a specific organization. Would you apply succession here?