SSourav Saha
HomeExperienceSoftware DesignSystem DesignLearningBooksToolsContact
SSourav Saha

Building scalable backend systems, distributed infrastructure, and cloud-native applications.

Navigation

  • Home
  • Experience
  • Software Design
  • System Design
  • Learning

More

  • Books
  • Tools
  • Contact

Connect

  • LinkedIn
  • Email

© 2026 Sourav Saha. All rights reserved.

Built with using Next.js

Software DesignRecognizing Bad Code
Software DesignAnti-Patterns

Recognizing Bad Code

Learn to identify long methods, god classes, feature envy, primitive obsession, and switch explosions in your Java applications — with refactoring examples.

May 15, 20246 min read
clean-coderefactoringjava

Introduction

Bad code is a surface-level indicator that usually corresponds to deeper problems in the system. It is not a bug — the code works — but it signals fragility, rigidity, and future pain. Recognizing Bad code early saves weeks of debugging later.

Martin Fowler defines Bad code as: "a surface indication that usually corresponds to a deeper problem in the system."

This article covers the six most dangerous Bad code patterns you will encounter in Java applications, with concrete before/after examples.


1. Long Method

A method that tries to do everything. It reads like a novel, not a function.

Bad Code

public class OrderService {
    public void processOrder(Order order) {
        // Validate order
        if (order.getItems() == null || order.getItems().isEmpty()) {
            throw new IllegalArgumentException("No items");
        }
        if (order.getCustomer() == null) {
            throw new IllegalArgumentException("No customer");
        }
        // Calculate total
        double total = 0;
        for (OrderItem item : order.getItems()) {
            double price = item.getPrice();
            int qty = item.getQuantity();
            double discount = item.getDiscount();
            total += (price * qty) * (1 - discount);
        }
        // Apply tax
        double tax = total * 0.18;
        total += tax;
        order.setTotal(total);
        // Save to database
        Connection conn = DriverManager.getConnection("jdbc:mysql://...");
        PreparedStatement ps = conn.prepareStatement("INSERT INTO orders ...");
        ps.executeUpdate();
        // Send email
        EmailClient client = new EmailClient();
        client.send(order.getCustomer().getEmail(), "Order Confirmed", "...");
    }
}

Refactored Code

public class OrderService {
    private final OrderValidator validator;
    private final PricingEngine pricingEngine;
    private final OrderRepository orderRepository;
    private final NotificationService notificationService;

    public void processOrder(Order order) {
        validator.validate(order);
        pricingEngine.calculateTotal(order);
        orderRepository.save(order);
        notificationService.sendOrderConfirmation(order);
    }
}

Each extracted method has a single responsibility and can be tested independently.


2. God Class

A class that knows too much and does too much. It becomes the dumping ground for every new feature.

Signs

  • Over 500 lines
  • Imports from 10+ packages
  • Has fields that belong to different domains (e.g., customerName, orderTotal, emailTemplate in one class)

Fix

Split into focused classes. An EmployeeService that handles payroll, leave, attendance, and reporting should become four separate services.


3. Duplicate Code

The same logic appears in multiple places. When a bug is fixed in one spot, the others remain broken.

Bad Code

// In OrderService
double total = 0;
for (Item item : items) {
    total += item.getPrice() * item.getQuantity();
}

// In InvoiceService — exact same logic
double invoiceTotal = 0;
for (Item item : items) {
    invoiceTotal += item.getPrice() * item.getQuantity();
}

Refactored Code

public class PricingCalculator {
    public static double calculateTotal(List<Item> items) {
        return items.stream()
            .mapToDouble(item -> item.getPrice() * item.getQuantity())
            .sum();
    }
}

Now both services call PricingCalculator.calculateTotal(items).


4. Feature Envy

A method in one class spends more time accessing data from another class than its own.

Bad Code

