- Shift Elevate
- Posts
- Feature Envy: Move Method Refactoring | Clean Code
Feature Envy: Move Method Refactoring | Clean Code
Feature Envy is a code smell where a method is more interested in another class's data than its own.
We will see how we can refactor using the Move Method technique.
Clean Code Reference
⚠️ Code Smell: Feature Envy
✅ Refactoring: Move Method
🎯 Goal: Methods belong with the data they operate on
The Feature Envy code smell occurs when a method in one class uses more data from another class than from its own class. The Move Method refactoring technique moves the method to the class that contains the data it's most interested in, improving encapsulation and reducing coupling.
The Code Smell: Feature Envy
Feature Envy is a subtle but important code smell that violates the principle of encapsulation. When a method spends more time accessing data from another class than from its own class, it indicates that the method might belong in the other class. Keeping the method in the wrong class creates tight coupling and makes the code harder to maintain.
Symptoms | Impact |
---|---|
Method calls more getters from another class | Violates encapsulation |
Method uses more data from another object | Creates tight coupling |
Method seems to belong in another class | Reduces code maintainability |
Here's a typical example of Feature Envy:
public enum CarType {
STANDARD, LUXURY, ECONOMY, SUV
}
public class RentalCalculator {
private static final double LOYALTY_DISCOUNT_RATE = 0.9;
private static final double CAR_AGE_DISCOUNT_RATE = 0.95;
private static final double LUXURY_CAR_SURCHARGE_RATE = 1.25;
private static final int LOYALTY_THRESHOLD_YEARS = 2;
private static final int CAR_AGE_THRESHOLD_YEARS = 5;
public double calculateRentalCost(Rental rental) {
double baseCost = 0.0;
// Feature Envy: This method is more interested in Car's data
Car car = rental.getCar();
double dailyRate = car.getDailyRate();
CarType carType = car.getType();
int carAge = car.getAge();
// Feature Envy: This method is more interested in RentalCustomer's data
RentalCustomer customer = rental.getCustomer();
int loyaltyYears = customer.getLoyaltyYears();
// Calculate base cost
int rentalDays = rental.getDays();
baseCost = dailyRate * rentalDays;
// Apply car-specific adjustments
if (carAge > CAR_AGE_THRESHOLD_YEARS) {
baseCost *= CAR_AGE_DISCOUNT_RATE; // 5% discount for older cars
}
if (CarType.LUXURY.equals(carType)) {
baseCost *= LUXURY_CAR_SURCHARGE_RATE; // 25% surcharge for luxury cars
}
// Apply rental customer-specific discounts
if (loyaltyYears > LOYALTY_THRESHOLD_YEARS) {
baseCost *= LOYALTY_DISCOUNT_RATE; // 10% loyalty discount
}
return baseCost;
}
}
public class Car {
private CarType type;
private double dailyRate;
private int age;
public double getDailyRate() { return dailyRate; }
public CarType getType() { return type; }
public int getAge() { return age; }
}
public class RentalCustomer {
private String name;
private int loyaltyYears;
public int getLoyaltyYears() { return loyaltyYears; }
}
In this example, the calculateRentalCost method is doing more work with Car and RentalCustomer data than with its own RentalCalculator data. This creates Feature Envy because the method seems to belong more to the Rental class where all this data is accessible.
The Refactoring: Move Method
The Move Method refactoring technique moves methods to the classes that contain the data they operate on most frequently. This improves encapsulation, reduces coupling, and makes the code more maintainable.
Step by Step Refactoring Process:
Identify the Feature Envy by looking for methods that use more data from another class.
Determine the target class where the method should be moved.
Move the method to the target class, adjusting parameters as needed.
Update the original method to delegate to the moved method.
Test the refactored code to ensure behavior remains the same.
Here's the refactored version:
public enum CarType {
STANDARD, LUXURY, ECONOMY, SUV
}
public class RentalCalculator {
public double calculateRentalCost(Rental rental) {
return rental.calculateTotalCost();
}
}
public class Rental {
private Car car;
private RentalCustomer customer;
private int days;
public double calculateTotalCost() {
double baseCost = car.calculateDailyCost() * days;
return customer.applyDiscounts(baseCost);
}
public Car getCar() { return car; }
public RentalCustomer getCustomer() { return customer; }
public int getDays() { return days; }
}
public class Car {
private static final double CAR_AGE_DISCOUNT_RATE = 0.95;
private static final double LUXURY_CAR_SURCHARGE_RATE = 1.25;
private static final int CAR_AGE_THRESHOLD_YEARS = 5;
private CarType type;
private double dailyRate;
private int age;
public double calculateDailyCost() {
double cost = dailyRate;
// Apply car-specific adjustments
if (age > CAR_AGE_THRESHOLD_YEARS) {
cost *= CAR_AGE_DISCOUNT_RATE; // 5% discount for older cars
}
if (CarType.LUXURY.equals(type)) {
cost *= LUXURY_CAR_SURCHARGE_RATE; // 25% surcharge for luxury cars
}
return cost;
}
public double getDailyRate() { return dailyRate; }
public CarType getType() { return type; }
public int getAge() { return age; }
}
public class RentalCustomer {
private static final double LOYALTY_DISCOUNT_RATE = 0.9;
private static final int LOYALTY_THRESHOLD_YEARS = 2;
private String name;
private int loyaltyYears;
public double applyDiscounts(double cost) {
if (loyaltyYears > LOYALTY_THRESHOLD_YEARS) {
return cost * LOYALTY_DISCOUNT_RATE; // 10% loyalty discount
}
return cost;
}
public int getLoyaltyYears() { return loyaltyYears; }
}
Benefits of Move Method
Benefit | Description |
---|---|
Improved Encapsulation | Methods are now closer to the data they operate on, reducing the need for getter methods and improving data hiding. |
Reduced Coupling | Classes are less dependent on each other's internal structure, making the code more flexible and easier to modify. |
Better Maintainability | Related functionality is grouped together, making it easier to understand and modify the code. |
When to Apply Move Method Refactoring
Methods that use more data from another class than from their own class.
Methods that seem to belong more to another class based on their behavior.
When you notice repeated getter calls to another object within a method.
When methods violate the principle of encapsulation.
When you want to improve the overall design and reduce coupling.
Apply Move Method refactoring to one Feature Envy in your current project today. Start with the method that has the most obvious data dependency on another class.
Repository & Resources
Complete Code Examples: Clean Code Repository
Find the complete implementation of Feature Envy refactoring and other clean code techniques in our dedicated repository. Each example includes:
Before and after code comparisons
Unit tests demonstrating the improvements
Found this helpful? Share it with a colleague who's struggling with Feature Envy. Have questions about refactoring method placement in your codebase? Email us directly, we read every message and the best questions become future newsletter topics.