Table of contents
Extract Method
Code Smells
- Too many responsibilities in a single method
- Method that breaks the single responsibility principle
- Long methods
- Comments on part of the method
And
in the method name
Technique
- Extract the new method by using your IDE feature
- Makes its purpose self-evident
- Be careful with the needed variables before extracting otherwise you will have to move them by yourself
Practice 1
- Open
Order
incomposing.methods
package - Extract methods to improve readability / reduce complexity
- Comments here can help you split your code
@AllArgsConstructor
@Getter
@Builder
public class Order {
private final Customer customer;
private final ArrayList<Product> products;
public String generateStatement() {
if (customer != null && !customer.getName().isEmpty() && products.size() > 0) {
StringBuilder statement = new StringBuilder();
//Add banner
statement.append("Statement for : " + customer + "%n");
for (Product p : products) {
// Add details.
statement.append("Product: " + p.getName() + " Price: " + p.getPrice() + "%n");
}
double total = AmountCalculator.calculatePrice(this, true, customer.getAge());
statement.append("Total: " + total + "€");
return statement.toString();
} else throw new IllegalArgumentException("InvalidOrder");
}
public Double totalPrice() {
return getProducts().stream().map(Product::getPrice).reduce(0.0, Double::sum);
}
}
Practice 2
- Open
AmountCalculator
incomposing.methods
package - Extract methods to remove code duplication
The power of your IDE you will use
- Start by trying to extract this piece of code :
double discountBasedOnAge = 0; if (age <= 16) { discountBasedOnAge = 0.35 * result; } else if (age >= 60) { discountBasedOnAge = 0.2 * result; }
- Start by trying to extract this piece of code :
Shortcuts
Extract method :
IntelliJ | Eclipse |
---|---|
Ctrl+Alt+M | Alt+Shift+M |
⌘+⌥+M | ⌥+⌘+M |
Benefits
- More readable code
- Avoid code duplication
Extract variable
Code Smells
- Hard to understand expressions
Technique
- Place the result of the expression or its parts in separate variables that are self-explanatory.
Practice
- Open
Food
incomposing.methods
package - Extract variables from the
isEdible
method
@Builder
public class Food {
private final LocalDate expirationDate;
private final Boolean approvedForConsumption;
private final Integer inspectorId;
public Food(LocalDate expirationDate, Boolean approvedForConsumption, Integer inspectorId) {
this.expirationDate = expirationDate;
this.approvedForConsumption = approvedForConsumption;
this.inspectorId = inspectorId;
}
public boolean isEdible() {
if (this.expirationDate.isAfter(LocalDate.now()) && this.approvedForConsumption == true && this.inspectorId != null) {
return true;
} else {
return false;
}
}
}
Shortcuts
Extract Variable :
IntelliJ | Eclipse |
---|---|
Ctrl+Alt+V | Alt+Shift+L |
⌘+⌥+V | ⌥+⌘+L |
Benefits
More readable code
Drawbacks
- More variables
- Performance
- Expressions will always be called
Inline Temp
Code Smells
- Temporary variable assigning the result of a simple expression
- And nothing more
Technique
- Replace the references to the variable with the expression itself
Practice 1
- Open
OrderHelper
incomposing.methods
package - Replace the price reference with the expression itself
Practice 2
- Refactor the
deserveDiscountBasedOnCustomer
by using previous learnings
public class OrderHelper {
private static final int MINIMUM_ITEMS_IN_STOCK = 10;
public static boolean deserveDiscount(Order order) {
double price = order.totalPrice();
return price > 1000;
}
// Customer deserves a discount if customer age / number of products < 5
public static boolean deserveDiscountBasedOnCustomer(Order order) {
double nbOfProducts = order.getProducts().size();
int customerAge = order.getCustomer().getAge();
int result = (int) (customerAge / nbOfProducts);
return result < 5;
}
public static int calculateNewStock(Stock stock, int outFromStock) {
stock.setNbOfItems(stock.getNbOfItems() - outFromStock);
if (stock.getNbOfItems() < MINIMUM_ITEMS_IN_STOCK) {
return stock.getNbOfItems() + MINIMUM_ITEMS_IN_STOCK;
}
return stock.getNbOfItems();
}
}
Faker library (or alternatives) can really help you save a lot of time when needing data for your tests.
Benefits
- Less code
- Less noise
Remove Assignments to Parameters
Code Smells
- A value is assigned to a parameter inside method’s body
Technique
- Use a local variable instead of a parameter.
Practice 1
- Open
OrderHelper
incomposing.methods
package - Reflect on the method
- Its signature
- Which concept does it break ?
- Add a new test for the
calculateNewStock
method
Practice 2
- Refactor the
calculateNewStock
into a Pure Function
Benefits
- Avoid side effects
- Create pure functions
- Easier to maintain / test
Replace long method with Method Object (composition)
Code Smells
- Long methods : local variables are so intertwined that you can’t apply Extract Method
- Break often Single Responsibility Principle
Technique
- Transform the method into a separate class
- Local variables become fields of the class
- Split the method into several methods within the same class
Practice
- Open
Warehouse
incomposing.methods
package - Extract the content of the
generateStockReport
method in aStockReportGenerator
class - Inject the Warehouse instance to it
- Refactor the code until you are happy with it
- What are the side effects on the consumers of the Warehouse class ?
@AllArgsConstructor
public class Warehouse {
private final int id;
private final LinkedHashMap<Product, Integer> stock;
public String generateStockReport() {
StringBuilder report = new StringBuilder();
report.append("Report for warehouse : " + id + "%n");
stock.forEach((key, value) -> report.append("Product: " + key.getName() + " Price: " + key.getPrice() + " Stock : " + value + " units%n"));
report.append("Total: " + stock.entrySet().stream().map(kvp -> kvp.getKey().getPrice() * kvp.getValue()).reduce(0.0, Double::sum) + "€");
return report.toString();
}
}
Shortcuts
- With IntelliJ select the content of the
generateStockReport
method - Go into your Refactor menu :
Refactor | Replace Method with Method Object...
- More info here
Benefits
- Stop a method from growing
Drawbacks
- Another class is added
- Increase the overall complexity of the program