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 em Larga Escala

4,360 views

Published on

Slides from my session at QCon SP on "Refactoring at Large". Covering examples of refactoring in Java, Ruby/Rails and some ways to make architecture changes in a safer and step-by-step way

Published in: Technology, Business
  • Be the first to comment

Refatoração em Larga Escala

  1. 1. Refatoração em Larga Escala Danilo Sato - @dtsato ThoughtWorks www.dtsato.com
  2. 2. 8 países 0+ esc ritórios2 On & off shore1600+ pessoas $200m receita US
  3. 3. 8 países 0+ esc ritórios2 On & off shore1600+ pessoas $200m receita US Estam os contratando! s r ent-opportunitie http://thou ghtworks.com/cur
  4. 4. Por que mudamos código?
  5. 5. Por que mudamos código?• Adicionar funcionalidade
  6. 6. Por que mudamos código?• Adicionar funcionalidade• Arrumar bugs
  7. 7. Por que mudamos código?• Adicionar funcionalidade• Arrumar bugs• Melhorar o design
  8. 8. Por que mudamos código?• Adicionar funcionalidade• Arrumar bugs• Melhorar o design• Otimização
  9. 9. Por que mudamos código?• Adicionar funcionalidade• Arrumar bugs• Melhorar o design• Otimização
  10. 10. "Refatoração é uma técnica disciplinada para reestruturarcódigo, alterando sua estrutura interna sem alterar seu comportamento externo"
  11. 11. Refatoração Código
  12. 12. Refatoração Código
  13. 13. Refatoração Código Extr air Método
  14. 14. Refatoração Código
  15. 15. Refatoração Código
  16. 16. Refatoração Código Eliminar Duplicação
  17. 17. • Extrair Método• Mover Método• Extrair Classe• Remover Mediador• Auto-Encapsular Atributo• Decompor Condicional• ...
  18. 18. Testes! CódigoTeste
  19. 19. Testes! CódigoTeste
  20. 20. Testes! CódigoTeste Eliminar Duplicação
  21. 21. Outros tipos... CódigoTeste
  22. 22. Outros tipos... Código CódigoTeste
  23. 23. Outros tipos... Código CódigoTeste Teste
  24. 24. Outros tipos... Código CódigoTeste Teste Se paração de Respons abilidades
  25. 25. Outros tipos... CódigoTeste
  26. 26. Outros tipos...Teste Código
  27. 27. Outros tipos...Teste Código
  28. 28. Outros tipos...Teste Código Mudança no Design
  29. 29. Ainda mais...
  30. 30. “Big Picture”
  31. 31. “Big Picture”
  32. 32. “Big Picture”
  33. 33. “Big Picture”
  34. 34. “Big Picture”
  35. 35. EstratégiaMecânica
  36. 36. Coding Kata!
  37. 37. Coding Kata!
  38. 38. EstratégiaMecânica
  39. 39. CheckoutControllerTest CheckoutController Order Cart
  40. 40. m/dtsato/refact oring-experimentht tp://github.co - mant.com/2011/0 9/01/refactoringhttp://paulhamexperiment CheckoutControllerTest CheckoutController Order Cart
  41. 41. public class CheckoutControllerTest { private CheckoutController checkoutController; private Cart cart = new Cart(); // could as easily be a mock private Order order = new Order(); // could as easily be a mock private ModelMap model = new ModelMap(); @Before public void setup() { checkoutController = new CheckoutController(cart, order); } @Test public void checkout_should_add_an_upsell() { String result = checkoutController.goCheckout(model); assertThat(result, equalTo("checkout")); assertThat(model.toString(), equalTo("{cart=Cart{contents=[[iPad]]}}")); } @Test public void place_order_in_checkout_should_make_an_order() { cart.addTo("dummy"); String result = checkoutController.placeOrder(model); assertThat(result, equalTo("orderPlaced")); assertThat(model.toString(),equalTo("{order=Order{cart=Cart{contents=[[dummy]]}}}")); }}
  42. 42. @Controller public class CheckoutController { private final Cart cart; private final Order order; @Autowired public CheckoutController(Cart cart, Order order) { this.cart = cart; this.order = order; } @RequestMapping("/go/to/checkout/") public String goCheckout(ModelMap model) { cart.addTo(findUpsell(cart)); model.addAttribute("cart", cart); return "checkout"; } @RequestMapping("/place/order") public String placeOrder(ModelMap model) { order.place(cart); model.addAttribute("order", order); return "orderPlaced"; } String findUpsell(Cart cart) { return cart.size() == 0 ? "iPad" : "iPod Nano"; }}
  43. 43. @Controller public class CheckoutController { “Extrair Clas se” private final Cart cart; OrderControll er em private final Order order; placeOrder() gerando get/s et @Autowired public CheckoutController(Cart cart, Order order) { this.cart = cart; this.order = order; } @RequestMapping("/go/to/checkout/") public String goCheckout(ModelMap model) { cart.addTo(findUpsell(cart)); model.addAttribute("cart", cart); return "checkout"; } @RequestMapping("/place/order") public String placeOrder(ModelMap model) { order.place(cart); model.addAttribute("order", order); return "orderPlaced"; } String findUpsell(Cart cart) { return cart.size() == 0 ? "iPad" : "iPod Nano"; }}
  44. 44. @Controller public class CheckoutController { private final Cart cart; private final Order order; private final OrderController orderController = new OrderController(this); @Autowired public CheckoutController(Cart cart, Order order) { this.cart = cart; this.order = order; } @RequestMapping("/go/to/checkout/") public String goCheckout(ModelMap model) { // other stuff cart.addTo(findUpsell(cart)); model.addAttribute("cart", cart); return "checkout"; } @RequestMapping("/place/order") public String placeOrder(ModelMap model) { return orderController.placeOrder(model); } String findUpsell(Cart cart) { return cart.size() == 0 ? "iPad" : "iPod Nano"; } public Order getOrder() { return order; } public Cart getCart() { return cart; }}
  45. 45. @Controller public class CheckoutController { ⌘⌥N private final Cart cart; “Inline” inic private final Order order; ialização de private final OrderController orderController = orderController new OrderController(this); @Autowired public CheckoutController(Cart cart, Order order) { this.cart = cart; this.order = order; } @RequestMapping("/go/to/checkout/") public String goCheckout(ModelMap model) { // other stuff cart.addTo(findUpsell(cart)); model.addAttribute("cart", cart); return "checkout"; } @RequestMapping("/place/order") public String placeOrder(ModelMap model) { return orderController.placeOrder(model); } String findUpsell(Cart cart) { return cart.size() == 0 ? "iPad" : "iPod Nano"; } public Order getOrder() { return order; } public Cart getCart() { return cart; }}
  46. 46. @Controller public class CheckoutController { private final Cart cart; private final Order order; @Autowired public CheckoutController(Cart cart, Order order) { this.cart = cart; this.order = order; } @RequestMapping("/go/to/checkout/") public String goCheckout(ModelMap model) { // other stuff cart.addTo(findUpsell(cart)); model.addAttribute("cart", cart); return "checkout"; } @RequestMapping("/place/order") public String placeOrder(ModelMap model) { return new OrderController(this).placeOrder(model); } String findUpsell(Cart cart) { return cart.size() == 0 ? "iPad" : "iPod Nano"; } public Order getOrder() { return order; } public Cart getCart() { return cart; }}
  47. 47. public class OrderController { private final CheckoutController checkoutController; public OrderController(CheckoutController checkoutController) { this.checkoutController = checkoutController; } @RequestMapping("/place/order") public String placeOrder(ModelMap model) { checkoutController.getOrder().place(checkoutController.getCart()); model.addAttribute("order", checkoutController.getOrder()); return "orderPlaced"; }}
  48. 48. ⌘⌥F “Introduzir a tributo” orde cart em Order r e Controllerpublic class OrderController { private final CheckoutController checkoutController; public OrderController(CheckoutController checkoutController) { this.checkoutController = checkoutController; } @RequestMapping("/place/order") public String placeOrder(ModelMap model) { checkoutController.getOrder().place(checkoutController.getCart()); model.addAttribute("order", checkoutController.getOrder()); return "orderPlaced"; }}
  49. 49. public class OrderController { private final CheckoutController checkoutController; private final Order order; private final Cart cart; public OrderController(CheckoutController checkoutController) { this.checkoutController = checkoutController; order = this.checkoutController.getOrder(); cart = this.checkoutController.getCart(); } @RequestMapping("/place/order") public String placeOrder(ModelMap model) { order.place(cart); model.addAttribute("order", order); return "orderPlaced"; }}
  50. 50. ⌘⌥P / ⌥ / ⌘Y “Introduzir p arâmetro” ord e cart no con erpublic class OrderController { strutor de OrderControl private final CheckoutController checkoutController; ler e inic ializar private final Order order; private final Cart cart; public OrderController(CheckoutController checkoutController) { this.checkoutController = checkoutController; order = this.checkoutController.getOrder(); cart = this.checkoutController.getCart(); } @RequestMapping("/place/order") public String placeOrder(ModelMap model) { order.place(cart); model.addAttribute("order", order); return "orderPlaced"; }}
  51. 51. public class OrderController { private final CheckoutController checkoutController; private final Order order; private final Cart cart; public OrderController(CheckoutController checkoutController, Order order, Cart cart) { this.checkoutController = checkoutController; this.order = order; this.cart = cart; } @RequestMapping("/place/order") public String placeOrder(ModelMap model) { order.place(cart); model.addAttribute("order", order); return "orderPlaced"; }}
  52. 52. ⌘⌫ “Remoção Segu ra” do atribupublic class OrderController { e parâmetro to private final CheckoutController checkoutController; checkoutContr oller private final Order order; private final Cart cart; public OrderController(CheckoutController checkoutController, Order order, Cart cart) { this.checkoutController = checkoutController; this.order = order; this.cart = cart; } @RequestMapping("/place/order") public String placeOrder(ModelMap model) { order.place(cart); model.addAttribute("order", order); return "orderPlaced"; }}
  53. 53. public class OrderController { private final Order order; private final Cart cart; public OrderController(Order order, Cart cart) { this.order = order; this.cart = cart; } @RequestMapping("/place/order") public String placeOrder(ModelMap model) { order.place(cart); model.addAttribute("order", order); return "orderPlaced"; }}
  54. 54. @Controller public class CheckoutController { private final Cart cart; private final Order order; @Autowired public CheckoutController(Cart cart, Order order) { this.cart = cart; this.order = order; } @RequestMapping("/go/to/checkout/") public String goCheckout(ModelMap model) { // other stuff cart.addTo(findUpsell(cart)); model.addAttribute("cart", cart); return "checkout"; } @RequestMapping("/place/order") public String placeOrder(ModelMap model) { return new OrderController(order, cart).placeOrder(model); } String findUpsell(Cart cart) { return cart.size() == 0 ? "iPad" : "iPod Nano"; } public Order getOrder() { return order; } public Cart getCart() { return cart; }}
  55. 55. @Controller public class CheckoutController { ⌘⌫ “Remoção Segu private final Cart cart; ra” de getCar e getOrder() t() private final Order order; em CheckoutContr oller @Autowired public CheckoutController(Cart cart, Order order) { this.cart = cart; this.order = order; } @RequestMapping("/go/to/checkout/") public String goCheckout(ModelMap model) { // other stuff cart.addTo(findUpsell(cart)); model.addAttribute("cart", cart); return "checkout"; } @RequestMapping("/place/order") public String placeOrder(ModelMap model) { return new OrderController(order, cart).placeOrder(model); } String findUpsell(Cart cart) { return cart.size() == 0 ? "iPad" : "iPod Nano"; } public Order getOrder() { return order; } public Cart getCart() { return cart; }}
  56. 56. @Controller public class CheckoutController { private final Cart cart; private final Order order; @Autowired public CheckoutController(Cart cart, Order order) { this.cart = cart; this.order = order; } @RequestMapping("/go/to/checkout/") public String goCheckout(ModelMap model) { // other stuff cart.addTo(findUpsell(cart)); model.addAttribute("cart", cart); return "checkout"; } @RequestMapping("/place/order") public String placeOrder(ModelMap model) { return new OrderController(order, cart).placeOrder(model); } String findUpsell(Cart cart) { return cart.size() == 0 ? "iPad" : "iPod Nano"; }}
  57. 57. ⌘⌥N@Controller public class CheckoutController { “Inline” méto do placeOrder mudar argumen e private final Cart cart; tos para usar cart e order private final Order order; no teste @Autowired public CheckoutController(Cart cart, Order order) { this.cart = cart; this.order = order; } @RequestMapping("/go/to/checkout/") public String goCheckout(ModelMap model) { // other stuff cart.addTo(findUpsell(cart)); model.addAttribute("cart", cart); return "checkout"; } @RequestMapping("/place/order") public String placeOrder(ModelMap model) { return new OrderController(order, cart).placeOrder(model); } String findUpsell(Cart cart) { return cart.size() == 0 ? "iPad" : "iPod Nano"; }}
  58. 58. @Controller public class CheckoutController { private final Cart cart; private final Order order; @Autowired public CheckoutController(Cart cart, Order order) { this.cart = cart; this.order = order; } @RequestMapping("/go/to/checkout/") public String goCheckout(ModelMap model) { // other stuff cart.addTo(findUpsell(cart)); model.addAttribute("cart", cart); return "checkout"; } String findUpsell(Cart cart) { return cart.size() == 0 ? "iPad" : "iPod Nano"; }}
  59. 59. public class CheckoutControllerTest { private CheckoutController checkoutController; private Cart cart = new Cart(); // could as easily be a mock private Order order = new Order(); // could as easily be a mock private ModelMap model = new ModelMap(); @Before public void setup() { checkoutController = new CheckoutController(cart, order); } @Test public void checkout_should_add_an_upsell() { String result = checkoutController.goCheckout(model); assertThat(result, equalTo("checkout")); assertThat(model.toString(), equalTo("{cart=Cart{contents=[[iPad]]}}")); } @Test public void place_order_in_checkout_should_make_an_order() { cart.addTo("dummy"); String result = new OrderController(order, cart).placeOrder(model); assertThat(result, equalTo("orderPlaced")); assertThat(model.toString(),equalTo("{order=Order{cart=Cart{contents=[[dummy]]}}}")); }}
  60. 60. public class CheckoutControllerTest { ⌘⌥F “ Introduzir at private CheckoutController checkoutController; ributo” private or be a ntro Cart cart = new Cart(); // could as easily derComock ller no teste, private inicialia ando Order order = new Order(); // could as easily be z mock no setUp() private ModelMap model = new ModelMap(); @Before public void setup() { checkoutController = new CheckoutController(cart, order); } @Test public void checkout_should_add_an_upsell() { String result = checkoutController.goCheckout(model); assertThat(result, equalTo("checkout")); assertThat(model.toString(), equalTo("{cart=Cart{contents=[[iPad]]}}")); } @Test public void place_order_in_checkout_should_make_an_order() { cart.addTo("dummy"); String result = new OrderController(order, cart).placeOrder(model); assertThat(result, equalTo("orderPlaced")); assertThat(model.toString(),equalTo("{order=Order{cart=Cart{contents=[[dummy]]}}}")); }}
  61. 61. public class CheckoutControllerTest { private CheckoutController checkoutController; private Cart cart = new Cart(); // could as easily be a mock private Order order = new Order(); // could as easily be a mock private ModelMap model = new ModelMap(); private OrderController orderController; @Before public void setup() { checkoutController = new CheckoutController(cart, order); orderController = new OrderController(order, cart); } @Test public void checkout_should_add_an_upsell() { String result = checkoutController.goCheckout(model); assertThat(result, equalTo("checkout")); assertThat(model.toString(), equalTo("{cart=Cart{contents=[[iPad]]}}")); } @Test public void place_order_in_checkout_should_make_an_order() { cart.addTo("dummy"); String result = orderController.placeOrder(model); assertThat(result, equalTo("orderPlaced")); assertThat(model.toString(),equalTo("{order=Order{cart=Cart{contents=[[dummy]]}}}")); }}
  62. 62. @Controller public class CheckoutController { private final Cart cart; private final Order order; @Autowired public CheckoutController(Cart cart, Order order) { this.cart = cart; this.order = order; } @RequestMapping("/go/to/checkout/") public String goCheckout(ModelMap model) { // other stuff cart.addTo(findUpsell(cart)); model.addAttribute("cart", cart); return "checkout"; } String findUpsell(Cart cart) { return cart.size() == 0 ? "iPad" : "iPod Nano"; }}
  63. 63. ⌘⌫ “Remoção Segu ra” do atribu e parâmetro o to@Controller public class CheckoutController { rder no CheckoutContr oller private final Cart cart; private final Order order; @Autowired public CheckoutController(Cart cart, Order order) { this.cart = cart; this.order = order; } @RequestMapping("/go/to/checkout/") public String goCheckout(ModelMap model) { // other stuff cart.addTo(findUpsell(cart)); model.addAttribute("cart", cart); return "checkout"; } String findUpsell(Cart cart) { return cart.size() == 0 ? "iPad" : "iPod Nano"; }}
  64. 64. @Controller public class CheckoutController { private final Cart cart; @Autowired public CheckoutController(Cart cart) { this.cart = cart; } @RequestMapping("/go/to/checkout/") public String goCheckout(ModelMap model) { // other stuff cart.addTo(findUpsell(cart)); model.addAttribute("cart", cart); return "checkout"; } String findUpsell(Cart cart) { return cart.size() == 0 ? "iPad" : "iPod Nano"; }}
  65. 65. EstratégiaMecânica
  66. 66. Identificar Objetivo
  67. 67. Identificar Objetivo
  68. 68. Identificar Objetivo Design “ativo”
  69. 69. Identificar Objetivo Design “ativo”• Design OO
  70. 70. Identificar Objetivo Design “ativo”• Design OO• Direção do Projeto
  71. 71. Identificar Objetivo Design “ativo”• Design OO• Direção do Projeto• Decisões Arquiteturais
  72. 72. Identificar Objetivo Design “passivo”
  73. 73. Identificar Objetivo Design “passivo”• Maus Cheiros
  74. 74. Identificar Objetivo Design “passivo”• Maus Cheiros• Métricas
  75. 75. Identificar Objetivo Design “passivo”• Maus Cheiros• Métricas• Visualizações
  76. 76. Traçar Estratégia
  77. 77. Traçar Estratégia ? ??
  78. 78. Traçar Estratégia
  79. 79. Traçar Estratégia• Isole o Impacto das Mudanças
  80. 80. Traçar Estratégia• Isole o Impacto das Mudanças• Baby Steps
  81. 81. Traçar Estratégia• Isole o Impacto das Mudanças• Baby Steps• Mantenha os testes passando
  82. 82. Traçar Estratégia• Isole o Impacto das Mudanças• Baby Steps• Mantenha os testes passando• Ir para trás antes de ir para frente
  83. 83. CalendarSearchController CalendarSearchController Test Event Event
  84. 84. r dtsato/kata-ref actoring-calendahttp ://github.com/ CalendarSearchController CalendarSearchController Test Event Event
  85. 85. def index(params) scope = Event case params[:timeframe] when tomorrow scope = scope.between_day(DateTime.now + 1) when this_week scope = scope.between_dates(DateTime.now, ( DateTime.now + 6 ).end_of_day) when custom if params[:start_date].blank? params[:start_date] = DateTime.now.beginning_of_week.strftime(%m/%d/%Y) end if params[:end_date].blank? params[:end_date] = (DateTime.now.end_of_week - 2).strftime(%m/%d/%Y) end scope = scope.between_dates(DateTime.strptime(params[:start_date], %m/%d/%Y),DateTime.strptime(params[:end_date], %m/%d/%Y).end_of_day) when hour scope = scope.between_hour_on_day(DateTime.strptime(params[:hour], %m/%d/%Y %H:%M)) when today scope = scope.between_day(DateTime.now) end @events = scope.allend
  86. 86. def index(params) scope = Event switch/case case params[:timeframe] when tomorrow scope = scope.between_day(DateTime.now + 1) when this_week scope = scope.between_dates(DateTime.now, ( DateTime.now + 6 ).end_of_day) when custom if params[:start_date].blank? params[:start_date] = DateTime.now.beginning_of_week.strftime(%m/%d/%Y) end if params[:end_date].blank? params[:end_date] = (DateTime.now.end_of_week - 2).strftime(%m/%d/%Y) end scope = scope.between_dates(DateTime.strptime(params[:start_date], %m/%d/%Y),DateTime.strptime(params[:end_date], %m/%d/%Y).end_of_day) when hour scope = scope.between_hour_on_day(DateTime.strptime(params[:hour], %m/%d/%Y %H:%M)) when today scope = scope.between_day(DateTime.now) end @events = scope.allend
  87. 87. def index(params) scope = Event case params[:timeframe] when tomorrow scope = scope.between_day(DateTime.now + 1) when this_week string parsing scope = scope.between_dates(DateTime.now, ( DateTime.now + 6 ).end_of_day) when custom if params[:start_date].blank? params[:start_date] = DateTime.now.beginning_of_week.strftime(%m/%d/%Y) end if params[:end_date].blank? params[:end_date] = (DateTime.now.end_of_week - 2).strftime(%m/%d/%Y) end scope = scope.between_dates(DateTime.strptime(params[:start_date], %m/%d/%Y),DateTime.strptime(params[:end_date], %m/%d/%Y).end_of_day) when hour scope = scope.between_hour_on_day(DateTime.strptime(params[:hour], %m/%d/%Y %H:%M)) when today scope = scope.between_day(DateTime.now) end @events = scope.allend
  88. 88. def index(params) scope = Event case params[:timeframe] when tomorrow scope = scope.between_day(DateTime.now + 1) when this_week scope = scope.between_dates(DateTime.now, ( DateTime.now + 6 ).end_of_day) when custom if params[:start_date].blank? params[:start_date] = DateTime.now.beginning_of_week.strftime(%m/%d/%Y) end if params[:end_date].blank? params[:end_date] = (DateTime.now.end_of_week - 2).strftime(%m/%d/%Y) end scope = scope.between_dates(DateTime.strptime(params[:start_date], %m/%d/%Y),DateTime.strptime(params[:end_date], %m/%d/%Y).end_of_day) when hour scope = scope.between_hour_on_day(DateTime.strptime(params[:hour], %m/%d/%Y %H:%M)) when today scope = scope.between_day(DateTime.now) end vários named scopes @events = scope.allend
  89. 89. class Event < ActiveRecord::Base named_scope :between_dates, lambda { |start_date, end_date| {:conditions => ["at >= ? AND at <= ?", start_date, end_date ] } } named_scope :between_hour_on_day, lambda { |start_hour| end_date = start_hour + 1.hour-1.second { :conditions => {:at => start_hour..end_date} } } named_scope :between_day, lambda { |date| start_date = date.at_beginning_of_day end_date = start_date.end_of_day { :conditions => {:at => start_date..end_date} } }end
  90. 90. jeitos diferentes de fazer a mesmaclass Event < ActiveRecord::Base coisa named_scope :between_dates, lambda { |start_date, end_date| {:conditions => ["at >= ? AND at <= ?", start_date, end_date ] } } named_scope :between_hour_on_day, lambda { |start_hour| end_date = start_hour + 1.hour-1.second { :conditions => {:at => start_hour..end_date} } } named_scope :between_day, lambda { |date| start_date = date.at_beginning_of_day end_date = start_date.end_of_day { :conditions => {:at => start_date..end_date} } }end
  91. 91. class Event < ActiveRecord::Base named_scope :between_dates, lambda { |start_date, end_date| {:conditions => ["at >= ? AND at <= ?", start_date, end_date ] } } named_scope :between_hour_on_day, lambda { |start_hour| end_date = start_hour + 1.hour-1.second { :conditions => {:at => start_hour..end_date} } } mais lógica named_scope :between_day, lambda { |date| start_date = date.at_beginning_of_day end_date = start_date.end_of_day { :conditions => {:at => start_date..end_date} } }end
  92. 92. ObjetivosAtivo: • Isolar testes unitários do banco de dadosPassivo: • Tratar dos maus cheiros
  93. 93. O Plano1. Centralizar a lógica de manipulação de datas2. Unificar named scopes3. Extrair lógica do Controller4. Quebrar timeframe em vários pedaços
  94. 94. def index(params) case params[:timeframe] when tomorrow date = DateTime.now + 1 start_date = date.at_beginning_of_day end_date = start_date.end_of_day when this_week start_date = DateTime.now end_date = (DateTime.now + 6 ).end_of_day when custom start_date = params[:start_date].blank? ? DateTime.now.beginning_of_week :DateTime.strptime(params[:start_date], %m/%d/%Y) end_date = params[:end_date].blank? ? (DateTime.now.end_of_week - 2) :DateTime.strptime(params[:end_date], %m/%d/%Y).end_of_day when hour start_date = DateTime.strptime(params[:hour], %m/%d/%Y %H:%M) end_date = start_date + 1.hour-1.second when today date = DateTime.now start_date = date.at_beginning_of_day end_date = start_date.end_of_day end @events = Event.between_dates(start_date, end_date)end
  95. 95. class Event < ActiveRecord::Base named_scope :between_dates, lambda { |start_date, end_date| {:conditions => {:at => start_date..end_date}} }end
  96. 96. class Event < ActiveRecord::Base named_scope :between_dates, lambda { |start_date, end_date| {:conditions => {:at => start_date..end_date}} }end lib/calendar_search_controller.rb | 27 +++++++++++++-------------- lib/event.rb | 13 +------------ spec/event_spec.rb | 37 ------------------------------------- 3 files changed, 14 insertions(+), 63 deletions(-)
  97. 97. Antes . ............ ds 0 .49367 secon Finished in res 13 examp les, 0 failuclass Event < ActiveRecord::Base named_scope :between_dates, lambda { |start_date, end_date| {:conditions => {:at => start_date..end_date}} }end lib/calendar_search_controller.rb | 27 +++++++++++++-------------- lib/event.rb | 13 +------------ spec/event_spec.rb | 37 ------------------------------------- 3 files changed, 14 insertions(+), 63 deletions(-)
  98. 98. Antes Depois ....... . ............ ds Finished in 0 .49367 secon 0.25355 seco Finished in res 7 examples, nds 13 examp les, 0 failu 0 failuresclass Event < ActiveRecord::Base named_scope :between_dates, lambda { |start_date, end_date| {:conditions => {:at => start_date..end_date}} }end lib/calendar_search_controller.rb | 27 +++++++++++++-------------- lib/event.rb | 13 +------------ spec/event_spec.rb | 37 ------------------------------------- 3 files changed, 14 insertions(+), 63 deletions(-)
  99. 99. O Plano1. Centralizar a lógica de manipulação de datas2. Unificar named scopes3. Extrair lógica do Controller4. Quebrar timeframe em vários pedaços
  100. 100. class CalendarSearchController attr_reader :events def index(params) timeframe = TimeFrame.for(params[:timeframe], params[:start_date],params[:end_date], params[:hour]) @events = Event.between_dates(timeframe.start_date, timeframe.end_date) endend mudança nos testes:it "should create time frame from params and retrieve events" do timeframe = TimeFrame.new(DateTime.now, DateTime.now) TimeFrame.should_receive(:for).with(today, start, end,hour).and_return(timeframe) events = [Event.new, Event.new] Event.should_receive(:between_dates).with(timeframe.start_date,timeframe.end_date).and_return(events) @controller.index(:timeframe => today, :start_date => start, :end_date =>end, :hour => hour) @controller.events.should == eventsend
  101. 101. describe "Acceptance tests" do around(:each) do |example| Event.transaction { example.run; raise ActiveRecord::Rollback } end before do @controller = CalendarSearchController.new @today = DateTime.strptime(06/01/2011, %m/%d/%Y) DateTime.stub!(:now).and_return(@today) end it "should return todays events" do today_events = [Event.create(:at => @today), Event.create(:at => @today)] tomorrow = @today + 1 tomorrow_events = [Event.create(:at => tomorrow), Event.create(:at => tomorrow)] @controller.index(:timeframe => today) @controller.events.should == today_events end it "should return tomorrows events" ... it "should return this weeks events" ... it "should return events for specified date range" ... it "should default date range to this business week" ... it "returns events for a specified hour" ...end
  102. 102. describe "Acceptance tests" do around(:each) do |example| Depois ...... Event.transaction { example.run; raise ActiveRecord::Rollback } end Finished in 0.24538 seco before do 6 examples, nds 0 failures @controller = CalendarSearchController.new @today = DateTime.strptime(06/01/2011, %m/%d/%Y) DateTime.stub!(:now).and_return(@today) end it "should return todays events" do today_events = [Event.create(:at => @today), Event.create(:at => @today)] tomorrow = @today + 1 tomorrow_events = [Event.create(:at => tomorrow), Event.create(:at => tomorrow)] @controller.index(:timeframe => today) @controller.events.should == today_events end it "should return tomorrows events" ... it "should return this weeks events" ... it "should return events for specified date range" ... it "should default date range to this business week" ... it "returns events for a specified hour" ...end
  103. 103. class TimeFrame < Struct.new(:start_date, :end_date) def self.for(type, start_date, end_date, hour) case type when tomorrow date = DateTime.now + 1 TimeFrame.new(date.at_beginning_of_day, date.end_of_day) when this_week TimeFrame.new(DateTime.now, (DateTime.now + 6 ).end_of_day) when custom start_date = start_date.blank? ? DateTime.now.beginning_of_week :DateTime.strptime(start_date, %m/%d/%Y) end_date = end_date.blank? ? (DateTime.now.end_of_week - 2) :DateTime.strptime(end_date, %m/%d/%Y).end_of_day TimeFrame.new(start_date, end_date) when hour start_date = DateTime.strptime(hour, %m/%d/%Y %H:%M) end_date = start_date + 1.hour-1.second TimeFrame.new(start_date, end_date) when today date = DateTime.now TimeFrame.new(date.at_beginning_of_day, date.end_of_day) end endend
  104. 104. describe TimeFrame do before do @today = DateTime.strptime(06/01/2011, %m/%d/%Y) DateTime.stub!(:now).and_return(@today) end describe "todays event" do it "should use beginning and end of day" do timeframe = TimeFrame.for(today, nil, nil, nil) timeframe.start_date.should == @today.at_beginning_of_day timeframe.end_date.should == @today.end_of_day end end ...end
  105. 105. describe TimeFrame do before do @today = DateTime.strptime(06/01/2011, %m/%d/%Y) DateTime.stub!(:now).and_return(@today) end describe "todays event" do it "should use beginning and end of day" do timeframe = TimeFrame.for(today, nil, nil, nil) timeframe.start_date.should == @today.at_beginning_of_day timeframe.end_date.should == @today.end_of_day end Rakefile | 4 ++ end lib/calendar_search_controller.rb | 24 +--------- ... lib/time_frame.rb | 24 ++++++++++end spec/acceptance_tests.rb | 75 +++++++++++++++++++++++++++++++ spec/calendar_search_controller_spec.rb | 75 ++++--------------------------- spec/time_frame_spec.rb | 63 ++++++++++++++++++++++++++ 6 files changed, 178 insertions(+), 87 deletions(-)
  106. 106. Antes ....... dsdescribe d in 0 .25355 secon Finishe TimeFrame do es before les, 0 failur 7 examp do @today = DateTime.strptime(06/01/2011, %m/%d/%Y) DateTime.stub!(:now).and_return(@today) end describe "todays event" do it "should use beginning and end of day" do timeframe = TimeFrame.for(today, nil, nil, nil) timeframe.start_date.should == @today.at_beginning_of_day timeframe.end_date.should == @today.end_of_day end Rakefile | 4 ++ end lib/calendar_search_controller.rb | 24 +--------- ... lib/time_frame.rb | 24 ++++++++++end spec/acceptance_tests.rb | 75 +++++++++++++++++++++++++++++++ spec/calendar_search_controller_spec.rb | 75 ++++--------------------------- spec/time_frame_spec.rb | 63 ++++++++++++++++++++++++++ 6 files changed, 178 insertions(+), 87 deletions(-)
  107. 107. Antes Depois ........ ....... ds Finished indescribe d in 0 .25355 secon she TimeFrame do 0.17938 seco nds Fini 8 examples, before les, 0 failures 7 ex amp do 0 failures @today = DateTime.strptime(06/01/2011, %m/%d/%Y) DateTime.stub!(:now).and_return(@today) end describe "todays event" do it "should use beginning and end of day" do timeframe = TimeFrame.for(today, nil, nil, nil) timeframe.start_date.should == @today.at_beginning_of_day timeframe.end_date.should == @today.end_of_day end Rakefile | 4 ++ end lib/calendar_search_controller.rb | 24 +--------- ... lib/time_frame.rb | 24 ++++++++++end spec/acceptance_tests.rb | 75 +++++++++++++++++++++++++++++++ spec/calendar_search_controller_spec.rb | 75 ++++--------------------------- spec/time_frame_spec.rb | 63 ++++++++++++++++++++++++++ 6 files changed, 178 insertions(+), 87 deletions(-)
  108. 108. O Plano1. Centralizar a lógica de manipulação de datas2. Unificar named scopes3. Extrair lógica do Controller4. Quebrar timeframe em vários pedaços
  109. 109. class TimeFrame attr_reader :start_date, :end_date class Today < TimeFrame def initialize date = DateTime.now @start_date, @end_date = date.at_beginning_of_day, date.end_of_day end end ...enddescribe TimeFrame::Today do it "should use beginning and end of day" do timeframe = TimeFrame::Today.new timeframe.start_date.should == @today.at_beginning_of_day timeframe.end_date.should == @today.end_of_day end ...end
  110. 110. class TimeFrame ... def self.for(type, start_date, end_date, hour) case type when tomorrow then Tomorrow.new when this_week then ThisWeek.new when custom then Custom.new(start_date, end_date) when hour then Hour.new(hour) when today then Today.new end endenddescribe TimeFrame::Today do ... describe "parsing" do it "should parse todays timeframe" do TimeFrame.for(today, nil, nil, nil).shouldbe_an_instance_of(TimeFrame::Today) end ... endend
  111. 111. class TimeFrame ... Antes .def .self.for(type, start_date, end_date, hour) ... ... case type ds 0 .17938 secon when tomorrow then Tomorrow.new Finished in when les, 0 failures ThisWeek.new 8 examp this_week then when custom then Custom.new(start_date, end_date) when hour then Hour.new(hour) when today then Today.new end endenddescribe TimeFrame::Today do ... describe "parsing" do it "should parse todays timeframe" do TimeFrame.for(today, nil, nil, nil).shouldbe_an_instance_of(TimeFrame::Today) end ... endend
  112. 112. class TimeFrame ... Antes Depois ............ def .self.for(type, start_date, end_date, hour) . .... ... case type ds Finished in when d in 0 .17938 secon she tomorrow then Tomorrow.new 0.19023 seco nds Fini 13 examples, when les, 0 failures ThisWeek.new 8 examp this_week then 0 failures when custom then Custom.new(start_date, end_date) when hour then Hour.new(hour) when today then Today.new end endenddescribe TimeFrame::Today do ... describe "parsing" do it "should parse todays timeframe" do TimeFrame.for(today, nil, nil, nil).shouldbe_an_instance_of(TimeFrame::Today) end ... endend
  113. 113. class TimeFrame ... Antes Depois ............ def .self.for(type, start_date, end_date, hour) . .... ... case type ds Finished in when d in 0 .17938 secon she tomorrow then Tomorrow.new 0.19023 seco nds Fini 13 examples, when les, 0 failures ThisWeek.new 8 examp this_week then 0 failures when custom then Custom.new(start_date, end_date) when hour then Hour.new(hour) when today then Today.new end endenddescribe TimeFrame::Today do ... describe "parsing" do lib/time_frame.rb | 59 +++++++++++++++++++++++++++++++++------------- it "should parse todays timeframe" do spec/time_frame_spec.rb | 44 ++++++++++++++++++++++++++-------- TimeFrame.for(today, nil, nil, nil).should 2 files changed, 75 insertions(+), 28 deletions(-)be_an_instance_of(TimeFrame::Today) end ... endend
  114. 114. EstratégiaMecânica
  115. 115. “Big Picture”• Mudanças Arquiteturais• Unificação de abordagens diferentes• Substituição de componentes/frameworks
  116. 116. “Big Picture”
  117. 117. “Big Picture”Escopo Risco Esforço Comunicação
  118. 118. Mikado Method Criar pacote para Stranger Eons Ltda.
  119. 119. Mikado MethodCriar projeto paraStranger Eons Ltda. Criar pacote para Stranger Eons Ltda.
  120. 120. Mikado MethodCriar projeto para Criar teste de aceitaçãoStranger Eons Ltda. para nova aplicação Criar pacote para Stranger Eons Ltda.
  121. 121. Mikado Method Código de UI em projeto separadoCriar projeto para Criar teste de aceitaçãoStranger Eons Ltda. para nova aplicação Criar pacote para Stranger Eons Ltda.
  122. 122. Mikado Method Mover código UI para novo projeto Criar projeto UI Código de UI em projeto separadoCriar projeto para Criar teste de aceitaçãoStranger Eons Ltda. para nova aplicação Criar pacote para Stranger Eons Ltda.
  123. 123. Mikado Method Quebrar dependênciacircular entre Aplicação e UI Mover código UI para novo projeto Criar projeto UI Código de UI em projeto separado Criar projeto para Criar teste de aceitação Stranger Eons Ltda. para nova aplicação Criar pacote para Stranger Eons Ltda.
  124. 124. Mikado MethodExtrair ApplicationInterface Quebrar dependênciacircular entre Aplicação e UI Mover código UI para novo projeto Criar projeto UI Código de UI em projeto separado Criar projeto para Criar teste de aceitação Stranger Eons Ltda. para nova aplicação Criar pacote para Stranger Eons Ltda.
  125. 125. Mikado MethodExtrair ApplicationInterface Injetar instância de ApplicationInterface Quebrar dependênciacircular entre Aplicação e UI Mover código UI para novo projeto Criar projeto UI Código de UI em projeto separado Criar projeto para Criar teste de aceitação Stranger Eons Ltda. para nova aplicação Criar pacote para Stranger Eons Ltda.
  126. 126. Mikado Method ✔Extrair ApplicationInterface Injetar instância de ✔ ApplicationInterface Quebrar dependência ✔circular entre Aplicação e UI Mover código UI para novo projeto Criar projeto UI Código de UI em projeto separado Criar projeto para Criar teste de aceitação Stranger Eons Ltda. para nova aplicação Criar pacote para Stranger Eons Ltda.
  127. 127. Mikado Method ✔Extrair ApplicationInterface Injetar instância de ✔ ApplicationInterface Quebrar dependência ✔circular entre Aplicação e UI Mover código UI para ✔ novo projeto ✔ ✔ Criar projeto UI Código de UI em projeto separado Criar projeto para Criar teste de aceitação Stranger Eons Ltda. para nova aplicação Criar pacote para Stranger Eons Ltda.
  128. 128. Mikado Method ✔Extrair ApplicationInterface Injetar instância de ✔ ApplicationInterface Quebrar dependência ✔circular entre Aplicação e UI Mover código UI para ✔ novo projeto ✔ ✔ Criar projeto UI Código de UI em projeto separado Criar projeto para ✔ Criar teste de aceitação ✔ Stranger Eons Ltda. para nova aplicação ✔ Criar pacote para Stranger Eons Ltda.
  129. 129. Uma história APP
  130. 130. Uma história APPSVC
  131. 131. Uma história APPSVC
  132. 132. Uma história APPSVC
  133. 133. A verdade APP Model View Controller
  134. 134. A verdade APP Model View Controller Helper
  135. 135. A verdade APP Model ViewSVC Controller Helper
  136. 136. A verdade APP Model ViewSVC Controller vai quebrar tudo !!! Helper
  137. 137. Arquitetura de transição APP Model View Controller SVC Helper
  138. 138. Arquitetura de transição APP Model View Controller SVC Helper
  139. 139. Arquitetura de transição APP Model View Controller SVC Helper
  140. 140. Arquitetura de transição APP Model View Controller SVC Helper
  141. 141. Arquitetura de transição APP Model View Controller SVC Helper
  142. 142. Arquitetura de transição APP Model View Controller SVC Helper
  143. 143. Arquitetura de Transição• Go: • “Branch by Abstraction” • Migrar de iBatis para Hibernate • Migrar de Velocity/JsTemplate para JRuby on Rails
  144. 144. Arquitetura de Transição • Go: • “Branch by Abstraction” • Migrar de iBatis para Hibernate • Migrar de Velocity/JsTemplate para JRuby on Rails - 0 11/05/make-largehttp://continuo usdelivery.com/2 - l y-with-branch-byscale-cha nges-incrementalabstraction/
  145. 145. ReferênciasLivros:"Refactoring: Improving the Design of Existing Code" - Martin Fowler, Kent Beck, JohnBrant, William Opdyke, Don Roberts"Refactoring to Patterns" - Joshua Kerievsky"Working Effectively with Legacy Code" - Michael Feathers"Beheading the Software Beast: Relentless Restructurings with The Mikado Method" -Daniel Brolund, Ola EllnestamCréditos (fotos):http://www.flickr.com/photos/dhammza/2227347832/http://www.flickr.com/photos/pragdave/173640462/Links:http://paulhammant.com/2011/09/01/refactoring-experimenthttp://continuousdelivery.com/2011/05/make-large-scale-changes-incrementally-with-branch-by-abstraction/Código:https://github.com/dtsato/refactoring_experimenthttps://github.com/dtsato/kata-refactoring-calendar
  146. 146. S etembro (18h): Segun da-Feira, 12 de / join-revolutionhttp://thought works.com/events
  147. 147. Obrigado!Danilo Sato - @dtsato ThoughtWorks www.dtsato.com

×