All we have ever worked with an application with legacy code. Refactoring is a technique that allows us to restructure and redesign our application code without changing its behavior so that it is more readable and easier to maintain. This presentation discusses the advantages and disadvantages of refactoring as well as some of the main techniques that apply during a refactoring.
5. "Refactoring is the process of changing a
software system in such a way that it
does not alter the external behavior of
the code yet improves its internal
structure"
Martin Fowler
16. Recipe
Ingredient
name 1:N
ingredients amount
food
nutritional_report
1:1
Food
HIGH
LOW
REGULAR
name
nutritional_code
17.
18. class Recipe
...
def nutritional_report
total_calories, nutritional_points = 0, 0
result = "Nutritional Report for #{name}n"
self.ingredients.each do |ingredient|
this_calories = 0
# add calories by ingredient
case ingredient.food.nutritional_code
when Food::HIGH
this_calories += 5
this_calories += (ingredient.amount - 2) * 1.5 if ingredient.amount > 2
when Food::LOW
this_calories += ingredient.amount * 3
when Food::REGULAR
this_calories += 1.5
this_calories += (ingredient.amount - 3) * 1.5 if ingredient.amount > 3
end
# add nutritional points
nutritional_points += 1
# add extra nutritional points for high food
if ingredient.food.nutritional_code == Food::HIGH && ingredient.amount > 1
nutritional_points += 1
end
# show figures for this rental
result += "t" + ingredient.food.name + "t" + this_calories.to_s + "n"
total_calories += this_calories
end
# add footer lines
result += "Total calories are #{total_calories}n"
result += "You earned #{nutritional_points} nutritional points"
result
end
end
21. describe Recipe do
let(:recipe) { Recipe.new("Lentils with chorizo") }
let(:chorizo) { Food.new('chorizo', Food::HIGH) }
let(:lentil) { Food.new('lentil', Food::LOW) }
let(:potatoe) { Food.new('potatoe', Food::REGULAR) }
it "has a name" do
recipe.name.should == "Lentils with chorizo"
end
describe "calories" do
it "without ingredients are 0"
it "with one regular ingredient are 1.5"
it "with one regular ingredient and amount > 3 are 3"
it "with one high ingredient are 5"
end
...
end
23. class Recipe
...
def nutritional_report
total_calories, nutritional_points = 0, 0
result = "Nutritional Report for #{name}n"
self.ingredients.each do |ingredient|
this_calories = 0
# add calories by ingredient
case ingredient.food.nutritional_code
when Food::HIGH
this_calories += 5
this_calories += (ingredient.amount - 2) * 1.5 if ingredient.amount > 2
when Food::LOW
this_calories += ingredient.amount * 3
when Food::REGULAR
this_calories += 1.5
this_calories += (ingredient.amount - 3) * 1.5 if ingredient.amount > 3
end
# add nutritional points
nutritional_points += 1
# add extra nutritional points for high food
if ingredient.food.nutritional_code == Food::HIGH && ingredient.amount > 1
nutritional_points += 1
end
# show figures for this rental
result += "t" + ingredient.food.name + "t" + this_calories.to_s + "n"
total_calories += this_calories
end
# add footer lines
result += "Total calories are #{total_calories}n"
result += "You earned #{nutritional_points} nutritional points"
result
end
end
26. class Recipe
...
def nutritional_report
total_calories, nutritional_points = 0, 0
result = "Nutritional Report for #{name}n"
self.ingredients.each do |ingredient|
this_calories = 0
# add calories by ingredient
case ingredient.food.nutritional_code
when Food::HIGH
this_calories += 5
this_calories += (ingredient.amount - 2) * 1.5 if ingredient.amount > 2
when Food::LOW
this_calories += ingredient.amount * 3
when Food::REGULAR
this_calories += 1.5
this_calories += (ingredient.amount - 3) * 1.5 if ingredient.amount > 3
end
# add nutritional points
nutritional_points += 1
# add extra nutritional points for high food
if ingredient.food.nutritional_code == Food::HIGH && ingredient.amount > 1
nutritional_points += 1
end
# show figures for this rental
result += "t" + ingredient.food.name + "t" + this_calories.to_s + "n"
total_calories += this_calories
end
# add footer lines
result += "Total calories are #{total_calories}n"
result += "You earned #{nutritional_points} nutritional points"
result
end
end
27. ...
# add calories by ingredient
case ingredient.food.nutritional_code
when Food::HIGH
this_calories += 5
this_calories += (ingredient.amount - 2) * 1.5 if ...
when Food::LOW
this_calories += ingredient.amount * 3
when Food::REGULAR
this_calories += 1.5
this_calories += (ingredient.amount - 3) * 1.5 if ...
end
...
29. class Recipe
...
def calories_for(ingredient)
case ingredient.food.nutritional_code
when Food::HIGH
this_calories += (ingredient.amount - 2) * 1.5 if ...
this_calories += 5
when Food::LOW
this_calories += ingredient.amount * 3
when Food::REGULAR
this_calories += (ingredient.amount - 3) * 1.5 if ...
this_calories += 1.5
end
end
end
30. def nutritional_report
total_calories, nutritional_points = 0, 0
result = "Nutritional Report for #{name}n"
self.ingredients.each do |ingredient|
this_calories = calories_for(ingredient)
# add nutritional points
nutritional_points += 1
# add extra nutritional points for high food
if ingredient.food.nutritional_code == Food::HIGH && ingredient.
amount > 1
nutritional_points += 1
end
# show figures for this rental
result += "t" + ingredient.food.name + "t" + this_calories.to_s +
"n"
total_calories += this_calories
end
# add footer lines
result += "Total calories are #{total_calories}n"
result += "You earned #{nutritional_points} nutritional points"
result
end
33. class Recipe
...
def calories_for(ingredient)
case ingredient.food.nutritional_code
when Food::HIGH
this_calories += (ingredient.amount - 2) * 1.5 if ...
this_calories += 5
when Food::LOW
this_calories += ingredient.amount * 3
when Food::REGULAR
this_calories += (ingredient.amount - 3) * 1.5 if ...
this_calories += 1.5
end
end
end
34. class Recipe
...
def calories_for(ingredient)
this_calories = 0
case ingredient.food.nutritional_code
when Food::HIGH
this_calories += (ingredient.amount - 2) * 1.5 if ...
this_calories += 5
when Food::LOW
this_calories += ingredient.amount * 3
when Food::REGULAR
this_calories += (ingredient.amount - 3) * 1.5 if ...
this_calories += 1.5
end
end
end
36. class Recipe
...
def calories_for(ingredient)
this_calories = 0
case ingredient.food.nutritional_code
when Food::HIGH
this_calories += (ingredient.amount - 2) * 1.5 if ...
this_calories += 5
when Food::LOW
this_calories += ingredient.amount * 3
when Food::REGULAR
this_calories += (ingredient.amount - 3) * 1.5 if ...
this_calories += 1.5
end
end
end
37. class Recipe
...
def calories_for(ingredient)
this_calories = 0
case ingredient.food.nutritional_code
when Food::HIGH
this_calories += (ingredient.amount - 2) * 1.5 if ...
this_calories += 5
when Food::LOW
this_calories += ingredient.amount * 3
when Food::REGULAR
this_calories += (ingredient.amount - 3) * 1.5 if ...
this_calories += 1.5
end
end
end
40. class Ingredient
...
def calories
this_calories = 0
case food.nutritional_code
when Food::HIGH
this_calories += 5
this_calories += (amount - 2) * 1.5 if amount > 2
when Food::LOW
this_calories += amount * 3
when Food::REGULAR
this_calories += 1.5
this_calories += (amount - 3) * 1.5 if amount > 3
end
end
end
41. class Recipe
def nutritional_report
total_calories, nutritional_points = 0, 0
result = "Nutritional Report for #{name}n"
self.ingredients.each do |ingredient|
# add nutritional points
nutritional_points += 1
# add extra nutritional points for high food
if ingredient.food.nutritional_code == Food::HIGH && ingredient.
amount > 1
nutritional_points += 1
end
# show figures for this rental
result += "t" + ingredient.food.name + "t"
result += ingredient.calories.to_s + "n"
total_calories += ingredient.calories
end
# add footer lines
result += "Total calories are #{total_calories}n"
result += "You earned #{nutritional_points} nutritional points"
result
end
end
42. describe Ingredient do
let(:chorizo) { Food.new('chorizo',Food::HIGH) }
let(:lentil) { Food.new('lentil', Food::LOW) }
let(:potatoe) { Food.new('potatoe', Food::REGULAR) }
describe 'calories' do
it "with one regular food are 1.5"
it "with one regular food and amount > 3 are 3"
it "with one high food are 5"
it "with one high food and amount > 2 are 6.5"
it "with one low food are 3"
end
end
44. class Recipe
def nutritional_report
total_calories, nutritional_points = 0, 0
result = "Nutritional Report for #{name}n"
self.ingredients.each do |ingredient|
# add nutritional points
nutritional_points += 1
# add extra nutritional points for high food
if ingredient.food.nutritional_code == Food::HIGH &&
ingredient.amount > 1
nutritional_points += 1
end
# show figures for this rental
result += "t" + ingredient.food.name + "t"
result += ingredient.calories.to_s + "n"
total_calories += ingredient.calories
end
# add footer lines
result += "Total calories are #{total_calories}n"
result += "You earned #{nutritional_points} nutritional points"
result
end
end
46. class Ingredient
...
def nutritional_points
if food.nutritional_code == Food::HIGH && amount > 1
2
else
1
end
end
end
47. class Recipe
...
def nutritional_report
total_calories, nutritional_points = 0, 0
result = "Nutritional Report for #{name}n"
self.ingredients.each do |ingredient|
nutritional_points += ingredient.nutritional_points
# show figures for this rental
result += "t" + ingredient.food.name + "t"
result += ingredient.calories.to_s + "n"
total_calories += ingredient.calories
end
# add footer lines
result += "Total calories are #{total_calories}n"
result += "You earned #{nutritional_points} nutritional point
result
end
end
48. describe Ingredient do
describe "nutritional points" do
it "is 2 if food is high and amount > 1"
it "is 1 if food is high and amount = 1"
it "is 1 if food is not high and amount = 1"
it "is 1 if food is not high and amount > 1"
end
end
50. class Recipe
def nutritional_report
total_calories, nutritional_points = 0, 0
result = "Nutritional Report for #{name}n"
self.ingredients.each do |ingredient|
nutritional_points += ingredient.nutritional_points
# show figures for this rental
result += "t" + ingredient.food.name + "t"
result += ingredient.calories.to_s + "n"
total_calories += ingredient.calories
end
# add footer lines
result += "Total calories are #{total_calories}n"
result += "You earned #{nutritional_points} nutritional point
result
end
end
52. class Recipe
...
def total_calories
ingredients.sum(:calories)
end
def total_nutritional_points
ingredients.sum(:nutritional_points)
end
end
53. class Recipe
def nutritional_report
result = "Nutritional Report for #{name}n"
ingredients.each do |ingredient|
# show figures for this rental
result += "t" + ingredient.food.name + "t"
result += ingredient.calories.to_s + "n"
end
# add footer lines
result += "Total calories are #{total_calories}n"
result += "You earned #{total_nutritional_points} nutritional
result
end
...
end
55. class Recipe
def nutritional_report
result = "Nutritional Report for #{name}n"
ingredients.each do |ingredient|
# show figures for this rental
result += "t" + ingredient.food.name + "t"
result += ingredient.calories.to_s + "n"
end
# add footer lines
result += "Total calories are #{total_calories}n"
result += "You earned #{total_nutritional_points} nutritional
result
end
...
end
56. HTML Report
class Recipe
def html_nutritional_report
result = "<h1>Nutritional Report for #{name}</h1>"
ingredients.each do |ingredient|
# show figures for this rental
result += "<p>#{ingredient.food.name} "
result += "{ingredient.calories}</p>"
end
# add footer lines
result += "<p>Total calories are #{total_calories}</p>"
result += "<p>You earned #{total_nutritional_points} "
result += "nutritional points</p>"
result
end
...
end
57. More Refactoring?
Replace Method
with Method Objet
Template Method Pattern
58. class NutritionalReport
def initialize(recipe)
@recipe = recipe
end
def output
head
body
foot
end
def head
...
def body
...
def line(ingredient)
...
def foot
...
end
59. class HTMLNutritionalReport < NutritionalReport
def head
"<h1>Nutritional Report for #{name}</h1>"
end
def line(ingredient)
"<p>#{ingredient.food.name} #{ingredient.calories}</p>"
end
def foot
result = "<p>Total calories are #{@recipe.total_calories}</p
result += "<p>You earned #{@recipe.total_nutritional_points}
nutritional points</p>"
result
end
end
64. class Food
def calories(amount)
this_calories = 0
case nutritional_code
when HIGH
this_calories += (amount - 2) * 1.5 if amount > 2
this_calories += 5
when LOW
this_calories += amount * 3
when REGULAR
this_calories += (amount - 3) * 1.5 if amount > 3
this_calories += 1.5
end
end
def nutritional_points(amount)
(nutritional_code == HIGH && amount > 1) ? 2 : 1
end
end
65. class Ingredient
def calories
food.calories(amount)
end
def nutritional_points
food.nutritional_points(amount)
end
end
66. describe Ingredient do
let(:chorizo) { Food.new('chorizo', Food::HIGH) }
let(:lentil) { Food.new('lentil', Food::LOW) }
let(:potatoe) { Food.new('potatoe', Food::REGULAR) }
describe 'calories' do
it "with one regular food are 1.5"
it "with one regular food and amount > 3 are 3"
it "with one high food are 5"
it "with one high food and amount > 2 are 6.5"
it "with one low food are 3"
end
describe "nutritional points" do
it "is 2 if food is high and amount > 1"
it "is 1 if food is high and amount = 1"
it "is 1 if food is not high and amount = 1"
it "is 1 if food is not high and amount > 1"
end
end
68. class Food
def calories(amount)
this_calories = 0
case nutritional_code
when HIGH
this_calories += (amount - 2) * 1.5 if amount > 2
this_calories += 5
when LOW
this_calories += amount * 3
when REGULAR
this_calories += (amount - 3) * 1.5 if amount > 3
this_calories += 1.5
end
end
def nutritional_points(amount)
(nutritional_code == HIGH && amount > 1) ? 2 : 1
end
end
69. Fix
Switch Statements
Replace Type Code with
State/Strategy
70. class Food
...
def nutritional_code=(value)
@nutritional_code = value
@nutritional_type = case @nutritional_code
when HIGH then HighNutritional.new
when LOW then LowNutritional.new
when REGULAR then RegularNutritional.new
end
end
def calories(amount)
@nutritional_type.calories(amount)
end
def nutritional_points(amount)
@nutritional_type.points(amount)
end
end
71. module DefaultNutritionalPoints
def points(amount)
1
end
end
class RegularNutritional
include DefaultNutritionalPoints
def calories(amount)
acum = 1.5
acum += (amount - 3) * 1.5 if amount > 3
acum
end
end
72. class LowNutritional
include DefaultNutritionalPoints
def calories(amount)
amount * 3
end
end
class HighNutritional
def calories(amount)
acum = 5
acum += (amount - 2) * 1.5 if amount > 2
acum
end
def points(amount)
amount > 1 ? 2 : 1
end
end
83. References
● Refactoring: Improving design of existing
code - Martin Fowler
● Refactoring to Patterns - Joshua Kerievsky
● Clean Code - Robert C. Martin
● Design Patterns in Ruby - Russ Olsen
● Source Making
http://sourcemaking.com/refactoring
84. Tools
● Reek - Code Smell Detector for ruby
https://github.com/troessner/reek
● Rails Best Practices
http://rails-bestpractices.com
● Code Climate
http://codeclimate.com
● Ruby Refactoring Tool for Vim
https://github.com/ecomba/vim-ruby-refactoring