Refatoração em Larga Escala

3,889 views
3,819 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
0 Comments
11 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
3,889
On SlideShare
0
From Embeds
0
Number of Embeds
108
Actions
Shares
0
Downloads
0
Comments
0
Likes
11
Embeds 0
No embeds

No notes for slide
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • zoom out\n
  • zoom out\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Mecânica: IDE resolve, ferramentas, catálogos...\n
  • Exercícios para praticar\nMelhoram uma habilidade específica: TDD, Algoritmos, Design, Código ruim? etc...\n
  • \n
  • Cobertura 100%\nApplicação Java/Spring\n
  • \n
  • Objetivo: extrair placeOrder para outro controller\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Foco nível um pouco mais alto\n
  • \n
  • Não tem segredo para identificar objetivo\n
  • Não tem segredo para identificar objetivo\n
  • Não tem segredo para identificar objetivo\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Michael Feathers: Effect Sketches\nCluster de métodos/atributos\nMova código repetido para um mesmo lugar\n
  • Michael Feathers: Effect Sketches\nCluster de métodos/atributos\nMova código repetido para um mesmo lugar\n
  • Michael Feathers: Effect Sketches\nCluster de métodos/atributos\nMova código repetido para um mesmo lugar\n
  • Michael Feathers: Effect Sketches\nCluster de métodos/atributos\nMova código repetido para um mesmo lugar\n
  • Aplicação Rails. Cobertura 100%\nForm de busca de eventos\nEm Ruby -> não tem mesmas ferramentas avançadas de refatoração\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • * Escopo único: between_dates\n* Mais feio (indo para trás antes de ir para frente)\n* Isolando mudança (toda a lógica de datas num único lugar)\n
  • * Remoção de código > Adição de código\n* Testes mais rápidos pois os testes dos 3 named scopes acessavam o BD\n
  • * Remoção de código > Adição de código\n* Testes mais rápidos pois os testes dos 3 named scopes acessavam o BD\n
  • * Remoção de código > Adição de código\n* Testes mais rápidos pois os testes dos 3 named scopes acessavam o BD\n
  • \n
  • Skinny Controller\nTestes do Controller usam mocks para testar colaboração\n
  • Testes do controller moveram para teste de aceitação (cucumber?)\n\n
  • * Timeframe só contêm start/end\n* Lógica ainda é feia\n
  • * Teste não acessa mais o BD!\n* Adição de muitos testes\n* Pouca mudança no código em si\n
  • * Teste não acessa mais o BD!\n* Adição de muitos testes\n* Pouca mudança no código em si\n
  • * Teste não acessa mais o BD!\n* Adição de muitos testes\n* Pouca mudança no código em si\n
  • \n
  • * Separação de subclasses em TimeFrame para remover switch/case\n* Testes mudam junto\n
  • * Factory method ainda tem switch/case (discutir possível uso de metaprogramação)\n* Redução total dos testes unitários: de 0.4936 para 0.19023 (61%)\n
  • * Factory method ainda tem switch/case (discutir possível uso de metaprogramação)\n* Redução total dos testes unitários: de 0.4936 para 0.19023 (61%)\n
  • * Factory method ainda tem switch/case (discutir possível uso de metaprogramação)\n* Redução total dos testes unitários: de 0.4936 para 0.19023 (61%)\n
  • \n
  • \n
  • Líder Técnico: compartilha a visão\nWorkshops de arquitetura presente vs. futura\n
  • Líder Técnico: compartilha a visão\nWorkshops de arquitetura presente vs. futura\n
  • Líder Técnico: compartilha a visão\nWorkshops de arquitetura presente vs. futura\n
  • Líder Técnico: compartilha a visão\nWorkshops de arquitetura presente vs. futura\n
  • Escolha objetivo, comece de forma inocente, aprenda e desenho um grafo, rollback quando está quebrado, trabalhe das folhas para a raíz\n
  • 1. Rollback ao decidir separar o projeto UI\n2. Dependência circular entre Aplicação e UI\n
  • 1. Rollback ao decidir separar o projeto UI\n2. Dependência circular entre Aplicação e UI\n
  • 1. Rollback ao decidir separar o projeto UI\n2. Dependência circular entre Aplicação e UI\n
  • 1. Rollback ao decidir separar o projeto UI\n2. Dependência circular entre Aplicação e UI\n
  • 1. Rollback ao decidir separar o projeto UI\n2. Dependência circular entre Aplicação e UI\n
  • 1. Rollback ao decidir separar o projeto UI\n2. Dependência circular entre Aplicação e UI\n
  • 1. Rollback ao decidir separar o projeto UI\n2. Dependência circular entre Aplicação e UI\n
  • 1. Rollback ao decidir separar o projeto UI\n2. Dependência circular entre Aplicação e UI\n
  • 1. Rollback ao decidir separar o projeto UI\n2. Dependência circular entre Aplicação e UI\n
  • 1. Rollback ao decidir separar o projeto UI\n2. Dependência circular entre Aplicação e UI\n
  • \n
  • \n
  • \n
  • Exemplo\n
  • Exemplo\n
  • Exemplo\n
  • Exemplo\n
  • Exemplo\n
  • Exemplo\n
  • Exemplo\n
  • Exemplo\n
  • Exemplo\n
  • Exemplo\n
  • Exemplo\n
  • Exemplo\n
  • Exemplo\n
  • Exemplo\n
  • \n
  • \n
  • \n
  • \n
  • 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

    ×