You know the old adage: where there's smoke, there's fire? When you see the first signs of a fire, whether it's the smoke, the smell of wood, or your detector sounding the alarm, you'll probably want to find the source of the problem and fix it (or run for your life!). In software development, it's the same thing! The smell of smoke is called code smells.
When we talk about code smells, we're talking about a vast subject on which we could spend years publishing relevant information. Today, I'd like to take you gently into this universe by defining what a code smell is, what it's used for, how to recognize it and give you a few examples. Let's get started!
First of all, it's a term coined by Kent Beck while helping Martin Fowler write his book on refactoring. He used the term to describe an element that may indicate unnecessary complexity, making the code evolution more complex.
Code smells are often an indicator, a symptom of a problem rather than the problem itself. They can take different forms, such as readability, duplication or difficulty in writing tests, for example. They can occur in both production and test code.
It's something that tells the developer: this doesn't smell right, I need to analyze it further and try to find the source of the problem so I can fix it.
Code smells can help us identify common design problems that are quick to spot. In this way, we can target problems that occur everywhere and at every level of abstraction. What's more, they allow us to identify the refactorings, or combinations of refactorings, that work best to eliminate these "smells".
Indeed, once we understand what a smell is, we're in a position to make links with refactoring techniques to correct them. Identifying smells opens the door to a series of proven solution paths, often patterns that old hands have built up over the years, making problem-solving much simpler.
As developers, it's in our interest to follow this nomenclature and master it, in order to benefit from the intelligence of the community, which is served to us on a silver platter. There's no need to reinvent the wheel every day when you've got a track record of solutions proven in hundreds, if not thousands, of cases!
To get started, there's no magic formula: you need to read catalogs and practice recognizing them. You can also hold workshops with your team: look at code together and share what you don't like, but above all why you don't like it. Does it have a name? Scratch away. Once you've put a name to the problem, you'll have some ideas for solutions!
In our field, many elements can look like smells because of the complexity of the work done. But real code smells tend to be accidentally complex. As a result, you need to analyze each smell carefully before concluding that there is indeed a problem.
Many code smells catalogs are available online from developer communities. These catalogs provide a rich and colorful vocabulary with which programmers can quickly communicate about design issues. Some categories also exist to show the impact that code smells can have in the long term.
For example, in her presentation Get a Whiff of This, renowned developer Sandi Metz lists some classic code smells, classified in different categories:
If we take bloaters, for example, these are code smells that pollute code that could be much simpler.
In codebases that don't use Domain-Driven Design (DDD), where we try to approximate the language of the domain, we commonly find the smell of "primitive obsession", which is part of object-oriented abuse. This can be observed when we directly manipulate the primitives offered by the language, and must always validate the content of these primitives before processing. All this adds complexity, very quickly, and creates a lot of duplication! The various smells and refactoring catalogs will suggest encapsulating validations with primitives in the construction of a new domain concept. You'll be able to put a word on this new concept and assume, when using this new concept, that it contains validated data.
Many developers are reluctant to refactor, for a number of reasons: they prefer to rewrite (a big red flag!), they're afraid of breaking everything, they've always worked that way, they don't have the time (aka it's not valued by the company), they don't know the patterns, techniques or best practices, or they don't know where to start. But often it's simply because we don't recognize the signs that the code is sending us aka, we don't recognize the smells.
The only valid reason for avoiding refactoring, and it's only valid temporarily, is a short-term schedule that forces us to focus on a single thing.
If you're interested in the lexicon of code smells, I suggest you also take a look at the concepts of refactoring, technical debt and Test-Driven Design (TDD). All this is closely linked to keeping your architecture evolving; identifying code smells is the first step!
References and catalogs