The One Timeless Benefit of Design Patterns

Originally published on DZone
There’s not much buzz about design patterns these days. They appear to have joined the hall of fame of accepted wisdom, alongside the Silver Bullet, SOLID and DRY. Lately, I had the opportunity to share some thoughts on the importance of good old design patterns with Koen Aerts, CTO of Team Rockstars IT. Here’s the gist of that talk in a more digestible format.

Before I start, let me set some boundaries, as people can get doctrinaire about definitions. I refer to the good old bridge, builder, decorator, and factory patterns. Architectural patterns like MVC do not fall into the same category, much fewer paradigms like serverless and microservices (aka SOA the next generation).

Then again, the latter do constitute a legitimate grey area. They’re clearly about design, patterns, and best practices as well. They offer standard solutions to common challenges. But what makes them different is the scale at which they operate. Classic design patterns are recipes for manageable bits of code, solutions that often fit in a single screen. They explain how to stack the bricks for your new house, whereas microservices show you how to lay out the entire neighborhood.

Medieval design patterns: the scaffolding inside the spire of Salisbury Cathedral, England

Design Patterns: Elements of Reusable Object-Oriented Software, aka The Gang of Four book narrowly preceded Java’s quest for world domination. It’s quite a feat for a programming book written in 1994 to be still in print, but it has not weathered the test of time equally well as other classics like The Mythical Man Month, published nineteen years earlier. It makes sense. Brooks talked about the human condition without a single line of code. The GoF offered hands-on coding advice using contemporary languages, which necessarily dates it. The scenarios themselves have not aged, but the solutions have.

Although these design patterns are in principle language agnostic, many are unique to the challenges of OO: creating objects (builder/decorator), using hierarchies (bridge), dealing with polymorphism (abstract factory) and isolation (adapter). Old school Object Orientation of the imperative and stateful flavor is falling out of favor. Renewed interest in functional programming (renewed because FP has been around for decades) sparks a search for patterns dealing with new challenges.

Ready for Retirement?

Modern JVM languages offer exciting new features that can make some patterns look ready for retirement. Scala’s stateful traits are an elegant alternative to the decorator pattern. Named constructor arguments in combination with default values make the builder pattern redundant. The object keyword takes care of the Singleton pattern. The latter two are also possible in Kotlin — and maybe in Java 42, who knows?

It is ungenerous and incorrect to dismiss classic patterns as kludges for missing language features. You can’t replace all these allegedly verbose workarounds with an elegant syntactic feature. The Gang of Four (I assume they’re not called that in the PRC) nailed an essential set of problems. We have seen a trickle of new design patterns over the years, but they were few, and hardly strokes of genius.

No Drops of Magic Potion

Neither the Agile framework nor design patterns are the recipes for Getafix’s magic potion. At best they are opinionated and precise solutions to a specific problem. That’s good, because their limited utility is also their strength. General-purpose tools are often not as powerful. A socket wrench has a superior grip compared to an adjustable wrench, but you need to own a whole set of them. Take general purpose best practices like the five values of Scrum. Don’t these fall into the category of easier-said-than-done? Yes, let’s all get more courageous, focused, open, and committed next sprint!

The danger of narrow-purpose recipes is using them inappropriately: too much and in the wrong places. You can overdose on vitamins and exercise like you can overindulge your love for patterns and end up with FizzBuzzEnterpriseEdition. Should I write a subclass or refactor the existing class, thus breaking the open/closed principle? It depends. Doesn’t a richer type hierarchy just overcomplicate things? Hint: probably.

Things that do the same should look the same

Experts who sell a methodology of best practices don’t like to hear that it depends. They believe in maximum usefulness and applicability. But I know of no best practice that is a universal killer problem solver. That would be a silver bullet indeed.

So, even if we must suppress the urge to create adapters for everything, design patterns can at least teach us a shared idiom, and that’s their enduring, language-agnostic benefit. Few coding habits are more important than naming things consistently and understandably. Things that behave the same should go by the same name. That way we come to expect that a leaf node has no children and that the return type of an abstract factory should not end with ‘Impl’. Break the rules and you violate others’ sacred right not to think more than needed. If you respect the power of language and the limits of our poor brains, you will write methods like the first and think twice before writing the second:

Percentage getCurrentInterestRate();

There is a wealth of useful hints here. The method is clearly a getter, so you don’t expect side effects. Using a custom value type (Percentage) eliminates the confusion that a floating point value would raise (does 0.1 mean 0.1 percent or 10 percent?). ‘Current’ indicates that the method is likely not idempotent, i.e. the return value depends on some mutable state, either internal or external.

void processResults(Map<String, List<String>> data);

What exactly do processing results involve? Your guess is as good as mine. We can’t even be sure that the state of the data parameter is not manipulated by the method.

Words create expectations. Builders, factories, adapters have reserved meanings and we should be able to rely on them. Creating your own fancy idioms (Creator, Wizard), adds a needless learning curve but is still not as confusing as misusing established idioms, such as getters with side effects.

Boring Is Good

If all this sounds like an appeal to write unsurprising, predictable code, you’re right. Reading code puts an enormous strain on our limited brains. Read Felienne Hermans’ excellent book The Programmer’s Brain for more insights. The art of programming is not to be clever or wayward at the level of statements. Innovative software must have predictable, helpful code written to be understood by other people.

If that doesn’t excite you enough intellectually, you can always take up experimental poetry. See how that pays the bills.