It’s time to find out who’s the best—so the boxing match between the Dev and the Code Smell is on!
The hard coder
The devs that prepare before coding














Code smells are signs that your code is violating fundamental design principles. These issues can significantly lower code quality, making it harder to understand and modify. As a result, even small changes can require a disproportionate amount of effort, which negatively impacts the development team’s productivity.
This post isn’t focused on the broader relationship between code smells, code quality, and team productivity. Instead, it dives into how to identify and fix some of the most common—and dangerous—code smells.
If you’re interested in exploring why, how, and where code smells emerge in your codebase, I invite you to check out my previous post:
Most Common Code Smells
The following table summarizes some of the most common code smells. In the next section, we’ll dive deeper into several of these smells — exploring examples, how to detect them, and how to fix them.
We won’t cover every single smell individually. Instead, we’ll group some of them together when they share similar root causes or solutions. By the end of this post, you should be able to recognize common code smells and have a solid idea of how to address them effectively.
| Code Smell | Description |
| Long Method | Functions that try to do too much, making them hard to understand and maintain. |
| Large Class | Classes that handle too many responsibilities, violating the Single Responsibility Principle. |
| Feature Envy | A method that relies more on data from another class than its own. |
| God Object | A class that holds too much knowledge or control, becoming overly complex. |
| Duplicate Code | Identical or very similar code appears in multiple places. |
| Switch Statements | Overused conditionals that may indicate a lack of proper polymorphism. |
| Shotgun Surgery | A small change requires editing code in many different classes or files. |
| Data Clumps | Groups of variables that tend to appear together and probably belong in their own class. |
| Inappropriate Intimacy | Classes that expose or depend on each other’s internals too much. |
Let’s go deeper into some of these…
Divergent Change
Imagine you’re in a restaurant kitchen, and one chef is responsible for everything — cooking the sauce, preparing the fish, and making the dessert. Now, suppose there’s a problem with the sauce: the ingredients are cut too large, and for whatever reason (I’m no chef), it’s making the sauce taste bad.
To fix this, we decide to change how the ingredients are cut — maybe slicing them smaller. But here’s the issue: if this new method takes more time, it might delay the chef, causing the fish to burn or the dessert to be rushed. So, even a small change in one part of the process can have unintended consequences elsewhere.
So, every time we want to make a change, like improving how the sauce is prepared, we also have to be careful not to mess up how the fish is cooked or how the dessert is made. That means we end up needing to understand too much about two other processes that aren’t really related to the one we want to change — all because the same chef is handling all three tasks:
- Preparing the sauce
- Cooking the fish
- Making the dessert
In programming, this is why it’s important to give each class or component a single, focused responsibility. Let’s look at an example (adapted from [2]):
Suppose we’re building an app that needs to create, delete, and manage users. These users are stored in the file system, so we also need to read and write user data to files. A first attempt might look like this:

How many responsibilities can you spot in this class?
It actually has a couple of them:
- Creating, deleting, and editing users
- Storing those users
This becomes a problem. For example:
- If you want to add validation before saving or editing a user, you have to change this class.
- If you decide to store users in JSON format instead of plain text, you also have to modify this class.
This means the class has multiple reasons to change, which is known as a Divergent Change — a clear sign that the class is doing too much.
When a class or method handles multiple responsibilities, it tends to grow quickly. This often leads to long methods and bloated classes that become harder and harder to understand. Every time you want to make a change, you’re forced to read through unrelated code. For example, if you want to change how users are stored as JSON, you might find yourself digging through logic related to editing or deleting users — just because everything is bundled in the same class.Also, large classes like this are harder to test, document, and debug. If something breaks, it’s more difficult to track down the root cause because there’s so much code involved. The same principle applies to methods:
If a method is hard to test or document, it might be a sign that it’s doing too much.
How detect and fix it
When the attributes and methods in a class are closely related, we say the class has high cohesion. High cohesion usually means the class has a single, well-defined responsibility, which is a good design practice.
But how can we measure cohesion? That’s where the LCOM metric comes in — Lack of Cohesion of Methods. It helps us quantify how cohesive a class really is.
There are several versions of LCOM — LCOM1, LCOM2, LCOM3, and LCOM4 — each with its own way of calculating cohesion. However, one of the most intuitive ways to understand it is through a graph-based visualization.
Here’s how it works:
- Draw a rectangle for each method.
- Draw a circle for each attribute.
- Then, draw arrows from each method to the attributes it uses.
By doing this, you can visually see how methods and attributes are connected. If you notice separate groups of methods that don’t share any attributes, that’s a sign the class might be doing more than one thing — and could be split into smaller, more focused classes.Check out the example below (adapted from [3]). You’ll see two distinct groups, suggesting that the class can be cleanly divided into two separate classes:

However, this approach doesn’t always work exactly the same way in every case. Sometimes, we need to look at the diagram more closely and carefully to understand what’s really going on. For example, let’s build the diagram for our UserManager class:

We don’t necessarily have to split the groups completely, but we’re very close. The diagram clearly shows that the class could be separated into two parts without much trouble. So, we can go ahead and split the UserManager class like this:

