Read more of this story at Slashdot.
Read more of this story at Slashdot.
How many times in your career as a software developer have you been handed a “requirement” that turned out to be difficult and costly to fulfil?
I’ve been there many times, and I imagine you have, too.
The hack I discovered a few years into my career is that one person’s “what” is almost always another person’s “how”. Software requirements very rarely describe an actual need – a user goal, for example. They’re usually a decision that someone has taken about how to address a need.
When we make decisions – the user will select their house number from a drop-down list – we constrain future possible decisions, reducing the options we have available to solve a problem we might not even be aware exists.
Visualising design decisions as a tree, we can more clearly see the relationships between decisions – how one decision is a consequence of an earlier decision.
We can traverse the tree in three directions – we can go down by asking “How?” How are we going to populate the list of house numbers? I call this direction of traversal “execution”.
We can go left or right by exploring other options – does it have to be a drop-down list? Could it be they stick a pin in a street map instead? Or just enter it in a text box? I call this direction of traversal – where we stay at the same level in the tree and evaluate other paths – “exploration”.
And we can climb back up the tree to the root decision that led us to these options by asking “Why?” Why does it need to be selected from a drop-down list? So we can be more confident it’s a real address. Okay, so why do we need to be confident it’s a real address? Because we’ll be delivering to that address.
And there, folks, is Waldo! That’s the real need.
Traversing the decision tree in this direction helps us understand not just what we’re creating, but why we’re creating it. It’s the context that’s so often missing in the design process.
Why is “Why?” such a powerful question?
Remember that in a decision tree, each decision constrains possible future decisions. Each time we commit to a specific branch, we lose significant optionality – we get fewer execution choices to work with and a smaller potential solution space. This reduces our chances of solving a problem with limited time and resources.
When we take a step back to an earlier root decision, optionality opens up again. I’ve lost count of the times when a deadline was looming and the path we’d chosen just wasn’t going to get us there in time, and I asked the question “What is it we’re trying to achieve, again?” 99% of the time, it turned out the requirement wasn’t the real requirement – it was a decision. And 99% of those times, it turned out there were quicker and cheaper options – often not involving writing any code at all.
In fact, this happened so often in the early part of my contracting career that I eventually adopted a policy of questioning every requirement in a specification, and ultimately rejecting any specification the team hadn’t been directly involved in agreeing.
This is why I refer to software requirements specifications these days as “handcuffs”.
And we must not forget that, no matter how clear the rationale for a design decision is, it is at best an educated guess at what will solve the root problem. Only reality can answer whether it actually does.
This brings me back to the topic of feedback latency which builds on the same decision tree model. How far down a branch do we go before we get meaningful feedback on the root decision?
It turns out that the practice of creating and releasing software in thin slices, getting continuous feedback from the real world about which decisions were the good decisions early and often, is also highly compatible with preserving optionality for longer.
Big Design Up-Front could be characterised as having very high feedback latency – we spend a lot more time climbing the wrong trees. It can also be characterised as closing off alternative branches too early – long before we’ve got anything real to go on – and collapsing optionality in the absence of any evidence that we’re on the right execution path.
In Extreme Programming, we delay committing to decisions – especially decisions that will be hard to change – as long as we reasonably can. This helps us to keep things as simple as we can (Y.A.G.N.I.), and it also helps to keep our options open for longer.
Another way we can keep our options open is by hiding the “how” behind some kind of abstraction. An interface, for example, describe what a module should do, but we can defer and/or substitute the choice about how it does it – the execution paths below it in the decision tree – more easily.
The outside-in test-driven design process described in Steve Freeman and Nat Pryce’s highly-recommended book Growing Object-Oriented Software Guided By Tests uses mock objects to define abstractions (contracts) before committing to any implementation.
This keeps options open for longer, and also has the benefit of allowing us to solve one problem at a time. It also provides a flex point in the architecture that keeps our options open in the future, should needs change. Checking out the basket charges the customer’s credit card. How? We’ll get to that later, but we know what the basket needs to tell the payment processor to do.
This was the original intent of mock objects – a tool for defining contracts for object interactions without committing to implementations. Mocks are a design tool, not a testing tool.
One final thought: software developers tend to be very “how”-oriented (we like solving problems), but – like the myth that pigs can’t look up – it’s also a myth that we can’t look back up a decision tree and understand the real needs that are driving the design. It’s very much in ours and our customers’ interests to do so.
The arrow of time doesn’t only run in one direction.
