Long method
Refactoring

Long method

Introduction

A long method is a code smell. It is a well known fact that the longer a method is, the harder it is to understand. Smaller methods might be harder to follow because there are deep sequences of delegation. The key to make it easy to understand is not the method length but naming.

Symptoms

A long method is easy to spot. If you need to read the actual implementation of a method to understand it, you probably have a too long method. This may come as a shock if you are used to writing longer methods but is key to self documenting code.

Causes

Since it is easier to write code than to read it, something is always being added to a method but never taken out. A long method can start smelling when it already has grown into monstrous proportions.

Solutions

As a general rule of thumb whenever you feel like adding a comment to code you should take that code and make it a method. Some might argue that smaller methods are harder to follow. However, the key is to make method names descriptive enough so that you do not need to look at the code at all.

  • If you need to make a long method shorter, in majority of cases, just extract a method. Modern IDE tools make this very easy.
  • If there are local variables or parameters that prevent extracting a method, replace temp with query, introduce a parameter object, or preserve whole object.
  • If all of the above rules fail, try to replace entire method with a method object. This means moving the entire method into a separate object.
  • If there are conditional operators, you can try to decompose the conditional.

Extract method

void printOwing() {
  printBanner();

  // print details
  System.out.println("name: " + name);
  System.out.println("amount: " + getOutstanding());
}
void printOwing() {
  printBanner();
  printDetails(getOutstanding());
}

void printDetails(double outstanding) {
  System.out.println("name: " + name);
  System.out.println("amount: " + outstanding);
}

Replace temp with query

In this example keep in mind that the local variable is preventing us from using extract method. The goal here is to present how to make it possible.

double basePrice = quantity * itemPrice;
if (basePrice > 1000)
  return basePrice * 0.95;
else
  return basePrice * 0.98;
if (basePrice() > 1000)
  return basePrice() * 0.95;
else
  return basePrice() * 0.98;

double basePrice() {
  return quantity * itemPrice;
}

Replace method with method object

class Order {
  double price() {
    double primaryBasePrice;
    double secondaryBasePrice;
    double tertiaryBasePrice;
    // long computation;
    // ...
  }
}
class Order {
  public double price() {
    return new PriceCalculator(this).compute();
  }
}

class PriceCalculator {
  private double primaryBasePrice;
  private double secondaryBasePrice;
  private double tertiaryBasePrice;
  
  public PriceCalculator(Order order) {
    //...
  }
  
  public double compute() {
    // long computation.
    //...
  }
}

Decompose conditional

if (date.before(SUMMER_START) || date.after(SUMMER_END))
  charge = quantity * winterRate + winterServiceCharge;
else
  charge = quantity * summerRate;
if (notSummer(date))
  charge = winterCharge(quantity);
else
  charge = summerCharge (quantity);

Benefits

Simply put, your code will become much more readable. It is also possible that you can remove duplicate code hidden in a long method.

Summary

A long method is hard to understand. The key to make it easy to understand is not the method length but naming. You should not have to read the actual implementation to understand what a method does.

Methods grow large because it is easier to write code than to read it. Whenever you feel like adding a comment to code you should take that code and make it a method.

When you find a large method, you should refactor it. In most cases you can just extract a method. Sometimes a local variable or parameter prevents doing this. You can then try to replace temp with query, introduce a parameter object, or preserve whole object. If that fails, replace method with a method object.

4 thoughts on “Long method

  1. Jere Teittinen

    I’d like to add that often times it’s also worth writing a short JavaDoc for classes and methods, explaining briefly what it’s for and why. I often find it difficult to conceptualise and fully understand the code without first forming a mental image of what’s the context of the thing we’re currently processing. This comes down to the fact that explains are simple for the sake of making it easier to understand the concepts being discussed. But in real life we are usually modeling quite complex and context-dependent issues. When someone else modifies your code later on, it’s often difficult to get started without a good understanding about why your base price might have a tertiary component. Changing something might break something unexpected somewhere else.

    In your example the JavaDoc for PriceCalculator might explain why we are introducing a tertiaryBasePrice to the equation. Do we invoice corporate customers more heavily than regular customers?

    But I agree that chopping your methods into smaller snippets makes the code more understandable and nicer to work with. But is there a significant difference in performance between long and modular methods? I mean classes and methods come with additional metadata and method call overhead. It’s probably completely negligible on languages like Java?

    As a side note that’s not really related to this article – I wonder how well approaches like this work in interpreted languages such as Python where the overhead of method calls is quite high.

    • I completely agree on writing JavaDocs, especially for classes and public interfaces. Code readability could be a broad enough topic for a post of its own.

      As for performance, the hit is so negligible that you should not think about it. By making the code cleaner and more modular you probably end up with faster solutions anyway. Thinking about the method call overhead is just micro-optimization; in most cases even with interpreted languages.

  2. Marko Miettinen

    Interesting reading, this is.

    How about TDD in connection with long monster methods and their refactoring? Terrifyingly often these long methods are untested, and understanding, let alone testing them can seem a bit overwhelming..Do you have some specific techniques to recommend when fixing long legacy methods with TDD?

    • I can’t say that I would have figured out a robust method for this. Chances are that a monster method is telling us that the class should also be split into smaller classes. The biggest problem in getting the code into a test harness is probably going to be both internal and external dependencies.

      The first thing to try could be extracting methods and seeing if you can find repeating patterns that way. You can probably try to do TDD by figuring out parts that you would like to extract, write tests for those parts, and then extract the parts to make the test pass.

      I have only read bits and parts of the book but a good reference for this is: Working Effectively with Legacy Code by Michael C. Feathers.

Leave a Reply