Shotgun Surgery
Let’s go back to our restaurant example. Imagine that every morning, someone goes to the local market and buys all the ingredients the chefs will need for the day. Later, they place all the ingredients on a big table in the kitchen. Each chef comes to the table to pick what they need — but before using any ingredient, they must first check that it passes the restaurant’s quality validation standards.
Now, let’s suppose the restaurant wants to make the ingredient validation process even stricter. To do this, they would need to explain the new validation rules to every chef. That means involving a lot of people and disrupting the process.
But what if we changed the original setup? Imagine that instead of the chefs checking the ingredients themselves, a dedicated person inspects the ingredients at the table. If they pass the quality check, this person places them on a second table where the chefs can simply take and use them. Now, if the validation rules change, we only need to inform and train that one person — the chefs wouldn’t even notice the change.
This illustrates the idea of Shotgun Surgery — when a single change in our code forces many small changes across different parts of the system. Let’s look at an example:

Now, suppose you want to add a phone number validation rule: “All phone numbers must be exactly 10 digits long.”
If your validation logic is scattered across multiple classes, you’ll need to update each one individually — just like the chefs who all need to learn a new validation rule. But instead, you could create a single, dedicated class responsible for validating phone numbers.

At first, you might think, “Isn’t this the same problem?” After all, you still need to update each class to use the new PhoneValidator class. But the key difference is that from now on, if the validation logic ever needs to change, you only have to update it in one place.
Shotgun Surgery makes code hard to maintain and easy to break. If you forget to update even one spot, it could lead to bugs in unexpected parts of the system. In our example, the code was simple, but in a real-world project, both the logic and the codebase can be much more complex — making it even easier to miss a spot.
Duplicate Code and Data Clumps are common code smells that resemble Shotgun Surgery in some ways.
- Duplicate Code occurs when the same logic is repeated in multiple places throughout the codebase.
- Data Clumps happen when a group of related parameters—such as database connection settings—appear together repeatedly in different parts of the code.
I believe the best way to detect Shotgun Surgery is by looking at how often two or more files tend to change together.
Complex code
I don’t have any restaurant example for complex code, so let go directly into code, the code complexity is about how hard it is to understand the code, maybe it is complex because it is doing a lot of things and sometimes it is complex because the dev makes it complex. In both cases the fix is slightly different.
Let see this to methods take from [9] to understand better what is all of complexity about:
Example A

Example B

Which example is more complicated? Obviously, the second one is more complex. That’s expected, since it’s doing more things. But as I mentioned before, complexity doesn’t always mean the code is wrong. Sometimes we do need to keep the same logic, but we can still improve how it looks to make it easier to understand. Take another look at Example B.”

Is it better? Let’s say you just want to change the messages that are printed — for example, you want to include the product in the message like: ‘The product is X, and it is even.’ Now, you can simply go to the checkProductDivisibility method and update the message there without worrying about the rest of the code. It might not seem like a big deal with this small example, but in a method with complex logic, it could take a lot of time to figure out exactly where to make such changes.
Also, believe me — the simpler the method, the easier it is to test and document.
Now, let’s go back to the original code in Example B. What exactly makes it more complicated than Example A? Well, it has more variables, more if statements, more loops, and of course, more lines of code. All of these contribute to making a method harder to understand. The good news is that this complexity can be measured
How detect it
Cyclomatic Complexity
Cyclomatic complexity is a software metric used to measure the number of independent paths through a method. Each path represents a possible way the code can execute, depending on different inputs or conditions.
For example, if a method contains an if statement, there are two possible paths:
- One path if the condition is true and the if block runs.
- Another path if the condition is false and the if block is skipped.
Certain code structures increase the number of possible paths, including:
- if statements
- switch cases
- for and while loops
- Logical operators like && and ||
- catch blocks
- Ternary (? 🙂 operators
To calculate the cyclomatic complexity, you count all of these branching points and add 1. The extra 1 accounts for the default, straight-line path with no branching.
So the formula is:
Cyclomatic Complexity = Number of decision points + 1
This number also tells you the minimum number of test cases needed to fully test the method, ensuring that every possible path is covered.
Let’s look at some examples from [source 9]
Low Complexity Example

Cyclomatic Complexity: 1 (No decision points)
Medium Complexity Example

Cyclomatic Complexity: 3 (Two decision points)
High Complexity Example

You can clearly see from the examples how the complexity increases as the number of decision points grows.
BUT WAIT!!!, you said that the number of decisions point was not the only thing that affect complexity, right, you have another measures that take account another thing like Halstead Volume taking account the numbers of operand and operators used in the method, also you have The Maintainability Index that take Cyclomatic complexity, Halstead Volume and the number of lines of the method to calculate complexity.
More Smells
There are many more code smells out there, but this post is already getting quite long. Hopefully, everything we’ve covered so far has given you a better idea of what code smells look like—and how to avoid them.
What next
I know what you’re thinking:
“Do I really have to calculate all these metrics manually?”
The answer is no—there are tools that can do it for you.
You might also be wondering:
- What principles are behind all of this?
- Which tools can I use to manage it effectively?
Those are great questions, but they’re also big topics that deserve their own deep dive.Reference
