Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Refatoração + Design Patterns em Ruby

7,741 views

Published on

Palestra feita no dia 29/05/2010 no evento Ruby e Rails no Mundo Real.

Published in: Technology

Refatoração + Design Patterns em Ruby

  1. 1. Refatorando Ruby Técnicas de Orientação a Objetos e Design Patterns Aplicados a Linguagens Dinâmicas Cássio Marques Ruby e Rails no Mundo Real 2010
  2. 2. cassiomarques.wordpress.com @cassiomarques cassiommc@gmail.com
  3. 3. http://www.flickr.com/photos/docman/ Refatoração
  4. 4. Motivações
  5. 5. Muitos programadores usando Ruby não sabem usar OO
  6. 6. “Raciocínio estático”
  7. 7. Linguagem nova, mas hábitos antigos
  8. 8. Organização
  9. 9. Modularizar o código
  10. 10. Facilitar manutenção
  11. 11. Facilitar compreensão do código para novos desenvolvedores
  12. 12. Design Patterns
  13. 13. Motivações
  14. 14. Ajudar o Ruby a ser “Enterprise”
  15. 15. Patterns implementados X Enterprisy
  16. 16. YAGNI
  17. 17. Um exemplo simples...
  18. 18.  1 class Bhaskara  2   def solve(a, b, c)  3     # Calcula o delta  4     d = b ** 2 - 4 * a * c  5  6     if d < 0  7       puts 'Raízes complexas - Não sei resolver!'  8     else  9       # calculando raízes 10       r1 = (-b + Math.sqrt(d))/(2.0 * a) 11       r2 = (-b - Math.sqrt(d))/(2.0 * a) 12 13       # imprimindo resultados 14       puts "Raiz 1: #{r1}" 15       puts "Raiz 2: #{r2}" 16     end 17   end 18 end
  19. 19. 1 class Bhaskara   2 end
  20. 20. 1 class Bhaskara 2   def initialize(a, b, c) 3     @a, @b, @c = a, b, c 4   end 5 end
  21. 21.  1 class Bhaskara  2   def initialize(a, b, c)  3     @a, @b, @c = a, b, c  4   end  5  6   private  7   def delta  8     @b ** 2 - 4 * @a * @c      9   end 10 11   def solve 12     @r1 = (-@b + Math.sqrt(delta))/(2.0 * @a) 13     @r2 = (-@b - Math.sqrt(delta))/(2.0 * @a) 14   end 15 end
  22. 22.  1 class Bhaskara  2   def initialize(a, b, c)  3     @a, @b, @c = a, b, c  4   end  5  6   def print_results  7     if delta < 0  8       puts 'Raízes complexas - Não sei resolver!'  9     else 10       solve 11       puts "Raiz 1: #{@r1}" 12       puts "Raiz 2: #{@r2}" 13     end 14   end 15 16   private 17   def delta 18     @b ** 2 - 4 * @a * @c     19   end 20 21   def solve 22     @r1 = (-@b + Math.sqrt(delta))/(2.0 * @a) 23     @r2 = (-@b - Math.sqrt(delta))/(2.0 * @a) 24   end 25 end
  23. 23.  1 class Bhaskara  2   def initialize(a, b, c)  3     @a, @b, @c = a, b, c  4   end  5  6   def print_results  7     delta < 0 ? print_complex_results : print_real_results  8   end  9 10   private 11   def delta 12     @b ** 2 - 4 * @a * @c     13   end 14 15   def solve 16     @r1 = (-@b + Math.sqrt(delta))/(2.0 * @a) 17     @r2 = (-@b - Math.sqrt(delta))/(2.0 * @a) 18   end 19 20   def print_real_results 21     solve 22     puts "Raiz 1: #{@r1}" 23     puts "Raiz 2: #{@r2}" 24   end 25 26   def print_complex_results 27     puts 'Raízes complexas - Não sei resolver!' 28   end 29 end
  24. 24. O que eu preciso para refatorar?
  25. 25. TESTES
  26. 26. Mantenha seus métodos pequenos http://www.flickr.com/photos/davidden/
  27. 27.  1 class ShoppingCart  2   def total_value  3     total = 0  4     for item in @items  5       sub_total = item.value * item.quantity  6       if item.quantity > 3  7         sub_total *= 0.90  8       elsif item.quantity > 6  9         sub_total *= 0.85 10       elsif item.quantiy > 9 11         sub_total *= 0.80 12       end 13       total += sub_total 14     end 15     total 16   end 17 end
  28. 28.  1 # - Elimina variáveis temporárias em favor de métodos  2 class ShoppingCart  3   def total_value  4     total = 0  5     for item in @items  6       total += if item.quantity > 3  7         sub_total(item) * 0.90  8       elsif item.quantity > 6  9         sub_total(item) * 0.85 10       elsif item.quantiy > 9 11         sub_total(item) * 0.80 12       end 14     end 15     total 16   end 17 18   def sub_total(item) 19     item.value * item.quantity 20   end 21 end 22
  29. 29.  1 # - Método usa apenas atributos de outro objeto  2 #  => Mover!  3 class ShoppingCart  4   def total_value  5     total = 0  6     for item in @items  7       total += if item.quantity > 3  8         item.total_value * 0.90  9       elsif item.quantity > 6 10         item_total_value * 0.85 11       elsif item.quantiy > 9 12         item.total_value = 0.80 13       end        14     end 15     total 16   end 17 end 18 19 class Item 20   def total_value 21     @value * @quantity 22   end 23 end
  30. 30.  1 # - Método fazendo coisa demais  2 # => Extrair!  3 class ShoppingCart  4   def total_value  5     total = 0  6     for item in @items  7       total += item.total_value * discount(item.quantity)  8     end  9     total 10   end 11 12   def discount(quantity) 13     case quantity 14     when (1..2); 1.00 15     when (3..5); 0.90 16     when (6..8); 0.85 17     else 0.80     18     end 19   end 20 end
  31. 31.  1 # - Use Ruby!  2 class ShoppingCart  3   def total_value  4     items.inject(0.0) { |sum, item|   5       sum += item.total_value * discount(item.quantity)  6     }  7   end  8  9   def discount(quantity) 10     case quantity 11     when (1..2); 1.00 12     when (3..5); 0.90 13     when (6..8); 0.85 14     else 0.80     15     end 16   end 17 end
  32. 32. Faça objetos se comportarem como coleções http://www.flickr.com/photos/deepsignal/366774303/sizes/l/
  33. 33.  1 class Library  2   def search_book_by_title(title)  3     @books.select { |book| book.title == title }  4   end  5  6   def search_book_by_author(author)  7     @books.select { |book| book.author == author }  8   end  9 10   def sort_by_title 11     @books.sort_by { |book| book.title } 12   end 13 14   def authors 15     @books.map { |book| book.author } 16   end 17 18   # ... 19 end
  34. 34.  1 class Library  2   include Enumerable  3  4   def each  5     @books.each { |book| yield book }  6   end  7 end  8  9 library.each { |book| ... } 10 library.inject { |book| ... } 11 library.map { |book| ... } 12 library.sort { |book| ... } 13 # ...
  35. 35. Dê nomes aos parâmetros dos métodos http://www.flickr.com/photos/giantginkgo/
  36. 36.  1 class Dialog  2   def initialize(height, width, name, title)  3     @height = height  4     @width  = width  5     @name   = name  6     @title  = title  7   end  8 end  9 10 dialog = Dialog.new 200, 400, 'dialog1', 'A Dialog'
  37. 37.  1 class Dialog  2   def initialize(options)  3     @height = options[:height]  4     @width  = options[:width]  5     @name   = options[:name]  6     @title  = options[:title]  7   end  8 end  9 10 dialog = Dialog.new( 11   :height => 200, 12   :width  => 400, 13   :name   => 'dialog1', 14   :title  => 'A Dialog' 15 )
  38. 38. Anotações http://www.flickr.com/photos/matski_98/
  39. 39.  1 class Person  2   def state=(state)  3     unless [:sick, :healthy].include?(state)  4       raise ArgumentError, "unknown state: #{state}"   5     end  6     @state = state  7   end  8 end  9 10 class Vehicle 11   def state=(state) 12     unless [:parked, :running].include?(state) 13       raise ArgumentError, "unknown state: #{state}"  14     end 15     @state = state 16   end 17 end
  40. 40.  1 module HasStates  2   def has_states(*args)  3     define_method :state= do |new_state|  4       unless args.include?(new_state)  5         raise ArgumentError, "unknown state: #{state}"   6       end  7       @state = new_state  8     end  9   end 10 end
  41. 41.  1 class Person  2   extend HasStates  3  4   has_states :sick, :healthy  5 end  6  7 class Vehicle  8   extend HasStates  9 10   has_states :parked, :running 11 end 12 13 Object.extend HasStates
  42. 42. Uma classe não deve realizar trabalhos que não lhe pertencem...
  43. 43. 1 class Person 2   attr_accessor :telephone_number, :telephone_area_code 3 4   def formatted_telephone 5     "(#{telephone_area_code}) #{telephone_number}" 6   end 7 end
  44. 44.  1 class Person  2   def initialize  3     @telephone_number = TelephoneNumber.new  4   end  5 end  6  7 class TelephoneNumber  8   attr_accessor :area_code, :number  9 10   def formatted 11     "(#{area_code}) #{number}" 12   end 13 end
  45. 45. Delegue, delegue, delegue...
  46. 46.  1 class Queue  2   def initialize  3     @queue = []  4   end  5  6   def enqueue(element)  7     @queue.unshift element      8   end  9 10   def dequeue 11     @queue.pop 12   end 13 end
  47. 47. O Ruby pode dar uma ajudinha...
  48. 48.  1 require 'forwardable'  2  3 class Queue  4   extend Forwardable  5  6   def initialize  7     @queue = []  8   end  9 10   def_delegator :@queue, :unshift, :enqueue 11   def_delegator :@queue, :pop, :dequeue 12 end
  49. 49. Não exponha o interior de seus objetos sem necessidade http://www.flickr.com/photos/mwichary/
  50. 50.  1 class Room  2   attr_reader :air_conditioner  3 end  4  5 class AirConditioner  6   attr_reader :sensor  7 end  8  9 class TemperatureSensor 10   def temperature 11     #... 12   end 13 end 14 15 if room.air_conditioner.sensor.temperature > 28 16   room.air_conditioner.on 17 end
  51. 51.  1 class Room  2   attr_reader :air_conditioner  3  4   def temperature  5     air_conditioner.sensor.temperature  6   end  7 end  8  9 if room.temperature > 28 10   room.air_conditioner.on 11 end
  52. 52. Substitua números mágicos por constantes http://decluttr.com/4524127771_white
  53. 53. 1 class Circle 2   def area 3     3.14159265 * (r ** 2) 4   end 5 end
  54. 54. 1 class Circle 2   PI = 3.14159265  3 4   def area 5     PI * (r ** 2) 6   end 7 end
  55. 55. Encapsule coleções de objetos http://www.flickr.com/photos/eagleglide/
  56. 56.  1 class Cart  2   attr_accessor :products  3 end  4  5 class Product  6   attr_reader :value, :name  7  8   def initialize(name, value)  9     @name, @value = name, value 10   end 11 end
  57. 57. 1 products = [] 2 products << Product.new('Camiseta', 35.00) 3 products << Product.new('Bermuda', 38.00) 4 products << Product.new('Boné', 20.00) 5 cart = Cart.new 6 cart.products = products 7 cart.products.pop # => alterei o estado do carrinho
  58. 58.  1 class Cart  2   def initialize  3     @products = []  4   end  5  6   def add_product(product)  7     @products << product      8   end  9 10   def remove_product(product) 11     @products.delete product 12   end 13 end 14 15 cart = Cart.new 16 cart.add_product(Product.new('Camiseta', 35.00))
  59. 59. Substitua condicionais por polimorfismo http://www.flickr.com/photos/randyread/
  60. 60.  1 class Tax  2   def initialize(type, value)  3     @type = type      4     @value = value  5   end  6  7   def retained_value  8     case @type  9     when :irpj;   @value * 0.015 10     when :pis;    @value * 0.0065 11     when :cofins; @value * 0.030 12     when :iss;    0.0 13     end 14   end 15 16   def due_value 17     case @type 18     when :irpj;   @value * 0.033 19     when :pis;    0.0 20     when :cofins; 0.0 21     when :iss;    @value * 0.02 22     end 23   end 24 end
  61. 61.  1 module Tax  2   def due_value  3     @due_rate * @value      4   end  5  6   def retained_value  7     @retained_rate * @value  8   end  9 end 10 11 class IrpjTax 12   include Tax 13 14   def initialize(value) 15     @value         = value 16     @due_rate      = 0.033 17     @retained_rate = 0.015 18   end 19 end 20 21 # o mesmo para as demais classes...
  62. 62.  1 class Invoice  2   def initialize(value)  3     @value = value  4     create_taxes  5   end  6  7   def add_tax(tax)  8     @taxes << tax  9   end 10    11   def total_taxes_value 12     @taxes.inject(0.0) { |sum, tax| 13       sum += tax.due_value + tax.retained_value 14     } 15   end 16 17   private 18   def create_taxes 19     @taxes = [] 20     @taxes << IrpjTax.new(@value) 21     @taxes << PisTax.new(@value) 22     @taxes << CofinsTax.new(@value) 23     @taxes << IssTax.new(@value) 24   end 25 end 26 27 invoice = Invoice.new 1400.00 28 puts invoice.total_taxes_value
  63. 63. Simplifique expressões condicionais
  64. 64. 1 if product.release_date < 3.months.ago || quantity > 100 2   self.value *= 0.90 3 end
  65. 65.  1 if elegible_for_discount?  2   self.value *= 0.90  3 end  4  5 def elegible_for_discount?  6   old? && great_quantity?  7 end  8  9 def old? 10   product.release_date < 3.months.ago 11 end 12 13 def great_quantity? 14   quantity > 100 15 end
  66. 66. http://www.flickr.com/photos/jeff_oliver/ Mas uma expressão condicional pode ser melhor que ifs aninhados...
  67. 67. 1 if age > 40 2   if gender == :male 3     if last_visit > 1.month.ago 4       # ... 5     end 6   end 7 end
  68. 68. 1 if age > 40 && gender == :male && last_visit > 1.month 2   # ... 3 end 4 5 # e a partir daqui podemos extrair as condições para 6 # métodos...
  69. 69. Às vezes faz mais sentido juntar do que separar...
  70. 70. 1 class Bicycle 2   def press_front_brake 3     brakes[:front].press 4   end 5 6   def press_rear_brake 7     brakes[:rear].press 8   end 9 end
  71. 71. 1 class Bicycle 2   def press_brake(brake) 3     brakes[brake].press 4   end 5 end
  72. 72. Verifique se a mensagem pode ser enviada http://www.flickr.com/photos/funtik/
  73. 73. 1 def do_something(target) 2   target.prepare rescue nil 3   target.explode 4 end
  74. 74. 1 def do_something(target) 2   target.prepare if target.respond_to? :prepare 3   target.explode 4 end
  75. 75. Herança
  76. 76.  1 class CdPlayer  1 class TapeDeck  2   def turn_on  2   def turn_on  3     @on = true  3     @on = true  4   end  4   end  5  5  6   def turn_off  6   def turn_off  7     @on = false  7     @on = false  8   end  8   end  9  9 10   def play 10   def play 11     play_disc 11     play_tape 12   end 12   end 13 end 13 end
  77. 77.  1 class AudioDevice  2   def turn_on  3     @on = true  4   end  5  6   def turn_off  7     @on = false  8   end  9 end 10 11 class CdPlayer < AudioDevice 12   def play 13     play_disk 14   end 15 end 16 17 class TapeDeck < AudioDevice 18   def play 19     play_tape 20   end 21 end
  78. 78. Strategy
  79. 79.  1 class Text  2   attr_reader :contents  3  4   def left_aligned_format  5     # ...  6   end  7  8   def right_aligned_format  9     # ... 10   end 11 12   def justified_format 13     # ... 14   end 15 end
  80. 80.  1 class LeftAlignedFormatter  2   def format(context)  3     align_left context.contents  4   end  5 end  6  7 class RightAlignedFormatter  8   def format(context)  9     align_right context.contents 10   end 11 end 12 13 class RightJustifiedFormatter  14   def format(context) 15     justify context.contents 16   end 17 end
  81. 81.  1 class Text  2   attr_reader :contents  3   attr_accessor :formatter  4  5   def initialize(contents, formatter)  6     @contents, @formatter = contents, formatter  7   end  8  9   def format 10     @formatter.format self 11   end 12 end
  82. 82. Blocos, ao resgate! http://www.flickr.com/photos/bekahstargazing/
  83. 83.  1 class Text  2   attr_reader :contents  3   attr_accessor :formatter  4  5   def initialize(contents, &formatter)  6     @contents, @formatter = contents, formatter  7   end  8  9   def format 10     @formatter.call self 11   end 12 end 13 14 left_aligned_text = Text.new 'some text' do |context| 15   align_left context.contents 16 end
  84. 84. Use quando você precisar alterar o algorítmo em tempo de execução
  85. 85. http://www.flickr.com/photos/micahdowty/
  86. 86.  1 class Document  2   def change_text_color(color)  3     @text.color = color  4   end  5  6   def change_background_color(color)  7     @text.background_color = color      8   end  9 10   def change_font_size(size) 11     @text.font_size = size     12   end 13 14   def change_font(font) 15     @text.font = font 16   end 17 end
  87. 87. ctrl-z ?
  88. 88.  1 class Command  2   attr_reader :description  3  4   def initialize(description)  5     @description = description      6   end  7 end
  89. 89.  1 class ChangeTextColorCommand < Command  2   def initialize(target)  3     super("Altera a cor do texto")      4     @target = target  5   end  6  7   def execute(color)  8     @old_color = @target.text.color  9     @target.text.color = color 10   end   11 12   def unexecute 13     @target.text.color = @old_color if @old_color 14   end 15 end
  90. 90.  1 class Document  2   def initialize  3     @commands = []  4   end  5  6   def change_text_color(color)  7     run_command ChangeTextColorCommand.new(self), color  8   end  9 10   def change_font(font) 11     run_command ChangeFontCommand.new(self), font 12   end 13 14   def undo_last_action 15     @commands.pop.unexecute unless @commands.empty? 16   end 17 18   private 19   def run_command(command, param) 20     command.execute param 21     @commands << command 22   end 23 end
  91. 91. Use quando você precisar realizar tarefas específicas e mantê-las em um registro
  92. 92. Proxy
  93. 93.  1 class Task   2   def initialize(creator)  3     @creator = creator  4   end  5  6   def owner=(owner)  7     @owner = owner      8   end  9 10   def add_comment(comment) 11     @comments << comment 12   end 13 14   def update_description(new_description) 15     @description = description 16   end 17 18   def close 19     @status = :closed 20   end 21 end
  94. 94.  1 class TaskProtectionProxy  2   def initialize(target)  3     @target = target      4   end  5  6   def owner=(owner)  7     check_permissions :owner=  8     @owner = owner      9   end 10 11   def add_comment(comment)     12     check_permissions :add_comment 13     @comments << comment 14   end 15 16   # ... 17 18   private 19   def check_permissions(what) 20     unless can_do?(current_user, what) 21       raise 'Acesso negado!'  22     end 23   end 24 end
  95. 95.  1 class TaskProtectionProxy  2   def initialize(target)  3     @target = target  4   end  5  6   def method_missing(method, *args)  7     check_permissions(method)  8     @target.send method, args  9   end 10 11   private  12   def check_permissions(what) 13 unless can_do?(current_user, what) 14       raise 'Acesso negado!'  15     end 16   end 17 end
  96. 96. Perguntas?
  97. 97. Obrigado!

×