Refactoring Ruby Code

97
@caike http://caikesouza.com Refatorando Código Ruby [email protected]

description

Refactoring Ruby code talk given @ Oxente Rails 2010

Transcript of Refactoring Ruby Code

Page 3: Refactoring Ruby Code

Métodos Ágeis

Page 4: Refactoring Ruby Code

Testes

Page 5: Refactoring Ruby Code

(Anti-)Patterns

Page 6: Refactoring Ruby Code

Software Craftsmanship

Page 7: Refactoring Ruby Code
Page 8: Refactoring Ruby Code

"Any fool can write code that a computer can understand.

Good programmers write code that humans can understand"

(Martin Fowler)

Page 9: Refactoring Ruby Code
Page 10: Refactoring Ruby Code
Page 11: Refactoring Ruby Code

Refatorar é ...

Page 12: Refactoring Ruby Code

Limpar a casa sem mudar a fachada

Page 13: Refactoring Ruby Code
Page 14: Refactoring Ruby Code

Algumas razões

Page 15: Refactoring Ruby Code

Design

Page 16: Refactoring Ruby Code

Responder a mudanças

Page 17: Refactoring Ruby Code

EntregasConstantes

Page 18: Refactoring Ruby Code

Jan Feb Mar Apr May Jun Jul

Cost of Maintenance

Waterfall

Extreme Programming Explained: Embrace ChangeAddison Wesley, 2000

Page 19: Refactoring Ruby Code

Jan Feb Mar Apr May Jun Jul

Cost of Maintenance Extreme Programming Explained: Embrace ChangeAddison Wesley, 2000

XP

Page 20: Refactoring Ruby Code

Quando ?

Page 21: Refactoring Ruby Code

novas features

Page 22: Refactoring Ruby Code

bugs

Page 23: Refactoring Ruby Code
Page 24: Refactoring Ruby Code

Débito Técnico

Page 25: Refactoring Ruby Code

Code Reviews

Page 26: Refactoring Ruby Code

“The great thing about programming is pretty much everyone's code is shit.

Writing software is hard, so it's no problem finding dogs in someone's stuff”

(Zed Shaw)

Page 27: Refactoring Ruby Code
Page 28: Refactoring Ruby Code

exemplos.rb

Page 29: Refactoring Ruby Code

managers = []

employees.each do |e| managers << e if e.is_manager?end

Page 30: Refactoring Ruby Code

managers = []

employees.each do |e| managers << e if e.is_manager?end

Page 31: Refactoring Ruby Code

Replace Loop withClosure Method

Page 32: Refactoring Ruby Code

managers = []

employees.each do |e| managers << e if e.is_manager?end

Page 33: Refactoring Ruby Code

managers = employees. select { |e| e.is_manager? }

Page 34: Refactoring Ruby Code

class Movie def initialize(stars) @stars = stars end def recommended? ((@stars > 5) ? 8 : 1) >= 8 endend

Page 35: Refactoring Ruby Code
Page 36: Refactoring Ruby Code
Page 37: Refactoring Ruby Code
Page 38: Refactoring Ruby Code

class Movie def initialize(stars) @stars = stars end def recommended? ((@stars > 5) ? 8 : 1) >= 8 endend

Page 39: Refactoring Ruby Code

Introduce ExplainingVariable

Page 40: Refactoring Ruby Code

class Movie def initialize(stars) @stars = stars end def recommended? ((@stars > 5) ? 8 : 1) >= 8 endend

Page 41: Refactoring Ruby Code

class Movie def initialize(stars) @stars = stars end def recommended? rating = (@stars > 5) ? 8 : 1 rating >= 8 endend

Page 42: Refactoring Ruby Code

class Movie def initialize(stars) @stars = stars end def recommended? rating = (@stars > 5) ? 8 : 1 rating >= 8 endend

Page 43: Refactoring Ruby Code

Replace TempWith Query

Page 44: Refactoring Ruby Code

class Movie def initialize(stars) @stars = stars end def recommended? rating = (@stars > 5) ? 8 : 1 rating >= 8 endend

