
When I teach modular software design, I proffer four qualities of well-designed modules.
Well-designed modules…
- Have one reason to change
- Hide their internal workings
- Have easily swappable dependencies
- Have interfaces designed from their user’s point of view
That fourth one opens a smorgasbord of successful software design techniques – and not just module design – dating back to the beginnings of software engineering.
When considering the design of software modules – at any level of granularity from methods and functions all the way up entire systems – we’ve learned that an effective approach is to ask not “What does this module need to do?”, but “What does that user need to tell it to do?”
Use cases are one example of approaching design from the outside; from needs and goals of users, rather than the features and behaviours of systems. Test-Driven Development is another example where design begins with a user outcome (and that user could be another module, of course).
It’s not magic. When we start design by considering how modules and systems will be used (and we could look at modules as mini-systems in their own right – it’s turtles all the way down), we are usually led to designs that are useful.
In both use case-driven design, and TDD, the internal design of modules is driven directly by that external point of view. We begin by defining the desired user outcome (the “what”). We don’t begin by considering the internal design details (the “how”). The “how” is a consequence of the “what”, and design flows in that direction – from the outside in. (For example, working backwards from failing tests to implementation design.)
The reverse approach, where we design the pieces of the jigsaw and then try to put the pieces together at the end (“inside-out” design) has proven to have considerable drawbacks:
- The wrong implementation code
- Jigsaw pieces that don’t fit
- Test code that bakes in the internal design
When we define the shape of the jigsaw pieces first, from the user’s point of view, their implementations are guaranteed to fit.
This was the original intention of Mock Objects. They can serve as placeholders for internal components that don’t exist yet. So when we’re writing a test for checking out a shopping basket, and we know that something will need to send a shipping note to the warehouse, but we don’t want to think about how that works yet, so we can “mock” a warehouse interface as a dependency of the module we’re testing. That mock, and our expectations about how it should be used by the checkout module, define a contract from that external point of view.
When we get around to implementing the warehouse module, its interface is already explicitly defined from the user’s point of view.
Outside-In design could be more accurately described as “usage-driven design”. It is working backwards from the user’s needs.
If you’re serious about building your team’s capability to rapidly, reliably and sustainably evolve software to meet rapidly changing business needs, visit codemanship.co.uk for details of high-quality hands-on training mentoring for software developers.








