KEMBAR78
Refactoring Ruby Code | PDF
Refatorando Código
       Ruby


@caike
http://caikesouza.com
caike@envylabs.com
www.envylabs.com
Métodos
 Ágeis
Testes
(Anti-)Patterns
Software
Craftsmanship
"Any fool can write code that
   a computer can understand.
Good programmers write code that
    humans can understand"

          (Martin Fowler)
Refatorar é ...
Limpar a casa sem
 mudar a fachada
Algumas razões
Design
Responder
a mudanças
Entregas
Constantes
Waterfall




Jan   Feb   Mar    Apr     May     Jun           Jul

             Cost of Maintenance         Extreme Programming Explained: Embrace Change
                                                     Addison Wesley, 2000
XP




Jan   Feb   Mar    Apr     May     Jun           Jul

             Cost of Maintenance         Extreme Programming Explained: Embrace Change
                                                     Addison Wesley, 2000
Quando ?
novas features
bugs
Débito Técnico
Code Reviews
“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)
exemplos.rb
managers = []

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

employees.each do |e|
  managers << e if e.is_manager?
end
Replace Loop with
 Closure Method
managers = []

employees.each do |e|
  managers << e if e.is_manager?
end
managers = employees.
  select { |e| e.is_manager? }
class Movie
  def initialize(stars)
    @stars = stars
  end

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

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

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

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

  def recommended?
    rating = (@stars > 5) ? 8 : 1
    rating >= 8
  end
end
Replace Temp
With Query
class Movie
  def initialize(stars)
    @stars = stars
  end

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

  def recommended?
    rating >= 8
  end

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

  def recommended?
    rating >= 8
  end

  def rating
    (@stars > 5) ? 8 : 1
  end
end
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
Inline Method
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
class Movie
  def initialize...end
  def recommended?
    rating >= 8
  end

  def rating
    @stars > 5 ? 8 : 1
  end




end
mock = mock('user')
expectation = mock.expects(:find)
expectation.with("1")
expectation.returns([])
mock = mock('user')
expectation = mock.expects(:find)
expectation.with("1")
expectation.returns([])
Replace Temp
 With Chain
mock = mock('user')
expectation = mock.expects(:find)
expectation.with("1")
expectation.returns([])
mock = mock('user')
mock.expects(:find).with("1").
      returns([])
Command
  vs.
 Query
def expects
  ...
  self
end

def with
  ...
  self
end

def returns
  ...
  self
end
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
  end
end
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
  end
end
Extract Surrounding
      Method
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
  end
end
def charge(amount, card_number)
  connect do |conn|
    conn.send(amount, card_number)
  end
end
def connect
  begin
    conn = CC_Charger_Server.connect(...)
    yield conn
  rescue IOError => e
    Logger.log "Error: #{e}"
    return nil
  ensure
    conn.close
  end
end
def body_fat_percentage(name,
  age, height, weight, metric_system)
  ...
end
body_fat_percentage("fred", 30, 1.82, 90, 1)
body_fat_percentage("joe", 32, 6, 220, 2)
body_fat_percentage("fred", 30, 1.82, 90, 1)
body_fat_percentage("joe", 32, 6, 220, 2)
Introduce Named Parameter
body_fat_percentage("fred", 30, 1.82, 90, 1)
body_fat_percentage("joe", 32, 6, 220, 2)
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)
def body_fat_percentage(name,
  age, height, weight, metric_system)
  ...
end
def   body_fat_percentage(name, params={})
  #   params[:age]
  #   params[:height]
  #   params[:weight]
  #   params[:metric_system]
end
user.posts.paginate(:page => params[:page],
    :per_page => params[:per_page] || 15)
user.posts.paginate(:page => params[:page],
    :per_page => params[:per_page] || 15)
Replace Magic Number
with Symbolic Constant
user.posts.paginate(:page => params[:page],
    :per_page => params[:per_page] || 15)
CONTACTS_PER_PAGE = 15

user.posts.paginate(:page => params[:page],
    :per_page => params[:per_page] ||
CONTACTS_PER_PAGE)
class MountainBike
  def price
    ...
  end
end

MountainBike.new(:type => :rigid, ...)
MountainBike.new(:type => :front_suspension, ...)
MountainBike.new(:type => :full_suspension, ...)
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
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
  if @type_code == :ultra_suspension
      ...
  end
end
CLOSED for modification
  OPEN for extension
Replace Conditional
With Polymorphism
class MountainBike
  def price
    ...
  end
end
module MountainBike
  def price
    ...
  end
end
class RigidMountainBike
  include MountainBike
end

class FrontSuspensionMountainBike
  include MountainBike
end

class FullSuspensionMountainBike
  include MountainBike
end
RigidMountainBike.new(:type => :rigid, ...)

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

FullSuspensionMountainBike.new(:type =>
 :full_suspension, ...)
class RigidMountainBike
  include MountainBike

  def price
    (1 + @comission) * @base_price
  end
end
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
  end
end
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
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
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
class RigidMountainBike
  include MountainBike
end

class FrontSuspensionMountainBike
  include MountainBike
end

class FullSuspensionMountainBike
  include MountainBike
end
Coding Dojo




  http://orlandodojo.org/
  http://dojorio.org/
Obrigado!
           http://www.flickr.com/photos/eurleif/255241547/
           http://www.flickr.com/photos/dhammza/91435718/
           http://www.flickr.com/photos/krash0387
           http://www.flickr.com/photos/benfrantzdale/208672143/
           http://www.flickr.com/photos/improveit/1574023621/
           http://www.flickr.com/photos/aaroncoyle/972403508
           http://www.flickr.com/photos/talios/3726484920
           http://www.flickr.com/photos/moow/3412079622
           http://www.flickr.com/photos/highwayoflife/2699887178/


@caike
http://caikesouza.com
http://smallactsmanifesto.org

Refactoring Ruby Code