Slide 1

Slide 1 text

Making tastier code through Refactoring

Slide 2

Slide 2 text

Person.new( name: 'Gabriel Ortuño', job: 'ASPgems', web: 'arctarus.com', pet_project: 'rezets.com', github: 'arctarus', twitter: 'arctarus' )

Slide 3

Slide 3 text

1. Introduction 2. Sample 3. Conclusions

Slide 4

Slide 4 text

Refactoring?

Slide 5

Slide 5 text

"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

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

Code Smells

Slide 8

Slide 8 text

Refactoring Toolbox

Slide 9

Slide 9 text

Why?

Slide 10

Slide 10 text

Green Field

Slide 11

Slide 11 text

Legacy Code

Slide 12

Slide 12 text

When?

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

1. Introduction 2. Sample 3. Conclusions

Slide 15

Slide 15 text

New Task Print nutritional report in HTML

Slide 16

Slide 16 text

Recipe name ingredients nutritional_report Ingredient amount food Food name nutritional_code HIGH LOW REGULAR 1:N 1:1

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Whyyyy?

Slide 20

Slide 20 text

1º Build a solid set of tests

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

$ rspec spec .............. Finished in 0.00742 seconds 14 examples, 0 failures

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

Long Method

Slide 25

Slide 25 text

Comments

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

... # 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 ...

Slide 28

Slide 28 text

Extract Method

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

$ rspec spec .FFFFFFFFFFFFF Finished in 0.00742 seconds 14 examples, 13 failures

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

$ rspec spec .............. Finished in 0.00742 seconds 14 examples, 0 failures

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

Feature Envy

Slide 39

Slide 39 text

Move Method

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

$ rspec spec ................... Finished in 0.00588 seconds 19 examples, 0 failures

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

Remove Feature Envy with Extract Method

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

$ rspec spec ....................... Finished in 0.00588 seconds 23 examples, 0 failures

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

Replace Temp with Query

Slide 52

Slide 52 text

class Recipe ... def total_calories ingredients.sum(:calories) end def total_nutritional_points ingredients.sum(:nutritional_points) end end

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

$ rspec spec ....................... Finished in 0.00621 seconds 23 examples, 0 failures

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

class Recipe def html_nutritional_report result = "

Nutritional Report for #{name}

" ingredients.each do |ingredient| # show figures for this rental result += "

#{ingredient.food.name} " result += "{ingredient.calories}

" end # add footer lines result += "

Total calories are #{total_calories}

" result += "

You earned #{total_nutritional_points} " result += "nutritional points

" result end ... end HTML Report

Slide 57

Slide 57 text

More Refactoring? Replace Method with Method Objet Template Method Pattern

Slide 58

Slide 58 text

class NutritionalReport def initialize(recipe) @recipe = recipe end def output head body foot end def head ... def body ... def line(ingredient) ... def foot ... end

Slide 59

Slide 59 text

class HTMLNutritionalReport < NutritionalReport def head "

Nutritional Report for #{name}

" end def line(ingredient) "

#{ingredient.food.name} #{ingredient.calories}

" end def foot result = "

Total calories are #{@recipe.total_calories}

Slide 60

Slide 60 text

WIN!

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

I notice a weird smell... Could it be envy?

Slide 63

Slide 63 text

Feature Envy Move Method Get rid of with

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

class Ingredient def calories food.calories(amount) end def nutritional_points food.nutritional_points(amount) end end

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

$ rspec spec/ ................................ Finished in 0.00865 seconds 32 examples, 0 failures

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

Replace Type Code with State/Strategy Switch Statements Fix

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

EPIC WIN!

Slide 74

Slide 74 text

1. Introduction 2. Sample 3. Conclusions

Slide 75

Slide 75 text

No Silver Bullet

Slide 76

Slide 76 text

Improve Design

Slide 77

Slide 77 text

Helps find bugs

Slide 78

Slide 78 text

Program faster

Slide 79

Slide 79 text

Good programmers write code that humans can understand

Slide 80

Slide 80 text

Refactor to Win

Slide 81

Slide 81 text

¡Thanks!

Slide 82

Slide 82 text

Questions?

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

Thanks!