Ruby Science
Move Method
Moving methods is generally easy. Moving a method allows you to place a method closer to the state it uses by moving it to the class that owns the related state.
To move a method:
- Move the entire method definition and body into the new class.
- Change any parameters that are part of the state of the new class to simply reference the instance variables or methods.
- Introduce any necessary parameters because of state which belongs to the old class.
- Rename the method if the new name no longer makes sense in the new
context (for example, rename
invite_usertoinviteonce the method is moved to theUserclass). - Replace calls to the old method to calls to the new method. This may require introducing delegation or building an instance of the new class.
Uses
- Removes feature envy by moving a method to the class where the envied methods live.
- Makes private, parameterized methods easier to reuse by moving them to public, unparameterized methods.
- Improves readability by keeping methods close to the other methods they use.
Let’s take a look at an example method that suffers from feature envy and use extract method and move method to improve it:
# app/models/completion.rb
def score
answers.inject(0) do |result, answer|
question = answer.question
result + question.score(answer.text)
end
endThe block in this method suffers from feature envy: It references
answer more than it references methods or instance
variables from its own class. We can’t move the entire method; we only
want to move the block, so let’s first extract a method:
# app/models/completion.rb
def score
answers.inject(0) do |result, answer|
result + score_for_answer(answer)
end
end# app/models/completion.rb
def score_for_answer(answer)
question = answer.question
question.score(answer.text)
endThe score method no longer suffers from feature envy, and the new
score_for_answer method is easy to move, because it only
references its own state. See the Extract Method chapter for
details on the mechanics and properties of this refactoring.
Now that the feature envy is isolated, let’s resolve it by moving the method:
# app/models/completion.rb
def score
answers.inject(0) do |result, answer|
result + answer.score
end
end# app/models/answer.rb
def score
question.score(text)
endThe newly extracted and moved Question#score method no
longer suffers from feature
envy. It’s easier to reuse, because the logic is freed from the
internal block in Completion#score. It’s also available to
other classes because it’s no longer a private method. Both methods are
also easier to follow because the methods they invoke are close to the
methods they depend on.
Dangerous: Move and Extract at the Same Time
It’s tempting to do everything as one change: create a new method in
Answer, move the code over from Completion and
change Completion#score to call the new method. Although
this frequently works without a hitch, with practice, you can perform
the two, smaller refactorings just as quickly as the single, larger
refactoring. By breaking the refactoring into two steps, you reduce the
duration of “down time” for your code; that is, you reduce the amount of
time during which something is broken. Improving code in smaller steps
makes it easier to debug when something goes wrong and prevents you from
writing more code than you need to. Because the code still works after
each step, you can simply stop whenever you’re happy with the
results.
Next Steps
- Make sure the new method doesn’t suffer from feature envy because of state it used from its original class. If it does, try splitting the method up and moving part of it back.
- Check the class of the new method to make sure it’s not a large class.
Ruby Science
The canonical reference for writing fantastic Rails applications from authors who have created hundreds.