Skip to main content
Create your own
Lesson illustration

Spotting SOLID Violations in Code

Hello! Welcome to the next lesson in our journey through Low-Level Design.

In the previous module, we dedicated a lesson to each of the five SOLID principles, understanding their individual purpose and how to apply them. We concluded with the Dependency Inversion Principle, which tied everything together by showing how to decouple high-level business logic from low-level implementation details.

Today's lesson builds directly on that foundation. We will focus on the learning outcome: Identify SOLID principle violations in existing code examples. Instead of learning new principles, our goal is to synthesize what we've learned and train our eyes to spot the "code smells" that indicate when these principles are being broken. This is a critical skill for code reviews, maintaining a healthy codebase, and, importantly, for system design interviews where you might be asked to critique a given design.

1. A Guide to Spotting SOLID Violations

Think of the SOLID principles as a guide to writing clean, maintainable object-oriented code. When code deviates from these principles, it often develops "smells"—symptoms of deeper design problems. Learning to recognize these smells is the first step toward fixing them.

We'll go through each principle, highlighting the common anti-patterns and code smells that signal a violation. The following article provides excellent "before" and "after" examples for each principle, and also has a great summary of common anti-patterns. We will use it as a reference throughout this section.

SOLID Principles in Java: A Practical Guide

The article 'SOLID Principles in Java: A Practical Guide' from Medium is a fantastic resource for this lesson. It clearly illustrates violations and provides refactored solutions. We will focus on the 'Before' examples to practice our identification skills.

I'll be guiding you through specific sections of this article as we discuss each principle. For now, just have it open and ready. We'll start by looking at the anti-patterns described at the end of the article, as they provide a great overview.

Let's begin by examining the tell-tale signs for each principle.

Single Responsibility Principle (SRP) Violation

  • Reminder: A class should have only one reason to change.
  • Code Smell: The "God Class"
    • The class has a name that implies multiple jobs (e.g., UserManagerAndReporter).
    • You find yourself describing what the class does using the word "and" multiple times.
    • The class contains methods that handle completely different concerns, such as business logic, database persistence, and data formatting, all mixed together.
    • A small change in one feature (e.g., changing an email format) requires you to modify and re-test a large, complex class that also handles user authentication and database logic.

Example Violation:

The UserManager class in the "Common Anti-patterns" section of the SOLID Principles in Java article is a perfect example of a God Class. It mixes user registration, email sending, activity logging, and report generation. Each of these is a separate responsibility.

Similarly, consider the Video class in the video below. It initially handles both data representation and database persistence, a clear violation of SRP.

SOLID Principles in Java Explained | Object-Oriented Clean Code & Design | Geekific Remastered

This video from Geekific shows a clear SRP violation. Notice how the initial Video class is responsible for both its own data and for persisting itself to a database.

Watch from 00:52 to 02:10. Focus on identifying the two distinct responsibilities that are incorrectly combined in the Video class.

Open/Closed Principle (OCP) Violation

  • Reminder: Software entities should be open for extension but closed for modification.
  • Code Smell: The if/else if or switch Chain
    • You see a block of code that uses instanceof or checks an enum/type field to decide which logic to execute.
    • Adding a new type (e.g., a new kind of report, a new shape, a new event type) requires you to go back and add another else if or case to an existing class. This means the class is open for modification, which violates the principle.

Example Violation:

The image below is a classic example of an OCP violation. To handle a new event type, a developer would have to modify the handleEvent method directly.

Java Code Snippet Demonstrating SOLID Principle Violations
This `handleEvent` method violates OCP. Adding a new `Event` subclass would force a modification to this method's `if-else if` structure, making the system fragile.

The "Before OCP" example in the SOLID Principles in Java article, with the AreaCalculator that uses instanceof to check for Rectangle or Circle, is another perfect illustration of this anti-pattern.

Liskov Substitution Principle (LSP) Violation

  • Reminder: Subclasses should be substitutable for their base classes without altering the program's correctness.
  • Code Smell: The "Surprising" Subclass
    • A method in a subclass is overridden to do nothing, or worse, to throw an UnsupportedOperationException.
    • A subclass changes a fundamental behavior or invariant of the parent class. The classic example is the Square/Rectangle problem. A Square "is-a" Rectangle, but if you set its width, its height must also change, which is not true for a Rectangle. This change in behavior can break client code that expects a Rectangle.

Example Violation:

The SOLID Principles in Java article provides a great explanation of the Square-Rectangle problem. Read the "Before" code example in the LSP section (section 2) to see how the Square subclass behaves in a way that a client using a Rectangle reference would not expect, leading to incorrect calculations.

Another clear violation is shown in the video below, where a NoShape class implements the Shape interface but throws an exception, making it non-substitutable.

Learn SOLID Principles with CLEAN CODE Examples

This clip from the Amigoscode video demonstrates a clear LSP violation. A subclass that cannot fulfill the contract of its parent is not truly substitutable.

Watch from 15:47 to 17:35. Pay attention to why the NoShape class, despite implementing the Shape interface, breaks the program when used as a Shape.

