Code smells and Anti-patterns: How to recognize bad code

‘Who doesn’t love a bit of bug fixing?’, said no one…probably ever. To make your code cleaner and more reliable, peek into this blog and learn to recognize the signs of poorly structured or inefficient code.

Reading Time8 minutes

During the development of a large software project, bad and questionable code is certain to accumulate. Engineers, analyzing large bodies of code, notice repeating instances of questionable code. Today, we call those instances code smells and anti-patterns.

There are many anti-patterns and code smells—too many to discuss in one blog. So, for this blog, I have chosen to discuss the ones I think are the most meaningful.

What's the difference?

At first glance, there isn’t a clear difference between code smells and anti-patterns, and many would call them the same thing. Many people falsely classify and use the terms interchangeably. It should be noted that some code smells can be interpreted as anti-patterns and vice versa, depending on the context.

In this blog, we will define code smells as indicators of deeper problems in a codebase. If you notice a code smell in a codebase that doesn’t automatically mean that code is bad. It means that there might be bigger problems in the codebase. 

Anti-patterns, on the other hand, are solutions, practices, or methodologies that are ineffective and lead to bad outcomes.

Internal consequences 

Internal consequences refer to problems that the development team may suffer from when code exhibits smells and anti-patterns. There are many consequences of anti-patterns and code smells. 

  • The first one is maintainability: code that exhibits code smells and anti-patterns is always harder to maintain. 
  • Then we have simplicity: anti-patterns make code more complex without any particular reason. 
  • Furthermore, there is reusability: one often wants to have pieces of code that can be reused in other projects. 

And lastly expandability: code that suffers from anti-patterns and code smells is harder to add to. There are a few more consequences but these are the ones I found most important.

External consequences

External consequences refer to problems that end users experience

  • The first one is reliability: code smells and anti-patterns can make software less reliable. 
  • Then there is availability: remote services are expected to always function as intended but code exhibiting code smells can make it not so. 
  • And lastly effectiveness: effectiveness in software engineering is evaluated based on how well the software meets their needs and expectations.

1. Methodological anti-patterns

These anti-patterns are false methodologies that are often accidentally followed by a software engineer and result in over-engineering. Here are some examples:

Analysis paralysis

This is commonly observed in decision-making processes, where a software engineer will overanalyze a situation to the point where no decisions are made or decisions are delayed. It occurs when there is a lot of information available, leading to uncertainty and indecision about the best course of action.

Not Invented Here (NIH)

The NIH describes a mindset where software engineers reject ideas, solutions, or products created externally in favor of developing their own, even if it duplicates existing work or requires more resources.

Premature optimization

When software developers focus on optimizing code or system performance before it's necessary. This can occur at various stages of the development process, from initial design to implementation and testing.

How to mitigate these anti-patterns?

The easiest way to mitigate them is to tell yourself that it doesn’t have to be perfect. The end user doesn’t see your perfect implementation and doesn’t care as long as it works well. Don’t overanalyze, don't reinvent the wheel, and optimize when needed.

2. Software development anti-patterns

Software development anti-patterns are false practices that a developer may partake in because of laziness or ignorance. Here are some examples:

Spaghetti code

Anti-pattern characterized by poorly structured, tangled, and difficult-to-follow code. It typically lacks clear organization and modularization, leading to a mess of interdependent logic.

God object

A God object is an anti-pattern that occurs when a single class or module within a system has an excessively large and tangled amount of responsibility. This class tends to know too much or do too much.

Input Kludge

Refers to a workaround or quick fix implemented to address issues related to user input. This could involve handling unexpected or invalid user input in a way that is not ideal but allows the program to continue functioning without crashing or producing incorrect results.

How to mitigate these anti-patterns?

Refactoring code can mitigate software development anti-patterns while keeping useful practices such as KISS, DRY, and JAGNI in mind.

3. Architectural anti-patterns

Here are some examples of architectural anti-patterns:

Big ball of mud

This anti-pattern describes a software system that lacks a clear architecture or design. Instead, it evolves over time in a haphazard manner, often becoming overly complex, difficult to understand, and challenging to maintain.

Vendor lock-in

The Vendor lock-in comes about when architecture is made with dependency on a specific vendor. Switching vendors becomes difficult, costly, and impractical.

Architecture by implication

It refers to the architectural decisions and structures that emerge implicitly as a result of various design choices, rather than being explicitly defined or planned.

