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,emailTemplatein 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
-
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.
-
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.
-
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 code | Sign | Fix |
|---|---|---|
| Long Method | 50+ lines, multiple responsibilities | Extract Method |
| God Class | 500+ lines, too many fields | Extract Class |
| Duplicate Code | Copy-pasted logic | Extract shared utility |
| Feature Envy | Method uses another class's data heavily | Move method to that class |
| Primitive Obsession | Raw strings/ints for domain concepts | Value Objects |
| Switch Explosion | Growing if-else/switch chains | Strategy Pattern |