Clean code — Does code smell? Part 3

Leonardo Herdy Marinho
8 min readFeb 15, 2024

--

Welcome to the third and final part of our series on clean code and code smells. In this article, we will delve into three more code smells: switch statements, data clumps, and tests. We will discuss the potential issues associated with these coding practices and explore strategies to mitigate them. By the end of this article, you will have a deeper understanding of these code smells and be equipped with techniques to write cleaner and more maintainable code. If you missed the previous articles, be sure to check them out to get a comprehensive understanding of clean code principles and other common code smells. We hope you find this series helpful in your journey towards writing elegant and efficient code.

Clean code — Does code smell? Part 1

Clean code — Does code smell? Part 2

Switch Statements

Switch statements, when used excessively or in certain ways, can become a code smell. A switch statement is a control flow structure commonly found in programming languages. It evaluates an expression and performs different actions based on the value of that expression. While switch statements are a useful tool, they can lead to issues when used inappropriately. Here are some concerns associated with switch statements that may indicate a code smell:

Large Switch Statements:

  • A large switch statement with many cases can make code hard to understand and maintain. It may suggest that the logic could be better organized or refactored.

Switch Statements in Multiple Places:

  • If the same switch statement or similar switch logic appears in multiple places in the code, it can be a sign that the logic should be encapsulated and reused.

Switch Statements Violating Open/Closed Principle:

  • Switch statements may violate the Open/Closed Principle of SOLID, which states that a class should be open for extension but closed for modification. Adding a new case often requires modifying the switch statement, which may not be scalable.

Switch Statements with Duplicated Code:

  • Similar actions in different case branches may indicate duplicated code. This violates the DRY (Don’t Repeat Yourself) principle, and it might be more appropriate to refactor common logic into a separate function or class.

To mitigate the issues associated with switch statements, consider the following strategies:

Use Polymorphism:

  • Replace switch statements with polymorphism when possible. Instead of a single switch statement, create a hierarchy of classes or interfaces, each handling a specific case.

Strategy Pattern:

  • Implement the Strategy pattern, where you encapsulate algorithms or behaviors in separate classes. The main code can then delegate the responsibility to the appropriate strategy without using a switch statement.

Command Pattern:

  • Similar to the Strategy pattern, the Command pattern can be employed to encapsulate actions as objects. This can help decouple the code and reduce the need for switch statements.

State Pattern:

  • If the switch statement deals with the state of an object, consider using the State pattern to represent different states as separate classes.

Factory Pattern:

  • Use the Factory pattern to create objects dynamically based on a condition instead of using a switch statement to determine the object type.

Here’s a simple example to illustrate a switch statement and a possible refactoring using the Strategy pattern:

Until now, it looks ok. But, let’s consider in 1 year we will accept more than 20 different payment gateways. What should we do? Add manually and create a switch monster? And probably some of them will receive more than 1 or 2 lines of code. It isn’t a good practice! We can refactor it using some pattern. I’m using the strategy pattern for the following image:

In this refactoring: The original switch statement is replaced with a set of classes (CreditCardProcessor, PaypalProcessor, BankTransferProcessor) implementing the PaymentProcessor interface. The processOrderWithStrategy function now accepts a PaymentProcessor instance, allowing for dynamic selection of the payment processing strategy. This approach adheres to the Open/Closed Principle, as adding a new payment method involves creating a new PaymentProcessor class without modifying the existing code.

Data Clumps

This is a code smell that occurs when groups of variables or parameters are frequently used together in the code. These sets of related data should be considered as a single unit, and their repeated appearance may indicate a need for encapsulation or the creation of a new data structure to represent that unit.

Here are some indicators and reasons why Data Clumps are considered a code smell:

Repetition of Grouped Data:

  • The same group of variables or parameters appears together in multiple places throughout the code.

Code Duplication:

  • Data Clumps often lead to code duplication, as developers copy and paste the same group of variables in various locations.

Decreased Maintainability:

  • Changes to the structure of the data (e.g., adding or removing a field) may require modifications in multiple places, increasing the risk of introducing errors.

Violates DRY Principle:

  • Data Clumps violate the DRY (Don’t Repeat Yourself) principle, which encourages the reuse of code. Instead of duplicating the same group of variables, it’s better to represent them as a single entity.

To address the issue of Data Clumps, consider the following strategies:

Encapsulation:

  • Group related variables into a class or structure. This encapsulation creates a more meaningful and self-contained representation of the data.

Parameter Object:

  • If a group of parameters is frequently passed together to methods, consider using a parameter object. This can improve code readability and reduce the number of parameters in method signatures.