How to mitigate these anti-patterns?

To mitigate architectural anti-patterns, regularly review the architecture and codebase to identify instances of anti-patterns. Educate the development team about these anti-patterns and their consequences to foster awareness. Refactor the codebase to eliminate anti-patterns while adhering to best practices and maintaining the integrity of the overall architecture.

Code smells

Code smells can be divided into 5 categories:

  • Structural
  • Behavioral
  • Concurrency
  • Deadlocks
  • Testing

1. Structural

These code smells are related to the overall structure of the code, including its organization, modularity, and relationships between components. Here are some examples:

God class/module

It suggests that a particular class or module is overly large, complex, or tightly coupled, which can make code harder to understand, maintain, and extend. As a code smell, it serves as a warning sign for potential issues that should be addressed through refactoring or redesigning the affected component.

Shotgun operation

Shotgun operation (or surgery) occurs when a single change to the codebase requires numerous small changes across multiple classes or modules. It's named as such because making a change feels like firing a shotgun blast, with the changes scattered across the codebase.

2. Behavioral

These relate to the behavior of the code, including its correctness, efficiency, and adherence to best practices. Here are some examples: 

Dead code

Dead code refers to sections of a program's source code that are no longer executed or reachable during the program's execution. It can manifest as unused variables, methods, classes, or even entire blocks of code. Dead code is problematic because it clutters the codebase, making it harder to understand, maintain, and debug. 

Golden hammer

The Golder hammer is a metaphorical concept in software development that refers to an overreliance on a single tool, technology, or approach to solving all problems. It implies using the same familiar solution for every problem, regardless of whether it's the best fit. This can lead to suboptimal solutions, missed opportunities for innovation, and technical debt.

3. Concurrency

Concurrency code smells refer to patterns or characteristics within concurrent or multi-threaded code that indicate potential issues or areas for improvement. Here is an example of concurrency code smells:

Race conditions

These occur when the outcome of the program depends on the sequence or timing of events, which can lead to unpredictable or unintended behavior. These issues arise when multiple threads or processes access shared resources simultaneously, and the final result depends on the order or timing of their operations.

4. Deadlocks

Deadlocks are a type of concurrency issue that appears when two or more threads or processes become stuck indefinitely, each waiting for the other to release a resource that it needs to proceed. They are a critical problem because they can cause the entire program to freeze or become unresponsive.

5. Testing

Testing code smells are patterns or characteristics within test code that indicate potential issues or areas for improvement. These smells can affect the quality, maintainability, and effectiveness of test suites. Here are two examples:

Faulty test

A "faulty test" refers to a test case within a test suite that doesn’t accurately or effectively verify the behavior of the code under test. Faulty tests can lead to false positives (passing tests that should fail) or false negatives (failing tests that should pass), reducing the reliability and effectiveness of the test suite.

Test dependence

Test dependence refers to a situation where the outcome of one test case is influenced by the execution or result of another test case. This dependency can manifest in various ways and can lead to issues such as test fragility, maintenance difficulties, and decreased test suite reliability. 

How to mitigate code smells

To mitigate code smells, prioritize clean code principles such as readability, maintainability, and scalability. Conduct regular code reviews to identify and address potential issues early on. Utilize automated testing and static code analysis tools to catch code smells and enforce coding standards. Foster a culture of continuous improvement and knowledge sharing within the development team to collectively identify and address code smells throughout the development process.

How can this knowledge help you?

Simply put, learning about various anti-patterns and code smells can help you avoid them. 

It can impact the quality of your software and the satisfaction of your clients. Furthermore, understanding anti-patterns and code smells enables you to write more maintainable and extensible code, reducing technical debt and facilitating easier collaboration among team members. 

By proactively addressing these issues, you can enhance the overall efficiency and effectiveness of your software development process, leading to improved outcomes for both your team and your clients.

Where and how to learn about them?

This blog mentions only a handful of code smells and anti-patterns. 

Here is an extensive list of code smells, and you can add your own as a contributor to this GitHub repo.

There isn't anything similar for anti-patterns, so here are some good sources:

Hey, you! What do you think?

They say knowledge has power only if you pass it on - we hope our blog post gave you valuable insight.

If you are in need of a high-caliberapp with resilient code or you are a software engineer or SDET who wants to contribute their insights, feel free to contact us

We'd love to hear what you have to say!