Refactoring: Forcing the Right Extract Method

Automated refactoring tools are great, but sometimes the tool doesn’t make the best choice. We can often nudge the tool to a better choice.

Why Bother?

Why bother nudging? Couldn’t you do the refactoring manually just as quickly? You could do it manually, but I prefer to lean on the tool. The tool is more likely to get it right, independent of my mood and hunger level.

Working manually isn’t that fast as people expect: by the time you create the boilerplate and fix any typos, you’ve come out well behind.

Extract Method Speaks

A refactoring tool analyzes the data flow of the code it extracts. This influences the parameters it chooses. Watch what’s chosen: Does it pass values you didn’t expect? Does it not pass values you did expect?

These are clues. When a new method doesn’t pass arguments, it’s telling you, “I depend on fields and/or global variables.” Is that what you want?

The initial Extract Method isn’t everything – you may be able to adjust the method after extraction, e.g., with an Introduce Parameter refactoring. But don’t ignore what the refactorings are trying to tell you!

Fixing a Constructor

Minesweeper game, after a few turns
Minesweeper game

One of these cases occurs often in constructors, especially when a new type wants to emerge.

Example: Perhaps you’ve seen the minesweeper game. The game begins with the computer hiding some mines randomly on a grid.

We can imagine constructor pseudocode something like this, hiding 10 mines on an 8×8 grid:

class Game {
var board : [Int]
: // more fields

constructor() {
board = Array(repeating: 0, count: 63)
var randomPositions = generatePositions(10, 0...63)
randomPositions.forEach { mine in
board[mine] = -1
}
0...63.forEach { position in
board[position] = countMines(board, position)
}
: // more initialization
}
:
}

Extract Method

Complicated setup suggests that you may be missing a type.

If you extract the code shown, the tool probably suggests a method with no arguments or return values. That improves the readability, but relies on the mid-level side effect of updating a field.

Instead, I’d rather have a method that returned the created board, called something like this:

    board = createBoard(cells: 64, mines: 10)

This new method should be standalone – no fields involved, only parameter values.

Complicated setup suggests you may be missing a type.

A Manual Approach

Suppose board is a field, not a parameter or local variable. (If board is a variable or parameter, rename it to get it out of the way.) Make this modification at the start of the sequence of statements:

    board = ....     
=> 
    var board = ...

Add this line to the end:

    self.board = board

Now, select the code from “var board” through the end of the original code (not the new assignment), and extract the method. You should now be able to have the desired assignment “board = createBoard(...)“, perhaps needing to Inline Variable to get it.

Side note: “self.” or “this.”

Some code styles require that every field reference be prefixed with “self.” (or “this.“). I used to be in the camp of “I wouldn’t bother, but I’ll go along with the house style.”

Having used this refactoring a few times, I find that style guideline to be an active hindrance.

If your code uses the “self.board” style, you’ll need to remove the “self.” references before applying the refactoring.

To go a little further, codes styles that use different naming standards for fields, constants, local variables, etc. are making the code less resilient to change.

You Can’t Always Get What You Want

I can’t offer you a magic formula that will always bend the refactoring tool to your will. For example, I had code something like this:

    x = array[index1].at(index2)

I really wanted to call it like this:

    x = fn(array, index1, index2)

I don’t say it’s impossible to make the tool extract how I want, but I can say I didn’t do it in my first three tries.

Conclusion

It’s easy to let the tool drive the decision about a refactoring, but sometimes we can influence it to go where we want. We saw a simple technique that used a local variable to override a field name. Other situations may need other approaches. That’s OK; sometimes we need small techniques to boost the power tools.

Further Reading

“Refactoring” [tag] – https://xp123.com/articles/tag/refactoring/