Introduce a Data Structure:

  • Create a data structure (e.g., a class or a tuple) to represent the group of related variables. This provides a clear abstraction for the data.

Code Refactoring:

  • When you notice Data Clumps, consider refactoring the code to eliminate duplication. Extract common groups of variables into their own structures or classes.

Here’s a simple example to illustrate the concept of Data Clumps:

And refactoring…

Now, all parameters are reusable, we aren’t violating the DRY, and looks better for maintenance. The OrderItem class is used to encapsulate the related variables (quantity, unitPrice, and discountRate).

(Missing) Tests

Tests themselves are not inherently a code smell. On the contrary, writing and maintaining tests is a crucial aspect of software development to ensure code correctness, catch regressions, and facilitate changes in the codebase. However, certain characteristics of tests or testing practices can be indicative of potential issues or areas for improvement. Here are some considerations related to tests that might be perceived as code smells:

  1. Brittle Tests:
  • Tests that break easily with minor changes in the code may be considered brittle. This could indicate poor test design, excessive coupling between tests and implementation details, or issues with the test environment.

Low Test Coverage:

  • Inadequate test coverage, where critical parts of the code are not adequately tested, can be a concern. Low coverage increases the risk of undiscovered bugs and reduces confidence in code changes.

Hard-to-Read Tests:

  • Tests that are hard to understand or maintain can be considered a code smell. Clear and readable tests are essential for effective collaboration and long-term maintainability.

Overuse of Mocks and Stubs:

  • Overusing mocks and stubs can lead to tests that don’t accurately reflect the real behavior of the code. It’s important to strike a balance between isolating units for testing and ensuring that tests reflect real-world scenarios.

Test Duplication:

  • Repeated test logic across different tests can indicate redundancy. Duplicated test code may lead to maintenance challenges and make it harder to update tests when needed.

Slow Test Suites:

  • A slow test suite can impact developer productivity. If tests take too long to run, developers may be less likely to run them frequently, increasing the risk of undetected issues.

Integration Test Overuse for Unit Testing:

  • Using integration tests where unit tests would be more appropriate can be a code smell. Unit tests should focus on testing individual units of code in isolation, while integration tests should cover interactions between components.

To address potential issues related to tests, consider the following strategies:

Improve Test Design:

  • Write tests that are clear, concise, and focus on one thing at a time. Avoid unnecessary complexity and ensure that tests accurately reflect the behavior of the code.

Refactor Brittle Tests:

  • Refactor tests that break frequently due to changes in the code. Consider whether the test is overly coupled to implementation details and adjust it to be more resilient.

Increase Test Coverage:

  • Regularly review and increase test coverage, especially for critical and frequently changing parts of the code. Identify areas with low coverage and create targeted tests.

Use Mocks and Stubs Wisely:

  • Be mindful of the use of mocks and stubs. While they are useful for isolating units, avoid overusing them to the point where tests no longer reflect real-world scenarios.

Address Slow Test Suites:

  • Optimize slow test suites by parallelizing tests, using test runners efficiently, or considering strategies like test data management to improve performance.

Refactor Duplicated Test Logic:

  • Eliminate duplication in test code by extracting common test logic into shared utility functions or using test fixtures.

Balance Integration and Unit Tests:

  • Ensure a balance between unit tests and integration tests. Use integration tests for broader scenarios and unit tests for isolated, focused testing.

Remember that tests are an integral part of the development process, and their quality is essential for maintaining a robust and reliable codebase. Regularly reviewing and improving testing practices can help identify and address potential code smells in the test suite. Don’t forget to check and write solid tests before huge refactorings. A code without tests is a code that smells bad!

In conclusion, understanding and recognizing code smells is essential for writing clean and maintainable code. In this series, we explored various code smells such as switch statements, data clumps, and issues related to tests. By being aware of these code smells and applying appropriate strategies, you can improve the quality of your code and make it more readable, flexible, and easier to maintain.

Remember, clean code is not just about following specific rules or conventions, but also about writing code that is easy to understand, modify, and extend. By continuously evaluating and refactoring your code, you can strive for cleaner and more efficient code that will benefit both you and your team in the long run.

We hope this series has provided you with valuable insights and techniques to identify and address common code smells. Keep practicing, learning, and refining your coding skills, and enjoy the journey of writing cleaner and more elegant code!

Happy coding!

--

--

Leonardo Herdy Marinho
Leonardo Herdy Marinho

Written by Leonardo Herdy Marinho

I am a Master in computer science, senior software engineer (mobile focused), author, and curious.

No responses yet