Refactoring at Large

2,733
-1

Published on

Slides from my session at the Atlanta Software Craftsmanship meetup 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
0 Comments
7 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
2,733
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
50
Comments
0
Likes
7
Embeds 0
No embeds

No notes for slide
  • \n
  • Developer, Agile Coach, Tech Lead, DevOps, Trainer\n
  • Developer, Agile Coach, Tech Lead, DevOps, Trainer\n
  • Developer, Agile Coach, Tech Lead, DevOps, Trainer\n
  • Developer, Agile Coach, Tech Lead, DevOps, Trainer\n
  • Developer, Agile Coach, Tech Lead, DevOps, Trainer\n
  • Developer, Agile Coach, Tech Lead, DevOps, Trainer\n
  • Developer, Agile Coach, Tech Lead, DevOps, Trainer\n
  • Assumptions:\n- You’re not in a complete mess\n- You have tests\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
  • Cobertura 100%\nApplicação Java/Spring\n
  • \n
  • Objetivo: extrair placeOrder para outro controller\n
  • Extracting placeOrder() into a new OrderController class, and a new OrderControllerTest.\nNote: Controller Annotation is copy/pasted\n
  • Note: Controller name first typo (Products -> SuggestedProducts)\n\n
  • Extracting Upseller collaborator\nNote: Controller annotation needs to be copy/pasted\n
  • \n
  • Não tem segredo para identificar objetivo (NFRs)\n
  • Não tem segredo para identificar objetivo (NFRs)\n
  • Não tem segredo para identificar objetivo (NFRs)\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
  • Líder Técnico: compartilha a visão\nWorkshops de arquitetura presente vs. futura\nRequires a disciplined approach to tackling large changes\n
  • Líder Técnico: compartilha a visão\nWorkshops de arquitetura presente vs. futura\nRequires a disciplined approach to tackling large changes\n
  • Líder Técnico: compartilha a visão\nWorkshops de arquitetura presente vs. futura\nRequires a disciplined approach to tackling large changes\n
  • Líder Técnico: compartilha a visão\nWorkshops de arquitetura presente vs. futura\nRequires a disciplined approach to tackling large changes\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Escolha objetivo, comece de forma inocente, aprenda e desenho um grafo, rollback quando está quebrado, trabalhe das folhas para a raíz\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \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
  • Refactoring at Large

    1. Refactoring at Large Danilo Sato - @dtsato ThoughtWorks www.dtsato.com
    2. My Plan for Today+ • “Inside out” approach • Show me Code (Java) • Strategies and Objectives • Refactoring Ruby/Rails- • Larger refactorings
    3. Why do we change code?
    4. Why do we change code?• Add features
    5. Why do we change code?• Add features• Fix bugs
    6. Why do we change code?• Add features• Fix bugs• Improve design
    7. Why do we change code?• Add features• Fix bugs• Improve design• Optimization
    8. Why do we change code?• Add features• Fix bugs• Improve design• Optimization
    9. "Refactoring is a disciplined technique for restructuring anexisting body of code, altering its internal structure withoutchanging its external behavior."
    10. Refactoring Code
    11. Refactoring Code
    12. Refactoring Code Extr act Method
    13. Refactoring Code
    14. Refactoring Code
    15. Refactoring Code Eliminate Duplication
    16. • Extract Method• Move Method• Extract Class• Remove Middle Man• Self Encapsulate Field• Decompose Conditional• ...
    17. Tests! CodeTest
    18. Tests! CodeTest
    19. Tests! CodeTest Eliminate Duplication
    20. Other types... CodeTest
    21. Other types... Code CodeTest
    22. Other types... Code CodeTest Test
    23. Other types... Code CodeTest Test Split Responsibility
    24. Other types... CodeTest
    25. Other types...Test Code
    26. Other types...Test Code
    27. Other types...Test Code Change Design
    28. Even more...
    29. “Big Picture”
    30. “Big Picture”
    31. “Big Picture”
    32. “Big Picture”
    33. “Big Picture”
    34. StrategyMechanics
    35. Coding Kata!
    36. Coding Kata!
    37. CheckoutControllerTest CheckoutController Order Cart
    38. - 1 2/10/refactoring o.com/blog/2011/http://www.dtsat / h rough-experimentst rategies-a-walkt - 0 9/01/refactoringhttp://paulh ammant.com/2011/experiment CheckoutControllerTest CheckoutController Order Cart
    39. 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]]}}}")); }}
    40. @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"; }}
    41. Mechanics t his video on: riment Watch factoring-expe com/dt sato/re p://vimeo.htt
    42. First Attempt... t his video on: riment Watch factoring-expe com/dt sato/re p://vimeo.htt
    43. Strategy t his video on: riment Watch factoring-expe com/dt sato/re p://vimeo.htt
    44. Identify your Objective
    45. Identify your Objective
    46. Identify your Objective “Active” design
    47. Identify your Objective “Active” design• OO Design
    48. Identify your Objective “Active” design• OO Design• Project direction
    49. Identify your Objective “Active” design• OO Design• Project direction• Architectural decisions
    50. Identify your Objective “Passive” design
    51. Identify your Objective “Passive” design• Bad Smells
    52. Identify your Objective “Passive” design• Bad Smells• Metrics
    53. Identify your Objective “Passive” design• Bad Smells• Metrics• Visualizations
    54. Strategize
    55. Strategize ? ??
    56. Strategize
    57. Strategize• Isolate the impact of Change
    58. Strategize• Isolate the impact of Change• Baby Steps
    59. Strategize• Isolate the impact of Change• Baby Steps• Keep the tests green
    60. Strategize• Isolate the impact of Change• Baby Steps• Keep the tests green• Moving backwards before moving forward
    61. CalendarSearchController CalendarSearchController Test Event Event
    62. r dtsato/kata-ref actoring-calendahttp ://github.com/ CalendarSearchController CalendarSearchController Test Event Event
    63. 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
    64. 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
    65. 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
    66. 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 multiple named scopes @events = scope.allend
    67. 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
    68. different ways to do the sameclass Event < ActiveRecord::Base thing 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
    69. 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} } } more logic 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
    70. ObjectivesActive: • Isolate unit tests from the databasePassive: • Deal with bad smells
    71. Strategizing1. Centralize date manipulation logic2. Unify named scopes3. Extract logic from Controller4. Break timeframe into separate pieces
    72. 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
    73. class Event < ActiveRecord::Base named_scope :between_dates, lambda { |start_date, end_date| {:conditions => {:at => start_date..end_date}} }end
    74. 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(-)
    75. Before . ............ 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(-)
    76. Before ....... After . ............ 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(-)
    77. Strategizing1. Centralize date manipulation logic2. Unify named scopes3. Extract logic from Controller4. Break timeframe into separate pieces
    78. 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 change to tests: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
    79. 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
    80. describe "Acceptance tests" do around(:each) do |example| After ...... 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
    81. 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
    82. 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
    83. 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(-)
    84. Before ....... 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(-)
    85. Before ........ After ....... 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(-)
    86. Strategizing1. Centralize date manipulation logic2. Unify named scopes3. Extract logic from Controller4. Break timeframe into separate pieces
    87. 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
    88. 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
    89. class TimeFrame ... Before .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
    90. class TimeFrame ... Before ............ After 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
    91. class TimeFrame ... Before ............ After 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
    92. “Big Picture”• Architectural Changes• Consolidating different approaches• Substituting components/frameworks
    93. “Big Picture”
    94. “Big Picture”Scope Risk Effort Communication
    95. Change
    96. x x x x x x x Change x xx x
    97. x x x x x x x Change x xx x
    98. x x x x x x x x x xx x Changexxx x xx x
    99. x x xx x x x x x x x x x x xx x Changexxx x xx x
    100. x x xx x x x x x x x x x x xx x Changexxx x xx x xx xx x x xxx xx x
    101. x x x x xx x x xx x x x x x x x x x x x x x xx x x Changexxx x x x xx x x x x xx xx x x x x x xxx xx x x x x x
    102. Change
    103. x x x x x x x Change x xx x
    104. x x x x x x x Change x xx x
    105. xPrereq x x x x x x Change x xx x
    106. xPrereq x x x Prereq x x x Change x xx x
    107. xPrereq x x x Prereq x x x Change x xx Prereq x
    108. Prereq Prereq Change Prereq
    109. x x Prereq Prereq xx x Changexxx Prereq
    110. x Prereq x Prereq Prereq xx x Prereq Changexxx Prereq
    111. Prereq Prereq PrereqPrereq Change Prereq
    112. Prereq Prereq PrereqPrereq Change Prereq
    113. Prereq Prereq PrereqPrereq ✔ Change Prereq
    114. Prereq Prereq PrereqPrereq ✔ Change Prereq
    115. Prereq ✔ Prereq PrereqPrereq ✔ Change Prereq
    116. Prereq ✔ Prereq ✔ PrereqPrereq ✔ Change Prereq
    117. Prereq ✔ Prereq ✔ PrereqPrereq ✔ Change Prereq
    118. Prereq ✔ Prereq ✔ Prereq ✔Prereq ✔ Change Prereq
    119. Prereq ✔ Prereq ✔ Prereq ✔Prereq ✔ Change Prereq
    120. Prereq ✔ Prereq ✔ Prereq ✔Prereq ✔ Change Prereq ✔
    121. Prereq ✔ Prereq ✔ Prereq ✔Prereq ✔ Change ✔ Prereq ✔
    122. A story APP
    123. A story APPSVC
    124. A story APPSVC
    125. A story APPSVC
    126. The truth APP Model View Controller
    127. The truth APP Model View Controller Helper
    128. The truth APP Model ViewSVC Controller Helper
    129. The truth APP Model ViewSVC Controller everything is going to break !!! Helper
    130. TransitionalArchitecture APP Model View ControllerSVC Helper
    131. TransitionalArchitecture APP Model View ControllerSVC Helper
    132. TransitionalArchitecture APP Model View ControllerSVC Helper
    133. TransitionalArchitecture APP Model View ControllerSVC Helper
    134. TransitionalArchitecture APP Model View ControllerSVC Helper
    135. TransitionalArchitecture APP Model View ControllerSVC Helper
    136. Transitional Architecture• Go: • “Branch by Abstraction” • Migrating from iBatis to Hibernate • Migrating from Velocity/JsTemplate to JRuby on Rails
    137. Transitional Architecture • Go: • “Branch by Abstraction” • Migrating from iBatis to Hibernate • Migrating from Velocity/JsTemplate to JRuby on Rails - 0 11/05/make-largehttp://continuo usdelivery.com/2 - l y-with-branch-byscale-cha nges-incrementalabstraction/
    138. ReferencesBooks:"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 EllnestamCredits (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/Code:https://github.com/dtsato/refactoring_experimenthttps://github.com/dtsato/kata-refactoring-calendar
    139. Obrigado!Danilo Sato - @dtsato ThoughtWorks www.dtsato.com
    1. A particular slide catching your eye?

      Clipping is a handy way to collect important slides you want to go back to later.

    ×