A few years ago, after listening to a presentation I gave on software release managament, a member of the audience expressed with what appeared to be a mixture of amazement and bitterness that “this should be taught at university as part of every computer science course”. At the time, I took that as a compliment, and didn’t really think about it much beyond that.
But working with spriteCloud puts us in contact with a large number of software engineering teams, which as a rule are comprised of extremely bright, motivated and capable engineers – and yet, despite their capabilities, some issues crop up over and over again.
As QA consultants, we can always advise our customers on the larger processes by which they can keep a project on track, but we rarely if ever can make suggestions to improve code quality before it’s even written.
Specifically, there are choices to be made in code that have are very rarely addressed in the plethora of HOWTOs and guides one can find in bookstores or on the world wide web. Let’s call that code design, and start discussing some of these topics.
First, let’s discuss why design might be a good term to use here.
Too often, due to job descriptions such as “web designer” or because we’ve been looking at “designer furniture”, etc., we associate the term design with aesthetics or largely visual values. Are we talking about aesthetically pleasing code, such as particularly elegant hacks? No, not really. Go learn lisp for that.
Wikipedia defines the meaning of design like this:
(noun) a specification of an object, manifested by an agent, intended to accomplish goals, in a particular environment, using a set of primitive components, satisfying a set of requirements, subject to constraints;
Wow, that’s a mouthful. It’s a good definition, but for our purposes, you need to understand only a number of things here:
- Aesthetics aren’t an intrinsic part of the deal.
- Design always intends to accomplish some goal. Being asthetically pleasing may be one of them, but being useful for its intended purpose definitely is. If the sole purpose of an object is to be asthetically pleasing, we tend to call it art (apologies to all artists out there for this rather short definition of art). Design, on the other hand, fulfils other purposes as well – as in the case of furniture, you might want to sit comfortably on a chair.
- There’s a lot of terms in that definition that we’re used to from writing code, words such as specification, environment, compoment, requirement.
Design vs. Engineering Practices
That sounds very much like what we tend to call engineering practices, no? Well, yes and no. Some engineering practices are worth covering in this series, but by and large such practices act as guidelines. That is, they effectively say in situation X, do Y. A good example of an engineering practice is “always use version control systems for storing your code”.
That’s a very useful shorthand, of course, but gives no indication as to why you should follow this particular practice. Now I’m being a little crass here as of course the web abounds with good explanations of why one should follow one engineering practice and not another.
With this series, we have a slightly different goal in mind, though. We don’t want to provide recipes for how to get things right in this series.
Design vs. Design Patterns
Perhaps your first thought when you read code design above was that this would be about design patterns. That’s not correct either, but of course this is the elephant in the room and must be addressed. Design patterns effectively say in situations similar to your X, people had good results with doing some variant of Y.
Opponents of design patterns criticize the entire industry that’s sprung up around it for dumbing down developers. Design patterns, so they argue, give cookie-cutter solutions to classes of problems, and programmers who know design patterns will slavishly apply them.
I’ve seen such developers. They treat design patterns as tools in a toolbox, when they should treat them as education. But that doesn’t make education bad.
The opponents of design patterns strive for an ideal world in which every engineer at any point in time comes up, from scratch, with an optimal solution to their problem. To me, that sounds like re-inventing the wheel – albeit perhaps in a billion tiny variations.
Nevertheless, while we want to provide education, it’s not supposed to be in the form of tools in a toolbox that you can just apply.
Design vs. Style
Quite often in our jobs as engineers we come across style guides, that may or may not be enforced in an organization. Style guides are typically comprised of two disjunct sets of guidelines, namely:
- Those that improve readability of code.
- Those that reduce the occurrence of common mistakes.
The latter category is closely related to engineering practices; they, too, try to provide what are essentially recipes to avoid disasters.
We’re not trying to provide style guides here, either. Code readability is a matter of preference in many cases, and we’re already trying to avoid describing engineering practices.
Design vs. Architecture
Another term that might come to mind is architecture, which is largely concerned with the high level view of how components and users interact, and some high level choices made based on that information.
The problem with simply writing about software architecture, though, is that it pretty much precludes us from going down to the code level. Architecture also doesn’t quite ask the question we want to concern ourselves with; it asks how do I, at an abstract level, solve problem X best?
Now that we’ve excluded a few possible definitions, what is it we mean by code design?
We’re not precisely trying to provide answers here. The one thing that opponents of design patterns get right is that cookie-cutter solutions don’t always fit. Worse, though, are situations where you don’t find a cookie-cutter solution to your problem. What then?
Largely what we want to achieve is to ask the kinds of questions that get you thinking. Or we want to point out shortcomings in a particular idiom, without necessarily suggesting an alternative. The thing we want to say, in a nutshell, is if you come across X, pay attention to Y, because that’s where things get interesting.
Well-written code is the result of paying attention to the interesting parts. It might be that the programmer who came up with it tried a bunch of times until they got it right. Or it might be that the programmer has the experience, from working with vaguely similar software, to make conscious design decisions that lead to better solutions.
If we can enable you to make similarly good conscious design decisions without the benefit of decades of software engineering experience, then the series has been worth it.
Interfaces and Contracts
Throughout the series, we will use the terms interface and contract a fair amount, and it’s worth discussing briefly what is meant by them.
However, the term has a better defined meaning, namely a shared boundary across which two separate components of a computer system exchange information. The exchange can be between software, computer hardware, peripheral devices, humans and combinations of these. – that is, an ABI or API is a specific case of a generic interface.
The crucial point is that interfaces are all about setting expectations. The provider of an interface makes promises and states requirements about what an implementation of the interface does and expects. Conversely the user of an interface should be able to reasonably assume that if they follow all the requirements, they get the results they desire.
In that sense, the provider and consumer of an interface enter a kind of contract. The provider essentially says “if you play by the rules I set, I will also play by the rules and deliver on my promises”. The concept of contracts has become popular enough to spawn the design by contract approach to software design, which — loosely stated — postulates that one should first spend time figuring out the interface of a piece of code before making it functional.
Design by Contract is a registered trademark, but that hasn’t stopped some programming languages from acquiring support for enforcing contracts in code.
The entire approach is quite similar to the ideas of test-driven development and behaviour-driven development. Both approaches force the existence of the interface before the implmentation is started, with test cases forming contracts. In that sense, they’re engineering practices that somewhat haphazardly push developers in the direction of contract oriented programming, and good interface design.
Adopt either or both practice, by all means. But be aware that neither practice automatically leads to good design choices. They just encourage you to ask design questions up front.