85
Making tastier code through Refactoring

Making tastier code through refactoring

Embed Size (px)

DESCRIPTION

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.

Citation preview

Page 1: Making tastier code through refactoring

Making tastier code through

Refactoring

Page 2: Making tastier code through refactoring

Person.new( name: 'Gabriel Ortuño',

job: 'ASPgems',

web: 'arctarus.com',

pet_project: 'rezets.com',

github: 'arctarus',

twitter: 'arctarus'

)

Page 3: Making tastier code through refactoring

1. Introduction

2. Sample

3. Conclusions

Page 4: Making tastier code through refactoring

Refactoring?

Page 5: Making tastier code through refactoring

"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

Page 6: Making tastier code through refactoring
Page 7: Making tastier code through refactoring

Code Smells

Page 8: Making tastier code through refactoring

Refactoring Toolbox

Page 9: Making tastier code through refactoring

Why?

Page 10: Making tastier code through refactoring

Green Field

Page 11: Making tastier code through refactoring

Legacy Code

Page 12: Making tastier code through refactoring

When?

Page 13: Making tastier code through refactoring
Page 14: Making tastier code through refactoring

1. Introduction

2. Sample

3. Conclusions

Page 15: Making tastier code through refactoring

New TaskPrint nutritional report in HTML

Page 16: Making tastier code through refactoring

Recipe

nameingredients

nutritional_report

Ingredient

amountfood

Food

namenutritional_code

HIGHLOWREGULAR

1:N

1:1

Page 17: Making tastier code through refactoring
Page 18: Making tastier code through refactoring

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

Page 19: Making tastier code through refactoring

Whyyyy?

Page 20: Making tastier code through refactoring

1º Build a solid set of tests

Page 21: Making tastier code through refactoring

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

Page 22: Making tastier code through refactoring

$ rspec spec

..............

Finished in 0.00742 seconds

14 examples, 0 failures

Page 23: Making tastier code through refactoring

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

Page 24: Making tastier code through refactoring

Long Method

Page 25: Making tastier code through refactoring

Comments

Page 26: Making tastier code through refactoring

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

Page 27: Making tastier code through refactoring

...

# add calories by ingredientcase ingredient.food.nutritional_codewhen Food::HIGH this_calories += 5 this_calories += (ingredient.amount - 2) * 1.5 if ...when Food::LOW this_calories += ingredient.amount * 3when Food::REGULAR this_calories += 1.5 this_calories += (ingredient.amount - 3) * 1.5 if ...end...

Page 28: Making tastier code through refactoring

Extract Method

Page 29: Making tastier code through refactoring

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 endend

Page 30: Making tastier code through refactoring

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" resultend

Page 31: Making tastier code through refactoring

$ rspec spec

.FFFFFFFFFFFFF

Finished in 0.00742 seconds

14 examples, 13 failures

Page 32: Making tastier code through refactoring
Page 33: Making tastier code through refactoring

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 endend

Page 34: Making tastier code through refactoring

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 endend

Page 35: Making tastier code through refactoring

$ rspec spec

..............

Finished in 0.00742 seconds

14 examples, 0 failures

Page 36: Making tastier code through refactoring

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 endend

Page 37: Making tastier code through refactoring

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 endend

Page 38: Making tastier code through refactoring

Feature Envy

Page 39: Making tastier code through refactoring

Move Method

Page 40: Making tastier code through refactoring

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 endend

Page 41: Making tastier code through refactoring

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 endend

Page 42: Making tastier code through refactoring

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" endend

Page 43: Making tastier code through refactoring

$ rspec spec

...................

Finished in 0.00588 seconds

19 examples, 0 failures

Page 44: Making tastier code through refactoring

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 endend

Page 45: Making tastier code through refactoring

Remove

Feature Envy

with

Extract Method

Page 46: Making tastier code through refactoring