public class ShippingCalculator {
    public double calculateCost(Order order) {
        double weight = order.getItems().stream()
            .mapToDouble(i -> i.getProduct().getWeight() * i.getQuantity())
            .sum();
        String zone = order.getCustomer().getAddress().getZone();
        boolean isPrime = order.getCustomer().getMembership().isPrime();

        if (isPrime) return weight * 0.5;
        if ("REMOTE".equals(zone)) return weight * 2.0;
        return weight * 1.0;
    }
}

This method is envious of Order, Customer, and Address. It should live closer to the data it uses.

Refactored Code

public class Order {
    public double totalWeight() {
        return items.stream()
            .mapToDouble(i -> i.getProduct().getWeight() * i.getQuantity())
            .sum();
    }

    public double calculateShippingCost() {
        double weight = totalWeight();
        if (customer.isPrimeMember()) return weight * 0.5;
        if (customer.isRemoteZone()) return weight * 2.0;
        return weight;
    }
}

5. Primitive Obsession

Using primitive types (String, int, double) to represent domain concepts instead of proper value objects.

Bad Code

public class User {
    private String email;        // Could be invalid
    private String phoneNumber;  // No format validation
    private int age;             // Could be -5
    private String currency;     // "USD", "INR", or "banana"?
}

Refactored Code

public class User {
    private Email email;
    private PhoneNumber phoneNumber;
    private Age age;
    private Currency currency;
}

public record Email(String value) {
    public Email {
        if (!value.matches("^[\\w.-]+@[\\w.-]+\\.\\w+$")) {
            throw new IllegalArgumentException("Invalid email: " + value);
        }
    }
}

Value objects enforce validation at construction time — invalid state becomes impossible.


6. Switch Explosion

A switch or if-else chain that grows every time a new type is added.

Bad Code

public double calculateDiscount(String customerType, double amount) {
    switch (customerType) {
        case "REGULAR": return amount * 0.05;
        case "PREMIUM": return amount * 0.10;
        case "VIP": return amount * 0.20;
        case "EMPLOYEE": return amount * 0.30;
        // Adding a new type means touching this method every time
        default: return 0;
    }
}

Refactored Code (Strategy Pattern)

public interface DiscountStrategy {
    double calculate(double amount);
}

public class PremiumDiscount implements DiscountStrategy {
    public double calculate(double amount) { return amount * 0.10; }
}

public class DiscountService {
    private final Map<String, DiscountStrategy> strategies;

    public double calculateDiscount(String type, double amount) {
        return strategies.getOrDefault(type, a -> 0.0).calculate(amount);
    }
}

New customer types are added by creating a new class — no existing code is modified (Open/Closed Principle).


Interview Questions

  1. What is the difference between Bad code and a bug? A bug produces incorrect output. Bad code produces correct output but makes the code harder to understand, modify, and extend.

  2. How do you prioritize which Bad code to fix first? Fix Bad code in areas that change frequently. Stable legacy code with Bad code but no active development is lower priority.

  3. Can design patterns introduce Bad code? Yes. Overusing patterns (e.g., wrapping everything in a Factory) creates unnecessary abstraction, which is itself Bad code called "Speculative Generality."


Common Mistakes

  • Ignoring Bad code in tests: Test code deserves the same care. Duplicated setup blocks and 200-line test methods are real problems.
  • Refactoring without tests: Always write tests before refactoring. You need a safety net.
  • Treating all duplication as evil: Sometimes two similar-looking blocks serve different business purposes and will diverge. Premature DRY can be worse than duplication.

Summary

Bad codeSignFix
Long Method50+ lines, multiple responsibilitiesExtract Method
God Class500+ lines, too many fieldsExtract Class
Duplicate CodeCopy-pasted logicExtract shared utility
Feature EnvyMethod uses another class's data heavilyMove method to that class
Primitive ObsessionRaw strings/ints for domain conceptsValue Objects
Switch ExplosionGrowing if-else/switch chainsStrategy Pattern

Related Articles

  • Clean Code Principles
  • Refactoring Techniques
  • The SOLID Principles Explained
PreviousClean Code Principles: Why Good Software Design MattersNextRefactoring Techniques That Actually Work