Imagine walking into a basement and picking up a musty smell. You suspect it immediately: There could be mold growing down here.
Just as your nose can detect foul odors around you, software developers similarly rely on “smells”—or signals within their code—to identify potential issues in their applications.
In this post, we’ll dive into code smells and explore what you can do to prevent them from occurring in the first place.
Table of Contents
- What Is a Code Smell?
- Smelly Code Can Become Rotten Code
- What to Do if You Discover a Code Smell
- Common Smells to Know About
- How to Prevent Code Smells From Slowing You Down
What Is a Code Smell?
Software development guru Martin Fowler credits his collaborator Kent Beck for coining the term "code smell." Fowler defines it as such: A code smell is a surface indication that usually corresponds to a deeper problem in the system.
Fowler highlights two important nuances packed into the term "smell." First, you detect smells effortlessly. The same is true of code smells: They jump out to you immediately.
Second, is that a smell is merely a superficial suggestion that there is bad code. The operative word there is "suggestion." Here's an analogy: If you get a whiff of that stinky feet smell, you could be smelling stinky feet. Or you could be smelling parmesan cheese.
Likewise with code. You could see a class that is packed with data but no behavior. That's a code smell: Why can't this class do anything? You investigate further and find that the real problem causing this data-heavy class lies in how you're dividing data and functionality in the code. Or you could discover that, actually, it makes perfect sense for your application to have this data-heavy class, even if it generally isn't best practice. But the point is that the code smell led you to investigate and understand your code at deeper levels.
A nice advantage of code smells is that they are detectable by even the most junior members on the team. Because they don't require extensive knowledge of business logic, or even advanced coding chops, anyone can spot them and contribute to improving the quality of the source code. Novice developers can bring the code smells they identify to more experienced team members and they can explore them together, which creates a fantastic learning opportunity.
Smelly Code Can Become Rotten Code
An orange starts to smell. After a couple more days, you pick it up and discover that the underside has turned black and is disintegrating - it's rotten.
A similar progression happens with code.
Smelly code can easily become rotten code. Code rot is the process by which code slowly degrades over time. One cause of code rot is technical debt. A small amount of technical debt is fine - perhaps even inevitable - but it accumulates quickly and can end up seriously hampering your development velocity. An example is a class which accumulates functionality until it becomes a "god class."
Technical debt, like financial debt, compounds: As your engineers write more code to accommodate the smelly code in the code base, the rot in the code spreads and refactoring the code will take longer and longer.
This is why code smells should be taken seriously. They're about a lot more than just making code pretty. They give you the opportunity to prevent technical debt and code rot, which promotes sustained, long-term development velocity. And when teams are moving fast - shipping new features and creating value - they're happier.
Patrick Jean, CTO of OutSystems, spoke with LinearB's Co-Founder and CEO, Ori Keren, at our INTERACT Conference this year about all the ways that technical debt negatively impacts dev teams:
LinearB helps you to to stay on top of technical debt. For example, we track the amount of rework that your team is doing - this is the amount of recently-shipped code that has been changed. A high rework rate indicates that your team is shipping sub-par code that is likely contributing to your technical debt.
What to Do if You Discover a Code Smell
Let's say you're conducting a code review and you get a whiff of a code smell. The first thing to do is to remember that the code smell only suggests that there is a deeper issue.
The second step is to fully understand the smell: What exactly is smelly about it? From there, you can begin exploring its causes. Ultimately, you want to decide whether it can and should be fixed or whether its smelliness is justified. This is a judgment call, so it's worth getting the opinions of other people. By collaborating, you can not only come to the right decision but also increase your knowledge of the code base, coding best practices, and the programming languages your organization uses.
Common Smells to Know About
There are countless kinds of code smells - and what is considered smelly varies from company to company. As such, it’s important to thoroughly understand your company’s development standards before making judgments about smelly code - and certainly before implementing any changes to fix smelly code!
With those caveats established, let's look at the most common code smells. This will give you a solid list to start working with which can go a long way toward improving the quality of your organization's code.
Duplicate code refers to similar code that exists in more than one area. Minimizing code duplication can keep an application clean and prevent you from having to make multiple adjustments during feature updates.
When it comes to code, simplicity is critical. Unfortunately, developers often make code complex when it doesn’t have to be. Contrived complexity occurs when you use complex design patterns instead of simpler ones. Code that is easier to understand is easier to maintain and build upon.
If your variables, classes, and functions lack proper names, this can be an indicator that your code isn’t clean. Names should correct, simple, and accurate in order to avoid issues.
Bloaters are classes and methods that increase in size over time - to the point where they become difficult to work with. Long methods and baggy classes tend to only grow in size as developers iterate and make changes within an application.
Dead code is code that’s in the application but not in use. If code isn't being used, delete it. If you need it later, you can always recover it by looking in the version history. A static analysis tool is a great way to check for dead code.
Sometimes you end up with a class that does nothing but delegate work to another class - for instance, its method calls are just call the methods of other classes. This is likely the result of previous refactoring that moved functionality elsewhere, leaving the class all but empty. If the class is doing nothing but acting as a "middle man," it's just noise in the code that needs to be eliminated.
Long Parameter List
It’s important to cap the number of parameters within a function at three or four. In the event a function has more than three or four parameters, this could indicate that the function needs to be decomposed into multiple smaller functions.
This is when you have method calls on top of method calls. There are so many issues associated with this code smell that we actually wrote an entire post on it.
Data clumps occur when multiple pieces of data go together. One easy way to spot a data clump is when one component doesn’t make sense in isolation but makes sense as a group.
A shotgun surgery means that in order to accomplish one change, you have to make lots of small changes to the codebase. This is the result of a violation of the principle of single responsibility.
An object-orientation abuser happens when the code doesn’t maximize its object-oriented design. Examples include alternative classes with different interfaces, switch statements, and refused bequests.
A dispensable code smell happens when unnecessary code accumulates. This leads to codebase clutter, which can slow down applications and negatively impact performance.
This is when some code uses functionality from another part of the codebase that it shouldn't be using. For example, one class is using the fields or methods of another class. When this happens, the functionality of a class is not completely encapsulated in that class. This presents a risk. If a developer refactors the internal structure of the class, this could cause bugs elsewhere in the application.
A change preventer is something that can stop you from easily developing and modifying code. Common examples include divergent changes and parallel inheritance hierarchies.
How to Prevent Code Smells From Slowing You Down
While code smell is often preventable, engineering teams frequently lack the time and resources to prevent it from happening. In many cases, developers would rather wait for problems to arise and then fix them instead of looking for potential issues that might impact operations at some point in the future. And as a result, the code smells persist - even smells that are easily fixable.
Using the right tools can help your teams address code smells. We've already looked at how the rework rate metric can reveal that your team is writing smelly code that is contributing to technical debt. Let's at a few more metrics that LinearB provides that you can use to stay on top of smelly code:
Review time: If it's taking your team a long to review code, this could be because your devs are writing smelly code that other people have to clean up.
Pull requests merged without review: This is risky because it allows smelly code to enter production undetected. Ensuring that a second pair of eyes looks over a delta before it is merged improves the quality of the code that is introduced into the codebase.
Pull request size: Large PRs could indicate that your team is having update lots of lines of code just to accomplish one change. This could be caused by a number of code smells that we looked at above: shotgun surgery, duplicate code, or contrived complexity.
LinearB not only surfaces these metrics; we have an entire framework for improvement built around metrics. Once you establish your baselines, you can set goals for improvement by leveraging the engineering benchmarks we calculated by analyzing almost 2,000 development teams. Our WorkerB automation bot can help you meet those goals by doing things like alerting you about hanging PRs, cursory reviews, or high-risk deltas so that you can proactively intervene and prevent smelly code from getting merged into production.
LinearB is powerful, but it isn't complicated. It takes minutes to set up; it integrates right with Git and tools like GitHub, GitLab, and Jira; and it's intuitive to use.
To see just how easy is to get started improving your development processes, get in touch to set up a demo.