In this series we're taking a look at how to refactor un-refactorable legacy code.
Part IPart IPart III
Part IVPart V

Part VI


Check out the original version of the code (Java, and also in C#, in tag 1.0).

Right, time to move some code around. Before we do that, remember step #1 in our process: Draw a line around our tested code. For us it’s mainly the “cook” method. That’s the heart of our code and the code we want to refactor.

The “cook” method is the entry point. Where does our system end? In our example, it is the private methods (which I didn’t add code to). Let’s take a look at them:

private Ingredient getIngredient(IngredientType ingredient, Place place) { 
    return new Ingredient(false); 
}
private Ingredient getPasta(PastaType pasta, Place place) { 
    return new Ingredient(true); 
}

private void fill(List<Ingredient> sauceIngredients) { }
private void prepare(Ingredient ingredient) { }
private void cookPasta() { }
private void addSauce() { }

There are two types of methods here. The first two methods go outside the class (or would if there was an implementation). In the real world, the may call another REST service, or send an async message. In the real world, that code can also be the combination of inlined logic, and external calls.

The last four are “regular” private methods. This is code we’ve extracted, that, in our example, stays in the class. Or call another class that’s still within the system boundaries.

In general, these are the two kinds of methods we’re going to deal with: The ones that call outside the boundary of our system, and those that stay within it. That is important, because if we want something outside, we want to establish a clear boundary, and we use *methods* to do it.

Now that we have a boundary, let’s push code outside it. And how do we do it? Introduce an interface You’ve probably seen that coming. Let’s call this interface dispenser. In our case it has two methods:

public interface Dispenser {
    Ingredient getIngredient(IngredientType ingredient, Place place);
    Ingredient getPasta(PastaType pasta, Place place);
}

Basically, we extract the code the code in the private method and move them beyond the interface, and whatever parameters we pass to the private method, are going to be parameters in the interface method. This is the kind of low-risk, and even possibly automated, refactoring for introducing interfaces and method extraction. If the code in the private method we’re moving accesses local or instance variables, we should pass them as well to the interface.

And it doesn’t have to be a single interface, like in this example. We can define two interfaces: IngredientDispenser and PastaDispenser. Whatever makes sense in terms of cohesion. The boundary we’re drawing can be temporary for our refactoring, but also can remain later because of separation of concerns, and a better design.

We need to introduce the interface somehow into the class. I decided to inject it through the constructor, but adding a setter works as well.

So here’s the constructor.

public PastaMaker(Dispenser dispenser) {
    this.dispenser = dispenser;
}

Again, this is a modification to the code, that is a low risk change.

But now we have a clear boundary, and we can use it later. The next step though is test case analysis.

The modified code is under tag 1.1 (Added Dispenser interface through constructor)

Categories: Refactoring

0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *