Many IDEs provide automated refactorings that can speed up development. Features like smart renaming and inlining or extracting variables and methods are really nice. There’s one refactoring that very few IDEs provide directly, but that I find particularly useful: Extract Method to Component.

So far, the only place where I’ve seen this refactoring is in VisualWorks Smalltalk. It’s really a combination of ExtractMethod and MoveMethod, but performing those in a single operation is really handy.

I’ll walk through an example of how it works, and maybe it’ll become your favorite refactoring too.

Before

I’m currently working on a project to write a Smalltalk implementation of a very involved communication specification. Part of the specification involves writing DataValues to Nodes. We started out only supporting scalar values, but recently needed to add support for arrays.

Before writing a value, there are a number of checks that need to be performed, including a check that the type of the DataValue is appropriate for the Node being written.

Here’s the basic write method I started with (note that attribute and writeBlock are instance variables of the class where this is defined, and that the Node is represented by the aUANodeRole parameter):

Before: write:with:
write: aUANodeRole with: aDataValue
aDataValue hasTimestamps ifTrue: [^StatusCode writeNotSupported].
aDataValue status = StatusCode good
ifFalse: [^StatusCode writeNotSupported].
(aUANodeRole canWriteAttribute: attribute)
ifFalse: [^StatusCode writeNotSupported].
(aDataValue value isKindOf: aUANodeRole dataType)
ifFalse: [^StatusCode typeMismatch].
writeBlock value: aUANodeRole value: aDataValue value.
^StatusCode good

As you can see, this method is already quite involved. In order to support arrays, I need to expand on the isKindOf: check in the middle of the method.

The Node knows what type of values it accepts, including whether or not it’s expecting an array. It makes more sense for the Node to decide whether or not the DataValue’s value is appropriate or not.

I’d like to move that isKindOf: test over to the Node.

I could do this manually, but VisualWorks provides the Extract Method to Component refactoring, so I’ll use that instead.

The Process

Note that I’m using a fairly old version of VisualWorks Smalltalk here (7.10.1). In newer versions, there have been a lot of improvements to the development tools, so they look and feel a lot more modern than what you see here.

I start by highlighting the code I want to extract.

'Highlight Code to Extract'

I right-click and choose Refactor -> Extract Method to Component.

'Choose the Refactoring'

It asks me which variable I want to extract to. I choose aUANodeRole.

'Choose the Variable'

It then tries to figure out which class the variable is an instance of. Because Smalltalk is a dynamically-typed language, there can be a lot of options in the list, as there is here.

'Choose the Class'

In my case, I know that only UAVariables can have array values, so I choose that class. Note that I can extract to either the instance side or the class side; in this case, I want the instance side.

If the code I am extracting needs to refer back to the object I’m extracting from, it will ask me what I want to call the back reference. In this case, I don’t have a back reference, so I don’t see that step.

Next, I need to provide the name of my new method. I call it canAccept:.

'Enter Method Name'

The refactoring is now done!

The Result

UAVariable now has a new canAccept: method:

After: UAVariable>>canAccept:
canAccept: aDataValue
^aDataValue value isKindOf: self dataType

Note that it was smart enough to replace aUANodeRole in the original code with self.

The original write:with: now looks like this:

After: write:with:
write: aUANodeRole with: aDataValue
aDataValue hasTimestamps ifTrue: [^StatusCode writeNotSupported].
aDataValue status = StatusCode good
ifFalse: [^StatusCode writeNotSupported].
(aUANodeRole canWriteAttribute: attribute)
ifFalse: [^StatusCode writeNotSupported].
(aUANodeRole canAccept: aDataValue)
ifFalse: [^StatusCode typeMismatch].
writeBlock value: aUANodeRole value: aDataValue value.
^StatusCode good

As you can see, the isKindOf: check has been replaced with a call to the new method: aUANodeRole canAccept: aDataValue.

Conclusion

Extract Method to Component is a powerful refactoring, especially when you find a case of Feature Envy in your code.

The VisualWorks implementation can handle the case where the new method needs to refer back to the original object.

If the code being extracted has any instance variable references, it will offer to create getter methods so that the extracted code can still access those variables from the new location.

There are complicated cases that this refactoring can’t handle, but it works quite often.

It’s one of my favorite tools to use when working in Smalltalk. I wish that other language IDEs supported it more directly.