My thoughts on software design

· by Steve · Read in about 8 min · (1533 Words)

I don’t claim to be an expert in software design, but I think I’ve learned a few things over the years. As I’ve wrestled with a few problems recently, I began to think about the approach that I generally take to the process of design. It’s influenced by books I’ve read, multiple projects that I’ve been involved in, and some of my gut feelings. You may agree or disagree, but I felt it would be good, even just for myself, to write down the tenets that I’ve come to regard as core to my design approach.

1. Step away from the computer

Seriously. I’ve used a lot of modelling tools over the years, from early entity relationship diagram tools, through the birth of UML (I rejoiced when the ‘3 Amigos’ finally decided to unify their notation), through to integrated round-trip engineering tools these days, and whilst they are very clever, and are great for recording and communicating a detailed design, but they are manifestly damaging when it comes to early stage conceptual design. Some people leap right into those tools as soon as they have a new problem to solve, and that scares me. These tools encourage you to think about detail and layout way too early, and lengthen the process of iterative high-level design purely because everything is so formalised. When you’ve put a lot of effort into documenting an embryonic design in detail in one of these tools, you don’t want to lose that effort, so you are less likely to trash it if it starts to look inappropriate. Plus it takes you longer to find that out because you spend too long driving the tool the way it wants to be driven (all those features cry out to be used). You might think that you can be strong enough not to be tempted by that, but even subconsciusly there’s going to be a little voice on your shoulder saying ‘you just spent 3 hours on that, surely you don’t want to toss it all away?’.

Your best conceptual design friend is a stack of paper (preferably recycled or, in my case, the backs of unwanted printouts), a HB pencil and a stout eraser. Oh, and a large paper bin, you’ll need that. There is nothing, absolutely nothing, faster for conceptual design than this, and I seriously doubt there ever will be, until of coruse they invent direct neural downloads. You’ll blast through ideas fast, you can link them up however you like, spatially arrange them, and easily discard them without feeling you’ve wasted too much time. Everything you do is exploration and requires no more effort than it merits.

So, don’t believe the hype from people trying to sell you design tools. Design is about what humans do best - deciding how best to tackle problems given a potentially abstract set of environmental conditions. The only tools you need are those that help you explore that, and the simpler the better. Get complex once you’ve decided on the approach by all means, but do that any earlier and it’s just slowing you down, and make doing the other things in this list harder.

2. Look at any problem from multiple directions

Don’t just think of a feature from the perspective of how you want to use it now; study trends and decide how it might be used in the future, or what else you might use it for given a moderate conceptual twist. You can’t predict everything, but anticipating change and alternate usages will give you a better chance of picking a holistic approach, where your current intended usage is just a facet of a greater, more generalised implementation.

3. Look for patterns

Almost every software engineering problem has been encountered before, in some guise or another. That means other people have already put a lot of thought into approaches (which may or may not be appropriate for your needs). Don’t come up with your own solution before exploring these, at the very least they may influence your eventual solution if you’re in the rare situation where none already exists.

This is one situation where being at the computer may help, if you don’t have a good grasp of a good number of design patterns already, and don’t have a good library on the subject. However, there is no tool that I’m aware of that can suggest a pattern for you, since drawing parallels with other data sets and pattern matching is something humans do very well, but machines very much less so. Also, blind internet searching can lead you astray, ince using patterns without appropriate context can be worse than not using them at all - every pattern comes with a set of parameters and suitability conditions that you really have to have a good grasp of. Your best bet is to read all the time - books like the Gang of Four, online articles etc, and re-read them regularly to keep them in your head so that you’re more likely to spot an appropriate pattern when you see it.

4. Always document your reasons for a design choice

There’s no such thing as a perfect design, every design is a series of compromises based on the goals and constraints in each problem domain. Being explicit about your reasons for choosing one approach over another allows you to evaluate alternative approaches long after your mind has exited ‘the zone’ on this particular issue.

5. Ruthlessly define scope

Knowing where your boundaries are is one of the most important things in design. If a designer can’t tell you where their system ‘ends’, or imply that it does ‘everything’, then they should be very worried. If you can’t define what’s inside, and just as importantly what’s outside your scope, how on earth are you ever going know when you’re finished, or whether a feature belongs in your system or whether it belongs somewhere else? You can’t.

You absolutely must be able to draw a clean, unambiguous box around any system you design. That’s because every system in the world has to interact with other systems, and that box represents the interface points - anything going in or out of that system boundary needs an interface, and that’s one of the most important aspects of the design; many systems will live or die by how well they can be integrated with other systems. If you can’t clearly define what’s ‘inside’ and what’s ‘outside’ that box, you can’t possibly define the line that describes it, which means you can’t define consistent interfaces. What you end up with is a bleeding, feature-creeping system with ad-hoc integration points without any holistic design behind it. If you’ve ever had to integrate with a system that feels like that, you have my sympathies.

6. Plan for change, but realise your limits

We all know that nothing stands still. That means we should try to design things flexibly so that they accept change readily. Using approaches like data-driven design, good abstraction of discrete concepts, dynamically customisable code (e.g. plugins) all help achieve that goal and are nearly always a good thing. But, you have to draw the line somewhere - there will be times when you have to make assumptions. That’s fine, so long as those assumptions are documented, and preferably they sit on your ‘scope boundary’ - ie in your original scope definition you said you would allow for ‘x’, and no more (see previous point). Sometimes, you just have to stop somewhere, stick a flag in, and move on to something else.

Why not try to plan for all change? Well, you can’t, and your efforts in this area will yield diminishing returns. Change within a strongly cenceived scope is a good idea, but beyond that scope you should deal with change through refactoring, should you need to. Hopefully your scope is defined clearly enough that you can draw that line in the sand unambiguously (again, see previous point).

7. Don’t be afraid to change your mind

Being wrong is fine, so long as you’re willing to realise and embrace it. Be aware that design is an optimisation process, and is often iterative - you’re trying to find the most appropriate design for the problem at hand, within the time constraints you have available. This means that even your best design may only be a local minimum; there may be other solutions which fit your needs better that are just over the next ‘bump’, you just might not be able to see them yet.

The previous points I’ve made are all in support of this point. Never assume you’ve got the best design yet, because you probably haven’t. Keeping agile in your design process (point 1), being as open-minded as possible (point 2), looking for accepted wisdom (point 3), putting a stake in the ground when you’ve made a particular choice (point 4), and knowing your scope for change (points 5 and 6) will all help you reflect when a potentially better option becomes available.

Maybe that’s given you a little insight into the way I go about design from a high level. Maybe not, or maybe you don’t care 😉 But there it is anyway.