Learn when design patterns do more harm than good.
In this article, I want to share some common pitfalls associated with design patterns and many best practices. Intermediate developers are the most fallible group (as opposed to novice or advanced developers) because they are at the stage of focusing on abstractions the most.
Design pattern is a general, reusable solution to a commonly occuring problem within a given context in software design.
By “design pattern” here I mean both design patterns, architectural patterns, and many best practices that describe better ways to structure code. While they are generally a “good thing,” there are pitfalls you should be aware of.
When you first learn about design patterns, the natural reaction is to start putting them everywhere.
I mean literally everywhere. You create a class, and you instantly create an interface—you don’t care that there can only be a single sensible implementation. You learn about singletons, and now it’s your go-to way to ensure there exists only a single instance of a class—you don’t think about the downsides and other alternatives.
Essentially, you pour patterns into the code to solve no problem. What you get is a code littered with “best” practices, patterns, and dozen layers of abstraction with no added benefit.
Little knowledge is often more dangerous than not knowing at all. Research shows that people who do extremely bad in a test express confidence that matches that of the top performers:
When looking only at the confidence of people getting 100 percent versus zero percent right, it was often impossible to tell who was in which group.
The reason is that they “know” something that is actually not true—they hold a misconception.
I often see the same happening with patterns—people learn a little about a pattern (e.g., a coworker’s two-sentence description) and now they think they grasped it.
We all know that “programming to an interface instead of an implementation” is a good thing: instead of relying on a class directly, you spin off an interface and use it—this gives you more flexibility as now you don’t rely on a specific implementation and can change it or even replace completely.
Except that… this is not what “programming to an interface” is about! Many people do not understand that “programming to an interface” has little to do with
interface construct—yet they hold a firm belief they know the concept and “follow best practices.”
It is natural to think that misconceptions are something that only happens to others and does not apply to you. However, it affects everyone.
I know you ignored the research link, so here is a friendly reminder.
In his We Are All Confident Idiots David Dunning shows what ignorance really is, why it feels like expertise, and how prone we humans are to fall into this trap—it applies to all of us, there is no exception.
It’s quite a long read (30–40 minutes), but it worth it.
As a motivating example, I recently dispelled a misconception that I’d been holding for six years! I was so sure about a concept, and I taught it to quite a few people. Guilty as charged.
Most patterns solve problems by adding layers of abstraction. Each layer adds more complexity to your code. And while it might seem neglectable at first, you should not ignore it! If not taken care of, the complexity adds up over time and cripples the project. On the other side of the spectrum, architectural patterns may impose significant complexity and incur additional challenges (that you may not even be aware of at first).
As an example, microservices architecture (while having tons of benefits) adds a lot of complexity to the system. Many teams underestimate complexity.
Best practices evolve; new ones appear and replace old ones. New design patterns emerge; old patterns get new variations and adaptations for different languages. More powerful languages include features that make design patterns redundant.
What was a best practice yesterday, today is an anti-pattern. (You know, because we have a bester practice now.)
If you learned design patterns from the Gang of Four’s book only, you’re missing, like, 25 years of evolution.
Here are some steps you should consider:
Learn KISS and YAGNI principles. Learn refactoring. These will help you write simpler code.
Write simplest possible code first, refactor to patterns later.
Learn more about patterns, study different variations, learn about drawbacks; for architectural patterns, study imposed challenges.
(If you followed the article, you might have noticed that most of the issues come from too little, wrong, or outdated knowledge.)
Every time you use a pattern, apply critical thinking. Evaluate whether it does more good than harm, especially for architectural patterns.
If you’re a novice developer and don’t know patterns yet, you probably didn’t get the article. Don’t worry. Just keep this article in mind—when you progress to intermediate level and start learning patterns and other cool principles like DRY, SRP, etc., come back and re-read.
If you’re an advanced developer and successfully escaped these traps—teach others! You may also investigate the root causes of pitfalls and apply your knowledge in other areas or even your daily life—these pitfalls are not unique to design patterns.