Interface Segregation Principle (ISP) Violation

  • Reminder: Clients should not be forced to depend on methods they do not use.
  • Code Smell: The "Fat Interface"
    • An interface contains many methods that cover different, potentially unrelated, functionalities.
    • Classes implementing the interface are forced to provide empty implementations or throw exceptions for methods they don't need or can't support.
    • If you see UnsupportedOperationException in an overridden method, it's a strong hint of either an ISP or LSP violation.

Example Violation:

The "Before ISP" example in the SOLID Principles in Java article is canonical. The Machine interface includes print, scan, fax, and copy. A SimplePrinter class is forced to implement all four methods, even though it can only print. This is a "fat interface" that needs to be segregated.

Dependency Inversion Principle (DIP) Violation

  • Reminder: Depend on abstractions, not concretions.
  • Code Smell: Tight Coupling to Implementation Details
    • You see the new keyword used to create an instance of a low-level utility or service class within a high-level business logic class (e.g., private EmailSender emailSender = new EmailSender();).
    • A high-level class directly imports and references a concrete low-level class (e.g., MySQLDatabase) instead of an interface (DatabaseRepository).
    • Changing a low-level implementation detail (e.g., swapping a MySQL database for a PostgreSQL database) requires you to change the code in your high-level business modules.

Example Violation:

Your experience with Spring Boot and dependency injection gives you a great advantage here. You know that injecting interfaces (@Autowired private MessageSender sender;) is the norm. Any code that manually instantiates a concrete dependency inside a class, like the "Without DIP" example in the SOLID Principles in Java article (section 4), is a clear violation. This creates the tight coupling that frameworks like Spring are designed to prevent.

Test your understanding!

You are reviewing a colleague's code for a simple e-commerce application. You find the following Product class:

public class Product {
    private String name;
    private double price;

    // constructor, getters, setters

    public String getProductAsHtml() {
        return "<div><h1>" + name + "</h1><p>" + price + "</p></div>";
    }

    public void saveToDatabase(DatabaseConnection conn) {
        // logic to save product to a SQL database
    }

    public void applyDiscount(String userRole) {
        if ("premium".equals(userRole)) {
            this.price *= 0.9;
        } else if ("gold".equals(userRole)) {
            this.price *= 0.95;
        }
        // no discount for regular users
    }
}

Which SOLID principles does this class violate, and why? Think about at least two violations.

Show answer

This class has several violations:

  1. Single Responsibility Principle (SRP): The Product class has at least three responsibilities:

    • Holding product data (its primary job).
    • Formatting the product as HTML (getProductAsHtml). This is a presentation concern.
    • Saving the product to a database (saveToDatabase). This is a persistence concern.
  2. Open/Closed Principle (OCP): The applyDiscount method violates OCP. If a new user role (e.g., "platinum") with a different discount is introduced, you would have to modify this method by adding another else if block.

  3. Dependency Inversion Principle (DIP): The saveToDatabase method likely violates DIP because it probably depends on a concrete DatabaseConnection class, coupling the Product entity directly to a specific database implementation.

2. A Practice Kata

Now it's time to practice spotting these issues in a repository designed for this purpose. The following GitHub repository contains small Java projects, each violating one of the SOLID principles.

Some exercises about solid principles and their solutions

This GitHub repository, 'solid-kata' by jahs-es, provides simple, focused exercises for each SOLID principle. We'll use it for a quick practice run.

Navigate to the repository. You will see folders named srp, ocp, lsp, isp, and dip. Each contains Java code that violates the corresponding principle.

Your Task:

  1. Navigate into the ocp folder in the solid-kata repository.
  2. Examine the AreaCalculator.java file.
  3. Without fixing the code, articulate exactly how this class violates the Open/Closed Principle. What would you have to do to add a Triangle shape, and why is that a problem?

Take a few minutes to analyze the code before checking the answer below.

Show your analysis

The AreaCalculator class violates OCP because its area method uses an if-else if block to check the type of each shape.

To add a Triangle shape, you would be forced to modify the AreaCalculator class by adding another else if (shape instanceof Triangle) block. This is a problem because the AreaCalculator should be closed for modification. A well-designed system would allow you to add the new Triangle class (extension) without touching the existing, tested AreaCalculator code.

Conclusion

Today, we've shifted from learning what the SOLID principles are to learning what code looks like when it breaks them. This ability to diagnose design problems is as important as knowing the solutions.

Key Takeaways:

  • SRP Violation (God Class): A class does too many unrelated things (e.g., logic, persistence, presentation).
  • OCP Violation (Modification Chains): if/else if or switch statements that check object types indicate that the code is not closed for modification.
  • LSP Violation (Broken Substitutability): Subclasses that throw UnsupportedOperationException or alter the core behavior of the parent class are a red flag.
  • ISP Violation (Fat Interface): Forcing classes to implement methods they don't need creates brittle code.
  • DIP Violation (Tight Coupling): Using new on concrete low-level classes within high-level modules creates a rigid design that is hard to test and change.
Applying SOLID Principles to a Monolithic Bank Account Application
This flowchart visualizes the journey from a monolithic design to a clean, SOLID architecture. Each step represents fixing the violations we've learned to identify today.

Now that you can effectively identify these violations, you're ready for the next logical step. In our next lesson, we will focus on refactoring code to improve its adherence to SOLID principles, where we'll take the "before" examples and transform them into the clean "after" solutions.

Can't find a good explanation? Sign up and we'll make it for you

Sign up