As I mentioned last week, I’m doing a series of posts on refactoring. As gSchool wraps up, I’m working on my refactoring skills, so that I’ll be a better developer when I get my first job. I’m trying some of the strategies outlined in the book Refactoring Ruby.
Today I’ll be trying out a strategy called Replace Method with Method Object. This is a good approach to take when you have a long method with many variables in it, and using Extract Method wouldn’t be practical. Essentially, it consists of removing the method from its class and turning it into a class of its own. From there, you can more easily refactor using Extract Method and other simple strategies.
Here are the steps as outlined in the book:
12345678910111213
1. Create a new class, name it after the method.
2. Give the new class an attribute for the object that hosted the original method (the source object) and an attribute for each temporary vairable and each parameter in the method.
3. Give the new class a constructor that takes the source object and each parameter.
4. Give the new class a method named "compute".
5. Copy the body of the original method into "compute". Use the source object instance variable for any invocations of methods on the original subject.
6. Test.
7. Replace the old method with one that creates the new method and call "compute".
Mancala
I took a look around my Github repositories, and found this little wonder from a Mancala app I wrote in Ruby Processing during the first month of gSchool.
To begin the refactoring process, I had to make sure the tests were in place first. This was fairly difficult, as this method is highly coupled to other methods throughout the app. For testing purposes, I added a couple of stubbed methods to KalahRules, and created dummy App and Model classes with stubbed methods of their own. Also, distribute_beads_for_player did not return anything meaningful, so I changed it to do return the value of @i.
In the end, here’s the code I used to make the test pass:
classBeadDistributor# Code omitted for brevitydefcomputenext_pit_id=app.model.find_next_pit_by_id(first_pit_id)@i=0while@i<=countdoexecute_store_logic(next_pit_id,player,count)breakif@i==countapp.model.add_bead_to_pit(next_pit_id)next_pit_id=app.model.find_next_pit_by_id(next_pit_id)if@i==count-1landing_spot=next_pit_id-1landing_spot=12iflanding_spot==0iflanded_on_empty?(landing_spot)&&app.model.find_all_pit_ids_on_players_side(player).include?(landing_spot)take_both_sides(landing_spot)breakendend@i+=1end@ienddefexecute_store_logic(pit_id,player,count)enddeftake_both_sides(spot)enddeflanded_on_empty?(spot)trueendend
Step 6: Test. I tested the newly created BeadDistributor class:
Running the tests one more time, I saw that everything was still green. Although I could have gone on to refactor some of the other methods in the original class that distribute_beads_for_player was coupled to, I decided to call it a day.
Conclusion
I enjoyed the Replace Method with Method Object strategy. It’s a pretty cool way to think about the microcosm/macrocosm relationship between methods and objects. Although it’s not probably useful for every refactoring case, I imagine it would come in really handy when cleaning up a hairy method like distribute_beads_for_player. I’m learning a lot from Refactoring Ruby, and I’m looking forward to seeing what other strategies it offers.