Code smells are indicators of potential problems in your code that could lead to issues with maintainability, readability, or performance. While they don't necessarily cause bugs, addressing code smells ensures your codebase remains clean and efficient.
We will look into 5 common code smells in Java, offering examples, in-depth explanations, and better approaches to handle them effectively.
1. Long Method
Code Smell: A method that is excessively long can make the code harder to read, test, and maintain. Even if the method appears modularized with helper methods, it might still combine multiple levels of abstraction, violating the Single Responsibility Principle (SRP).
Example:
public void processOrder(Order order) {
validateOrder(order);
calculateDiscount(order);
updateInventory(order);
generateInvoice(order);
sendNotification(order);
}
Here, processOrder
orchestrates multiple unrelated tasks—validation, discount calculation, inventory update, invoice generation, and notification—making it difficult to extend or modify without risking unintended consequences.
Better Approach:
Refactor the orchestration logic to separate these responsibilities more clearly. Using design patterns such as the Command Pattern or Pipeline Pattern can help ensure modularity.
Refactored Code with Command Pattern:
public interface OrderCommand {
void execute(Order order);
}
public class ValidateOrderCommand implements OrderCommand {
public void execute(Order order) {
// Validation logic
}
}
public class ApplyDiscountCommand implements OrderCommand {
public void execute(Order order) {
// Discount application logic
}
}
public class OrderProcessor {
private List<OrderCommand> commands;
public OrderProcessor(List<OrderCommand> commands) {
this.commands = commands;
}
public void processOrder(Order order) {
for (OrderCommand command : commands) {
command.execute(order);
}
}
}
// Usage
List<OrderCommand> commands = List.of(
new ValidateOrderCommand(),
new ApplyDiscountCommand(),
new UpdateInventoryCommand(),
new GenerateInvoiceCommand(),
new NotifyCustomerCommand()
);
OrderProcessor processor = new OrderProcessor(commands);
processor.processOrder(new Order());
Benefit:
- Improves modularity.
- Each command handles a single responsibility and can be tested or reused independently.
- Adding new steps (e.g., fraud detection) is as simple as adding a new
OrderCommand
.
2. God Class
Code Smell: A "God Class" tries to handle too many responsibilities, leading to high coupling and poor maintainability.
Example:
public class OrderManager {
public void createOrder() { /* Implementation */ }
public void updateOrder() { /* Implementation */ }
public void deleteOrder() { /* Implementation */ }
public void validatePayment() { /* Implementation */ }
public void sendInvoice() { /* Implementation */ }
}
Better Approach:
Divide the responsibilities into smaller, focused classes, each aligned with a specific domain task.
Refactored Code:
public class OrderService {
public void createOrder() { /* Implementation */ }
public void updateOrder() { /* Implementation */ }
public void deleteOrder() { /* Implementation */ }
}
public class PaymentService {
public void validatePayment() { /* Implementation */ }
}
public class NotificationService {
public void sendInvoice() { /* Implementation */ }
}
Benefit:
- Reduces coupling and improves modularity.
- Each service class becomes easier to maintain, test, and extend independently.
3. Magic Numbers
Code Smell: Using literal numbers (or "magic numbers") directly in your code makes it hard to understand and modify.
Example:
public double calculateDiscount(double totalAmount) {
return totalAmount > 1000 ? totalAmount * 0.1 : totalAmount;
}
Better Approach:
Replace magic numbers with constants that have meaningful names.
Refactored Code:
private static final double DISCOUNT_THRESHOLD = 1000;
private static final double DISCOUNT_RATE = 0.1;
public double calculateDiscount(double totalAmount) {
return totalAmount > DISCOUNT_THRESHOLD ? totalAmount * DISCOUNT_RATE : totalAmount;
}
Benefit:
- Improves readability and reduces the risk of errors during updates.
- Constants provide clarity about the business logic.
4. Duplicated Code
Code Smell: Repetitive code across methods or classes leads to maintenance challenges and inconsistency.
Example:
public double calculateTax(double amount) {
return amount * 0.18;
}
public double calculateDiscount(double amount) {
return amount * 0.1;
}
Better Approach:
Abstract the common logic into a reusable method.
Refactored Code:
private double applyRate(double amount, double rate) {
return amount * rate;
}
public double calculateTax(double amount) {
return applyRate(amount, 0.18);
}
public double calculateDiscount(double amount) {
return applyRate(amount, 0.1);
}
Benefit:
- Avoids redundancy.
- Ensures consistency across methods.
- Makes the code easier to modify and extend.
5. Excessive Parameter List
Code Smell: Methods with too many parameters are difficult to read, understand, and prone to errors during calls.
Example:
public void createUser(String firstName, String lastName, String email, String phoneNumber, String address) {
// Implementation
}
Better Approach:
Encapsulate parameters in an object or use a builder pattern.
Refactored Code:
public class User {
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
private String address; // Getters, setters, and constructor
}
public void createUser(User user) {
// Implementation
}
Benefit:
- Improves readability and extensibility.
- Additional parameters can be added without changing the method signature.
Code smells are early indicators of deeper design issues that can impact maintainability and scalability. By identifying and addressing these smells, you improve code quality and reduce technical debt.
Using principles like DRY (Don't Repeat Yourself), SRP (Single Responsibility Principle), and modular design, you can build robust, clean, and efficient Java applications.
Have you encountered these code smells in your projects? Share your experiences and solutions in the comments below!
Top comments (0)