Design patterns are no silver bullet


Design patterns are probably one of the most misused and misunderstood programming techniques out there, and I argue this is because they’re studied out of the context in which they were conceived, or worse, because is assumed that this context never changes. This ultimately causes some particular set of patterns to be considered as a somewhat general tool whose use is a good programming practice.

This is not a critic to the Gang Of Four book[1], which I believe is one of the best books addressing object oriented software programming. The authors of that book were very aware of the consequences of the wrong use of the techniques they were introducing, and so they stated in its first pages[2].

The context of a design pattern

A design pattern is a general reusable solution to a commonly occurring problem within a given context in software design[3]; a set of descriptions of communicating objects and classes that are customized to solve a general design problem in a particular context[1]. Much of the misuse of design patterns comes from ignoring the context part.

If you change the problem domain or the programming paradigm or the language applied, then the set of appropriate patterns also changes. Indeed, since patterns are reusable, proved solutions to common programming problems, if a problem goes away by changing one of those variables, then the pattern is no more. Similarly, as some problems disappear, new ones arise when the context is changed[4]. A detailed study of this argument is made by Peter Norvig in his slides Design patterns in dynamic languages[5]. There he reaches the conclusion that most of GoF’s patterns are simpler or invisible in Lisp or Dylan.

Some argue that while the implementation gets simpler in some languages, the patterns are still there, so they’re still useful in the sense that they provide a vocabulary for programmers to describe the design. This might be true in some cases, but not in most. Usually, the higher the level of the language features, the higher the design abstractions and the lesser the need to refer to patterns that usually are foreign to the domain of those abstractions.

For example, in a statically typed language such as Java one might need to introduce interfaces or class hierarchies to achieve polymorphism, whereas in a dynamically typed one everything is polymorphic, the feature is taken for granted by the programmers and since it’s built-in, it’s not needed anymore to describe the design. Another example, more related to what’s commonly accepted as a design pattern, are iterators. In C++, when not using STL, one might find the need to introduce iterators as a part of the program design. In Java, where every standard data structure comes with its own iterator, one might use them a lot, but they stop being a part of the design worth discussing. Lastly, in Python, where the iterator implementation is completely hidden by the control and data structures, one might never even see an iterator-related piece of code.

Choosing a programming language

This is part of a broader problem, but is directly related to the one I’m addressing here. Commonly in the software industry (but unfortunately in the academic community too) the people in charge of the design and architectural decisions on a software project don’t consider the language as a parameter of the solution. Sometimes they just pick the one they’re more comfortable with, but often this decision is made by management people based on non technical factors, such as programmer availability. Paul Graham puts it very clearly in one of his essays[6]:

The pointy-haired boss miraculously combines two qualities that are common by themselves, but rarely seen together: (a) he knows nothing whatsoever about technology, and (b) he has very strong opinions about it.

Suppose, for example, you need to write a piece of software. The pointy-haired boss has no idea how this software has to work, and can’t tell one programming language from another, and yet he knows what language you should write it in. Exactly. He thinks you should write it in Java.

Why does he think this? Let’s take a look inside the brain of the pointy-haired boss. What he’s thinking is something like this. Java is a standard. I know it must be, because I read about it in the press all the time. Since it is a standard, I won’t get in trouble for using it. And that also means there will always be lots of Java programmers, so if the programmers working for me now quit, as programmers working for me mysteriously always do, I can easily replace them.

Well, this doesn’t sound that unreasonable. But it’s all based on one unspoken assumption, and that assumption turns out to be false. The pointy-haired boss believes that all programming languages are pretty much equivalent.

I won’t get into how viable it is for the employee to disobey the manager’s bad decisions or how valid the manager’s point of view is. But as a programmer I too think that you can’t let the suits make technical decisions for you.

When professors are closer to the the industry than to science, this too has an effect in their approach on teaching. They will tend to select as teaching languages those more widely used and (at least where I study) impose that choice thus creating a vicious cycle where the student never develops the need to compare and choose the language better suited for each job.

Be it by choice or by obligation, the  lack of the habit of choosing the programming language for each project causes the context to be considered as invariable. If the context is assumed static then the patterns for that context are the only patterns ever applied (ever taught for that matter), and so they end up looking universal or more relevant than others. The fact that most GoF patterns are never needed when programming in languages such as Python is seen by the naive as a shortcoming of the language rather than the opposite.

Patterns smell funny

Some programmers (specially those recently introduced to the technique) think that the more patterns it has, the better a design is. Or maybe a less extreme position is that given two ways of solving a problem, if one uses a known pattern, it’s better. This, I suppose, is based on the mentioned idea that patterns introduce a common vocabulary that lets them speak at a higher level. Also on the widespread belief that they are some sort advanced programming technique.

On the contrary, I think that a design that introduces much patterns smells funny, using the parlance of Refactoring (and Frank Zappa). Fowler and Beck argue that certain structures in code suggest the possibility of refactoring, and call those bad smells[7]; they don’t necessarily imply that the code is wrong, but it probably is.

In one hand, a pattern introduces foreign abstractions into the problem domain, increasing the design’s complexity. On the other, often making the design more flexible in one specific direction, it does so by sacrificing flexibility in the others. This tends to be a recipe for disaster when programmers believe they can predict how a system may evolve in the long run. Graham holds a similar position[6]:

When I see patterns in my programs, I consider it a sign of trouble. The shape of a program should reflect only the problem it needs to solve. Any other regularity in the code is a sign, to me at least, that I’m using abstractions that aren’t powerful enough– often that I’m generating by hand the expansions of some macro that I need to write.

He is off course, speaking from the standpoint of a Lisp programmer. Indeed, if we accept the idea that patterns  come to overcome the lack of  a certain language feature, most patterns are presumably needed because of the absence of macros. Still, there are many contexts in which using Lisp might not be the best technical decision, so we should apply a more general principle.

First we should abandon the idea that patterns improve the design of software, and use them only when they are really needed, this is, when there’s no simple solution to a problem with the current abstractions of our domain. Second, we should be aware of the proliferation of patterns, since it might be a sign that the language (or architecture) we are using is not the right tool for the job. And be prepared to leave the tool when necessary.

[1] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Design Patterns: Elements of Reusable Object-Oriented software.
[2] Design patterns should not be applied indiscriminately. Often they achieve flexibility and variability by introducing additional levels of indirection, and that can complicate a design and/or cost you some performance. A design pattern should only be applied when the flexibility it affords is actually needed.
[3] Software design pattern.
[4] Despite the book’s size, the design patterns in it capture only a fraction of what an expert might know. It doesn’t have any patterns dealing with concurrency or distributed programming or real-time programming. It doesn’t have any application domain-specific patterns. It doesn’t tell you how to build user interfaces, how to write device drivers, or how to use an object-oriented database. Each of these areas has its own patterns, and it would be worthwhile for someone to catalog those too.
The choice of programming language is important because it influences one’s point of view. Our patterns assume Smalltalk/C++-level language features, and that choice determines what can and cannot be implemented easily. If we assumed procedural languages, we might have included design patterns called “Inheritance,” “Encapsulation,” and “Polymorphism.” Similarly, some of our patterns are supported directly by the less common object-oriented languages. CLOS has multi-methods, for example, which lessen the need for a pattern such as Visitor. In fact, there are enough differences between Smalltalk and C++ to mean that some patterns can be expressed more easily in one language than the other.
[5] Peter Norvig, Design Patterns in Dynamic Languages.
[6] Paul Graham, Revenge of the Nerds.
[7] Martin Fowler, Refactoring: Improving the Design of Existing Code.