Page 45: Refactoring Ruby Code

class Movie def initialize(stars) @stars = stars end def recommended? rating >= 8 end

def rating (@stars > 5) ? 8 : 1 end end

Page 46: Refactoring Ruby Code

OMG OMG OMG OMG OMG

Page 47: Refactoring Ruby Code

class Movie

def recommended? rating >= 8 end

def rating (@stars > 5) ? 8 : 1 end end

Page 48: Refactoring Ruby Code

class Movie

def recommended? rating >= 8 end

def rating more_than_five_stars? ? 8 : 1 end

def more_than_five_stars? @stars > 5 end end

Page 49: Refactoring Ruby Code

Inline Method

Page 50: Refactoring Ruby Code

class Movie def initialize...end def recommended? rating >= 8 end

def rating more_than_five_stars? ? 8 : 1 end

def more_than_five_stars? @stars > 5 end end

Page 51: Refactoring Ruby Code

class Movie def initialize...end def recommended? rating >= 8 end

def rating @stars > 5 ? 8 : 1 end

end

Page 52: Refactoring Ruby Code

mock = mock('user')expectation = mock.expects(:find)expectation.with("1")expectation.returns([])

Page 53: Refactoring Ruby Code

mock = mock('user')expectation = mock.expects(:find)expectation.with("1")expectation.returns([])

Page 54: Refactoring Ruby Code

Replace TempWith Chain

Page 55: Refactoring Ruby Code

mock = mock('user')expectation = mock.expects(:find)expectation.with("1")expectation.returns([])

Page 56: Refactoring Ruby Code

mock = mock('user')mock.expects(:find).with("1"). returns([])

Page 57: Refactoring Ruby Code

Commandvs.

Query

Page 58: Refactoring Ruby Code

def expects ... selfend

def with ... selfend

def returns ... selfend

Page 59: Refactoring Ruby Code

def charge(amount, card_number) begin conn = CC_Charger_Server.connect(...) conn.send(amount, card_number) rescue IOError => e Logger.log "Error: #{e}" return nil ensure conn.close endend

Page 60: Refactoring Ruby Code

def charge(amount, card_number) begin conn = CC_Charger_Server.connect(...) conn.send(amount, card_number) rescue IOError => e Logger.log "Error: #{e}" return nil ensure conn.close endend

Page 61: Refactoring Ruby Code

Extract SurroundingMethod

Page 62: Refactoring Ruby Code

def charge(amount, ccnumber) begin conn = CC_Charger_Server.connect(...) conn.send(amount, card_number) rescue IOError => e Logger.log "Error: #{e}" return nil ensure conn.close endend

Page 63: Refactoring Ruby Code

def charge(amount, card_number) connect do |conn| conn.send(amount, card_number) endend

Page 64: Refactoring Ruby Code

def connect begin conn = CC_Charger_Server.connect(...) yield conn rescue IOError => e Logger.log "Error: #{e}" return nil ensure conn.close endend

Page 65: Refactoring Ruby Code

def body_fat_percentage(name, age, height, weight, metric_system) ...end

Page 66: Refactoring Ruby Code

body_fat_percentage("fred", 30, 1.82, 90, 1)body_fat_percentage("joe", 32, 6, 220, 2)

Page 67: Refactoring Ruby Code

body_fat_percentage("fred", 30, 1.82, 90, 1)body_fat_percentage("joe", 32, 6, 220, 2)

Page 68: Refactoring Ruby Code

Introduce Named Parameter

Page 69: Refactoring Ruby Code

body_fat_percentage("fred", 30, 1.82, 90, 1)body_fat_percentage("joe", 32, 6, 220, 2)

Page 70: Refactoring Ruby Code

body_fat_percentage("fred", :age => 30, :height => 1.82, :weight => 90, MetricSystem::METERS_KG)

body_fat_percentage("joe", :age => 32, :height => 6, :weight => 220, MetricSystem::FEET_LB)

Page 71: Refactoring Ruby Code

def body_fat_percentage(name, age, height, weight, metric_system) ...end

