A Quiet Defense of Patterns
brooker.co.za
brooker.co.za
This thinking is flawed in two ways. The glaring flaw is in the restrictive definition of patterns. The more subtle flaw is in not recognizing that they have patterns of their own at similar scales to the ones that were rejected. Abstraction is extremely powerful, but operating at higher levels of abstraction doesn't appear to imply higher productivity or reduced needs for patterns as a medium for sharing context."
The reason we don't get a boost in productivity with higher abstraction is because (a) we have to build such abstractions out of a great number of smaller pieces, so that 'greater abstraction' means 'more work', and (b) it is just very difficult to map our mental models into reusable abstractions. Not very many people can do it well.
Yes, every language has 'patterns', but we call those linguistic structure, syntax, or grammar. "Design patterns" refers to something else, (or if it doesn't, it is just a confusing domain-specific renaming of the above.) The problem with design patterns is that they are ostensibly solved problems, and thus people shouldn't be implementing them except pedagogically.
Take arrays for instance. There is no "Array Design Pattern", and we don't ask people to implement arrays every time they want to employ them. We ask them to use existing array structure. Yes, you should understand how they work. You probably should implement them once or twice and know how to recognize them. But you do not say "Oh, just use the array pattern here. Start by rolling your own array, since our language doesn't have array syntax."
We build arrays into the language so that they are easy to recognize and the pattern becomes a primitive tool. If you have to keep rolling your own, you'll never get past that. It will never become primitive, and you will stagnate. That is what those who object to design patterns object to: by thinking of them as elements of design -- the work of day-to-day programmers -- we won't provide linguistic abstractions that allow them to become primitive tools. We'll be stuck re-rolling our own arrays because people think that this is what programming is all about. Tedium.
For one, many people really do regularly relearn the benefits of arrays. To the point that maybe it should get more discussion.
For two, not everything can be a linguistic structure/syntax/grammar. Often they are pretty good highlevel descriptions of what is intended. Implementation left to the reader.
Which is ultimately the problem. Two implementations of the visitor pattern may have made different trade offs for what they are doing. Same for many other patterns. Singleton is often itself used as a tradeoff in terms of flexibility.
Now, I fully agree that when used without thinking/acknowledging the tradeoffs, they lead to issues. They are not unique here. Consider actors. Arguably the vogue pattern for the popular crowd nowdays. Can you honestly tell me the vanilla actor is a solid win?
Says who? Why should I believe this?
There is also the parallel to spoken languages. Where there are pretty good patterns of speach for making different points. Why haven't all of those been converted to simply an atomic linguistic structure.
>There is also the parallel to spoken languages. Where there are pretty good patterns of speach for making different points.
And programming is largely a domain specific task today, so you could have pretty good patterns for it.
But anyway, that parallel expresses a better point: language is extensible. If you have something in another spoken language to express, you can certainly do it in any other language. You just can't maximize terseness everywhere, which is not what we're using to measure how good a language is. The measure for this is terseness in proportion to usage, which doesn't conflict that rule. Especially if you can control terseness dynamically.
Language is less extensible and more just plain mutable. Consider the phrase "firing rate" when applied to an archer. Something tells me this mutability is not a selling point around here.
The higher level of linguistic structure, syntax or grammar is the 'idiom', and idioms are a small step below the 'pattern'. So I think there are differences there.
Arrays are a datastructure, not a design pattern, but allocating an array, populating it, using it to recall its contents and finally destroying it could be called a pattern (or an idiom, if you prefer).
Whether you choose to implement something as an array or as a hash table is a "design pattern": a pattern invoked by a designer to solve a problem. You don't have to use a singleton pattern or a visitor pattern. You choose to as a consideration of your design. But you shouldn't have to roll your own code to make that design work, because we have a name for it and we know what you're talking about. It should be in a library or in the language. (Which, in my opinion, is a farcical distinction: languages and libraries should always be considered together at all levels, including in language design.)
Does this mean that a design pattern is not an absolute, but relative to a language? Or does it mean that there is no clear distinction between syntax and pattern? I don't think that being one excludes being the other.
We build arrays into the language so that they are easy to recognize and the pattern becomes a primitive tool. If you have to keep rolling your own, you'll never get past that. It will never become primitive, and you will stagnate. That is what those who object to design patterns object to: by thinking of them as elements of design -- the work of day-to-day programmers -- we won't provide linguistic abstractions that allow them to become primitive tools.
Rather than fearing that naming a concept will cause stagnation, I think the opposite: the failure to have a "shorthand" for a commonly used feature means it will continually be reinvented. Once named, the concept can be pushed down to syntax while remaining a design pattern.
The surprise (to me) is that the gulf between being part of the language and being an easy to add library is so great. Why is there no standard hash table that gets used in C? Why is there such a difference between core and non-core? I don't know what it is, but don't think it's for lack of syntax.
This is exactly the problem with design patterns. Once you recognize a design pattern, you should abstract it away and just use it from a library. Sometimes that is very hard to do. (E.g., Model-View-Controller.) But just because it is hard doesn't mean it is impossible. That kind of activity is a prime measure of success in computer science.
Of course, we have the internet, but we still don't write languages that expect to pull needed tools from the internet in a smart way. You have to manually look for a library and include it yourself. When you write some code, it isn't being compared against a database of online code with your IDE telling you "Hey, someone else wrote something like this and it runs way faster and uses less text, check it out maybe?"
> The confusion is, that both concepts are implemented by the same language construct: Classes
This made me think. In C# this statement is somewhat untrue, since you have structs that have value semantics (value equality). But why are there no other concepts within the language that help you from reinventing the pattern wheel?
For example, I had seen the singleton pattern quite frequently in a codebase I worked on, with the label leaking into the class name. But why is it that you have to write:
public class DatabaseConnectionSingleton { ... }
and then deal with all the static stuff (i.e. x.Instance()) rather than public singleton DatabaseConnection { ... }
Like this, you could easliy use standard stuff: new DatabaseConnection()
and the runtime would simply return the same instance. This would keep people from reinventing the mechanism. Like IoC / DI built into your language...[1] http://www.amazon.co.uk/Growing-Object-Oriented-Software-Gui...
Well, except that the whole reason for documentation of design pattern implementations is that, in the languages for which they are documented, they generally do not admit of a reusable library implementation because of limitations in the abstractions available in the language. That's why people (slightly inaccurately) say that certain languages (Lisps are frequently cited here) don't need design patterns -- the kinds of things documented as design patterns with implementation recipes for Java/C++-like languages can often be implemented in library code in lisps (to a certain extent this is also true of many dynamic OO languages, like Ruby, and sometimes in static functional languages, like Haskell), so the kind of recipe-style presentation and fill-in-the-blanks implementations seen in Java/C++ patterns just aren't applicable in some other languages.
(Sure, you can address design patterns by building each one into the language individually, the way you discuss with arrays, and that will shift which patterns the language needs at the expense of a bigger base language. You get more bang for the buck building languages which are expressive enough to allow you to reusably implement a broad array of "patterns", so you instead of implementation recipes you just need libraries with API documentation.)
Language design should be about saving the end programmer from having to do extra work, not minimizing your core language and deferring the savings for later. Of course, there is some push-and-pull here, because we want to make languages small so that they are comprehensible. So what we want are the smallest possible languages that can express a broad array of powerful abstractions with minimal effort.
This is why I hate the "no language is the best" line of thought. (Which is similar to "ever language uses design patterns.") There is no theoretical reason why there can't be a best language. All we have is a history of languages that suck to look at. You can't infer that all possible languages suck at something from our short history of terribleness. It is 2015. What kind of languages will people be using in 4015? How about 14015? Are they going to be saying "It doesn't matter what language you use?"
However, this is only half of the story.
There are also "real" patterns (e.g. Facade) which describe by what criteria you should structure your code in what circumstances. These can't be directly translated into a language feature, library or framework, as they still require human judgement on the details. One might disregard these patterns as "soft" or "too imprecise", but I think these are the real patterns. These helped me more than any of these mechanic patterns.
Patterns if done well give us a language to solve problems in sensible ways as teams.
Instead, we worry about how patterns are named and argue over minutiae. We let things like "exceptions should only be used for exceptional behavior" distract us from effectively communicating with each other.
Our industry sees things like FP, OOP, Design Patterns, etc. as an exercise in taxonomy when building software is an exercise in communication. It's like we are writing a novel, but we are more worried about the names and families of characters than the actual plot itself. That doesn't make for a good book and it doesn't make for good software.
- Deciding on a solution before having explored the problem. "I have a pattern for that."
- They act as shared context only on the surface; all the details and subtleties of the pattern as an implemented artifact are rarely shared, and communication suffers.
In practice, they tend to make the easy stuff easier, but the hard stuff harder.
But I know what I saw: fresh off my love affair with the Lakos C++ book, I though GOF was the next cornerstone of OOP and a must read for everyone. Over the next few years, design debates and proposals where design patterns featured more and more prominently, started turning into bizarre minutiae and half-explored problem discussions. Again, simple and noncritical pieces of software seemed easier to describe and even develop, but the hard stuff became harder to manipulate, overabstracted, took longer to be truly finished (although it got to MVP state faster), and usually much less performant.
That's where I see their value: they are extremely good blueprints. Like every tool, they are incredibly powerful if used correctly. They are most often misused, especially in the case of verbatim use.
> Two people with a common set of patterns find it easier to communicate [...] than those without one.
> [...] writing down our shared context lowers the barrier to entry.
In my experience this is often not the case. Patterns come along with labels and the labels will be put everywhere in a codebase. Assumptions get made and replace discussions, leading to a diluted codebase. For example Repositories will become synonyms for "thing that does database access", and will then be reduced to stupid GetByID wrappers [1]. Even worse, some patterns make entry really hard.
I used to work with C# and ASP.NET MVC. There, a view is the thing that contains markup, the thing controlling the input is the controller. Then I started using Flask and was utterly confused, since there the functions with the @app.route decorators, which are run on the server and then return markup rendered via jinja2 templates, are called view functions. In Django this is understanding is the same.
I was really baffeled. I could not reapply my understanding of the patterns across these domains. This is just one explicit example that I wrote about in my blog [2], but I really made the general experience that patterns replace or inhibit discussions, rather than improving communication and understanding.
[1] http://philcalcado.com/2010/12/23/how-to-write-a-repository/
- Developers try to find a problem to use a pattern they know
This error is mostly commited by people who learn the first time of patterns and are giddy to use them. I wonder if this was less likely to happen, had the GOF included a warning about this in their book.
- Developers keep using the wrong terminology and this causes patterns to become squishy
Just stick to the GOF terminology and your good to go.
In my eyes, the patterns discussion is just an indicator for a greater "problem": Anyone can become a software developer. I won't elaborate on the positive aspects of this. But it's clear that areas with a higher entrance bar, like law or health, have a greater professionalism and, among other advantages, a standardized terminology.
I don’t think that patterns devalue the craft.
Also they helped me get a better understanding of what I do. For example in Python you can create a Singleton by simply accessing an instance which is instantiated directly in its module. The pattern is built-in and very easy to use, but I only started making heavy use of it when I learnt the Singleton pattern. It was then that I realized how much value this simple language feature provides.
> “In a functional language…you end up programming in concepts that eliminate design patterns all together.”
I saw lots of ad-hoc patterns which have to be understood to understand the code.
The most vicious arguments I have read were about GoF not keeping the spirit of CA. One example is GoF being a sort of disconnected catalogue, while CAs patterns are supposed to generate the world. I don't see these referenced in this article.