class Ingredient ... def nutritional_points if food.nutritional_code == Food::HIGH && amount > 1 2 else 1 end endend

Page 47: Making tastier code through refactoring

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 points" result endend

Page 48: Making tastier code through refactoring

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" endend

Page 49: Making tastier code through refactoring

$ rspec spec

.......................

Finished in 0.00588 seconds

23 examples, 0 failures

Page 50: Making tastier code through refactoring

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 points" result endend

Page 51: Making tastier code through refactoring

Replace Temp with Query

Page 52: Making tastier code through refactoring

class Recipe

...

def total_calories ingredients.sum(:calories) end

def total_nutritional_points ingredients.sum(:nutritional_points) endend

Page 53: Making tastier code through refactoring

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 points" result end

...

end

Page 54: Making tastier code through refactoring

$ rspec spec

.......................

Finished in 0.00621 seconds

23 examples, 0 failures

Page 55: Making tastier code through refactoring

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 points" result end

...end

Page 56: Making tastier code through refactoring

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

HTML Report

Page 57: Making tastier code through refactoring

More Refactoring?

Replace Method with Method Objet

Template Method Pattern

Page 58: Making tastier code through refactoring

class NutritionalReport

def initialize(recipe) @recipe = recipe end

def output head body foot end

def head ... def body ... def line(ingredient) ... def foot ... end

Page 59: Making tastier code through refactoring

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 endend

Page 60: Making tastier code through refactoring

WIN!

Page 61: Making tastier code through refactoring

class Ingredient ... def calories this_calories = 0 case food.nutritional_code when Food::HIGH this_calories += (amount - 2) * 1.5 if amount > 2 this_calories += 5 when Food::LOW this_calories += amount * 3 when Food::REGULAR this_calories += (amount - 3) * 1.5 if amount > 3 this_calories += 1.5 end end

def nutritional_points (food.nutritional_code == Food::HIGH && amount > 1) ? 2 : 1 end ...end

Page 62: Making tastier code through refactoring

I notice a weird smell...

Could it be envy?

Page 63: Making tastier code through refactoring

Feature Envy

Move Method

Get rid of

with

Page 64: Making tastier code through refactoring

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 endend

Page 65: Making tastier code through refactoring

class Ingredient def calories food.calories(amount) end

def nutritional_points food.nutritional_points(amount) endend

Page 66: Making tastier code through refactoring

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" endend

Page 67: Making tastier code through refactoring

$ rspec spec/

................................

Finished in 0.00865 seconds

32 examples, 0 failures

Page 68: Making tastier code through refactoring

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

Page 69: Making tastier code through refactoring

Replace Type Code with State/Strategy

Switch StatementsFix

Page 70: Making tastier code through refactoring

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) endend

Page 71: Making tastier code through refactoring

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 endend

Page 72: Making tastier code through refactoring

class LowNutritional

include DefaultNutritionalPoints

def calories(amount) amount * 3 endend

class HighNutritional def calories(amount) acum = 5 acum += (amount - 2) * 1.5 if amount > 2 acum end

def points(amount) amount > 1 ? 2 : 1 endend

Page 73: Making tastier code through refactoring

EPIC WIN!

Page 74: Making tastier code through refactoring

1. Introduction

2. Sample

3. Conclusions

Page 75: Making tastier code through refactoring

No Silver Bullet

Page 76: Making tastier code through refactoring

Improve Design

Page 77: Making tastier code through refactoring

Helps find bugs

Page 78: Making tastier code through refactoring

Program faster

Page 79: Making tastier code through refactoring

Good programmers writecode that humans can

understand

Page 80: Making tastier code through refactoring

Refactor to Win

Page 81: Making tastier code through refactoring

¡Thanks!

Page 82: Making tastier code through refactoring

Questions?

Page 83: Making tastier code through refactoring

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

Page 84: Making tastier code through refactoring

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

Page 85: Making tastier code through refactoring

Thanks!