Skip to main content Link Menu Expand (external link) Document Search Copy Copied

refactoring-journey

Table of contents
  1. Extract Method
  2. Extract variable
  3. Inline Temp
  4. Remove Assignments to Parameters
  5. Replace long method with Method Object (composition)

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 in composing.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 in composing.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;
      }
      

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 in composing.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 in composing.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 in composing.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 in composing.methods package
  • Extract the content of the generateStockReport method in a StockReportGenerator 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... refactoring-journey
  • More info here

Benefits

  • Stop a method from growing

Drawbacks

  • Another class is added
  • Increase the overall complexity of the program

Journey proposed by Yoan Thirion #sharingiscaring