Page 72: Refactoring Ruby Code

def body_fat_percentage(name, params={}) # params[:age] # params[:height] # params[:weight] # params[:metric_system]end

Page 73: Refactoring Ruby Code

user.posts.paginate(:page => params[:page], :per_page => params[:per_page] || 15)

Page 74: Refactoring Ruby Code

user.posts.paginate(:page => params[:page], :per_page => params[:per_page] || 15)

Page 75: Refactoring Ruby Code

Replace Magic Number with Symbolic Constant

Page 76: Refactoring Ruby Code

user.posts.paginate(:page => params[:page], :per_page => params[:per_page] || 15)

Page 77: Refactoring Ruby Code

CONTACTS_PER_PAGE = 15

user.posts.paginate(:page => params[:page], :per_page => params[:per_page] || CONTACTS_PER_PAGE)

Page 78: Refactoring Ruby Code

class MountainBike def price ... endend

MountainBike.new(:type => :rigid, ...)MountainBike.new(:type => :front_suspension, ...)MountainBike.new(:type => :full_suspension, ...)

Page 79: Refactoring Ruby Code

def price if @type_code == :rigid (1 + @comission) * @base_price end if @type_code == :font_suspension (1 + @comission) * @base_price + @front_suspension_price end if @type_code == :full_suspension (1 + @comission) * @base_price+ @front_suspension_price + @rear_suspension_price

end

end

Page 80: Refactoring Ruby Code

def price if @type_code == :rigid (1 + @comission) * @base_price end

if @type_code == :font_suspension (1 + @comission) * @base_price + @front_suspension_priceend if @type_code == :full_suspension (1 + @comission) * @base_price+

@front_suspension_price + @rear_suspension_price

end if @type_code == :ultra_suspension ... end end

Page 81: Refactoring Ruby Code

CLOSED for modificationOPEN for extension

Page 82: Refactoring Ruby Code
Page 83: Refactoring Ruby Code

Replace ConditionalWith Polymorphism

Page 84: Refactoring Ruby Code

class MountainBike def price ... endend

Page 85: Refactoring Ruby Code

module MountainBike def price ... endend

Page 86: Refactoring Ruby Code

class RigidMountainBike include MountainBikeend

class FrontSuspensionMountainBike include MountainBikeend

class FullSuspensionMountainBike include MountainBikeend

Page 87: Refactoring Ruby Code

RigidMountainBike.new(:type => :rigid, ...)

FrontSuspensionMountainBike.new(:type => :front_suspension, ...)

FullSuspensionMountainBike.new(:type => :full_suspension, ...)

Page 88: Refactoring Ruby Code

class RigidMountainBike include MountainBike

def price (1 + @comission) * @base_price endend

Page 89: Refactoring Ruby Code

class FrontSuspensionMountainBike include MountainBike def price (1 + @comission) * @base_price + @front_suspension_price end end

class FullSuspensionMountainBike include MountainBike def price (1 + @comission) * @base_price + @front_suspension_price + @rear_suspension_price endend

Page 90: Refactoring Ruby Code

def price if @type_code == :rigid raise "should not be called" end if @type_code == :font_suspension (1 + @comission) * @base_price + @front_suspension_price end if @type_code == :full_suspension (1 + @comission) * @base_price+ @front_suspension_price + @rear_suspension_price

end end

Page 91: Refactoring Ruby Code

def price if @type_code == :rigid raise "should not be called" end if @type_code == :font_suspension raise "should not be called" end if @type_code == :full_suspension raise "should not be called" end end

Page 92: Refactoring Ruby Code

def price if @type_code == :rigid raise "should not be called" end if @type_code == :font_suspension raise "should not be called" end if @type_code == :full_suspension raise "should not be called" end end

Page 93: Refactoring Ruby Code

class RigidMountainBike include MountainBikeend

class FrontSuspensionMountainBike include MountainBikeend

class FullSuspensionMountainBike include MountainBikeend

Page 94: Refactoring Ruby Code
Page 96: Refactoring Ruby Code