Surgical Refactors

E6c6e133e74c3b83f04d2861deaa1c20?s=47 Justin Searls
September 09, 2016

Surgical Refactors

As presented on September 9th at RubyKaigi 2016 in Kyoto, Japan.
Video here: http://blog.testdouble.com/posts/2016-09-16-surgical-refactors-with-suture

E6c6e133e74c3b83f04d2861deaa1c20?s=128

Justin Searls

September 09, 2016
Tweet

Transcript

  1. 1.
  2. 8.
  3. 16.
  4. 17.

    4 3

  5. 18.
  6. 20.

    3 3

  7. 21.
  8. 40.

    Refactor - verb To change the design of code without

    changing its observable behavior.
  9. 41.

    Refactor - verb To change in advance of a new

    feature or bug fix, making the job easier.
  10. 57.
  11. 79.
  12. 89.

    1. Scare them! "If we don't refactor, then . !"

    to rewrite everything someday we'll need
  13. 90.

    1. Scare them! "If we don't refactor, then . !"

    to rewrite everything someday we'll need Far in the future
  14. 91.

    1. Scare them! "If we don't refactor, then . !"

    costs will be much higher your maintenance
  15. 92.

    1. Scare them! "If we don't refactor, then . !"

    costs will be much higher your maintenance Hard to quantify
  16. 109.

    3. Take hostages Feature #1 Feature #2 Blames business for

    rushing Technical Debt Technical.Debt
  17. 110.

    3. Take hostages Feature #1 Feature #2 Erodes trust in

    the team Technical Debt Technical.Debt
  18. 130.

    1. Refactoring Patterns • Extract method • Pull up /

    push down • Split loop Safer with good tools
  19. 131.
  20. 170.

    3. A/B Testing / Experiments Old code New code if

    rand < 0.2 false true Rewriting in big steps is confusing & error-prone
  21. 171.

    3. A/B Testing / Experiments Old code New code if

    rand < 0.2 false true Heavy monitoring & analysis required
  22. 172.

    3. A/B Testing / Experiments Old code New code if

    rand < 0.2 false true Experimenting on humans is risky
  23. 173.
  24. 175.
  25. 193.

  26. 195.
  27. 196.
  28. 197.

  29. 199.

    TDD

  30. 201.
  31. 203.
  32. 219.

    9

  33. 221.
  34. 222.
  35. 230.
  36. 234.
  37. 235.
  38. 236.
  39. 237.
  40. 245.
  41. 246.

    class Controller def show calc = Calculator.new @result = calc.add(

    params[:left], params[:right] ) end end We will create our "seam" here
  42. 254.

    class Calculator attr_reader :total def tally(n) @total ||= 0 n.downto(0)

    do |i| if i * 2 == n @total += i * 2 end end return end end
  43. 255.

    class Calculator attr_reader :total def tally(n) @total ||= 0 n.downto(0)

    do |i| if i * 2 == n @total += i * 2 end end return end end
  44. 256.

    class Calculator attr_reader :total def tally(n) @total ||= 0 n.downto(0)

    do |i| if i * 2 == n @total += i * 2 end end return end end
  45. 257.

    class Calculator attr_reader :total def tally(n) @total ||= 0 n.downto(0)

    do |i| if i * 2 == n @total += i * 2 end end return end end
  46. 258.

    class Calculator attr_reader :total def tally(n) @total ||= 0 n.downto(0)

    do |i| if i * 2 == n @total += i * 2 end end return end end
  47. 259.

    class Calculator attr_reader :total def tally(n) @total ||= 0 n.downto(0)

    do |i| if i * 2 == n @total += i * 2 end end return end end
  48. 260.

    class Calculator attr_reader :total def tally(n) @total ||= 0 n.downto(0)

    do |i| if i * 2 == n @total += i * 2 end end return end end
  49. 263.

    class Controller def index calc = Calculator.new params[:nums].each {|n| calc.tally(n)

    } @result = calc.total end end This seam is more complex
  50. 265.

    Cut

  51. 267.
  52. 268.
  53. 273.

    calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), args:

    [ params[:left], params[:right] ] :old must respond_to?(:call)
  54. 275.

    calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), args:

    [ params[:left], params[:right] ] Initially a no-op; verify it still works
  55. 279.

    calc = Calculator.new params[:nums].each {|n| calc.tally(n) :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total
  56. 280.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total
  57. 281.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total
  58. 282.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total Wait, calc isn't an arg!
  59. 303.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total
  60. 304.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total Broaden the seam
  61. 305.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total
  62. 306.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total
  63. 307.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total
  64. 308.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total Return a value
  65. 309.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, args: [calc, n] } @result = calc.total
  66. 311.
  67. 314.

    calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), args:

    [ params[:left], params[:right] ], record_calls: true
  68. 315.

    calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), args:

    [ params[:left], params[:right] ], record_calls: true
  69. 316.

    calc = Calculator.new @result = Suture.create :add, old: calc.method(:add), args:

    [ params[:left], params[:right] ], record_calls: true Most options support ENV: SUTURE_RECORD_CALLS=true
  70. 319.

    controller = Controller.new controller.params = {:left => 5, :right =>

    6} controller.show controller.params = {:left => 3, :right => 2} controller.show controller.params = {:left => 1, :right => 89} controller.show
  71. 320.

    controller = Controller.new controller.params = {:left => 5, :right =>

    6} controller.show controller.params = {:left => 3, :right => 2} controller.show controller.params = {:left => 1, :right => 89} controller.show
  72. 321.

    controller = Controller.new controller.params = {:left => 5, :right =>

    6} controller.show controller.params = {:left => 3, :right => 2} controller.show controller.params = {:left => 1, :right => 89} controller.show
  73. 322.

    controller = Controller.new controller.params = {:left => 5, :right =>

    6} controller.show controller.params = {:left => 3, :right => 2} controller.show controller.params = {:left => 1, :right => 89} controller.show
  74. 323.

    controller = Controller.new controller.params = {:left => 5, :right =>

    6} controller.show controller.params = {:left => 3, :right => 2} controller.show controller.params = {:left => 1, :right => 89} controller.show
  75. 324.

    controller = Controller.new controller.params = {:left => 5, :right =>

    6} controller.show controller.params = {:left => 3, :right => 2} controller.show controller.params = {:left => 1, :right => 89} controller.show
  76. 325.

    controller = Controller.new controller.params = {:left => 5, :right =>

    6} controller.show controller.params = {:left => 3, :right => 2} controller.show controller.params = {:left => 1, :right => 89} controller.show
  77. 326.

    controller = Controller.new controller.params = {:left => 5, :right =>

    6} controller.show controller.params = {:left => 3, :right => 2} controller.show controller.params = {:left => 1, :right => 89} controller.show
  78. 334.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, args: [calc, n], record_calls: true } @result = calc.total
  79. 335.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, args: [calc, n], record_calls: true } @result = calc.total
  80. 336.

    controller = Controller.new controller.params = {nums: [2,4,6]} controller.index controller.params =

    {nums: [10,20,30]} controller.index controller.params = {nums: [4,11]} controller.index controller.params = {nums: [1,3,5,7,9]} controller.index
  81. 337.

    controller = Controller.new controller.params = {nums: [2,4,6]} controller.index controller.params =

    {nums: [10,20,30]} controller.index controller.params = {nums: [4,11]} controller.index controller.params = {nums: [1,3,5,7,9]} controller.index
  82. 338.

    controller = Controller.new controller.params = {nums: [2,4,6]} controller.index controller.params =

    {nums: [10,20,30]} controller.index controller.params = {nums: [4,11]} controller.index controller.params = {nums: [1,3,5,7,9]} controller.index
  83. 339.

    controller = Controller.new controller.params = {nums: [2,4,6]} controller.index controller.params =

    {nums: [10,20,30]} controller.index controller.params = {nums: [4,11]} controller.index controller.params = {nums: [1,3,5,7,9]} controller.index
  84. 340.

    controller = Controller.new controller.params = {nums: [2,4,6]} controller.index controller.params =

    {nums: [10,20,30]} controller.index controller.params = {nums: [4,11]} controller.index controller.params = {nums: [1,3,5,7,9]} controller.index
  85. 341.

    controller = Controller.new controller.params = {nums: [2,4,6]} controller.index controller.params =

    {nums: [10,20,30]} controller.index controller.params = {nums: [4,11]} controller.index controller.params = {nums: [1,3,5,7,9]} controller.index
  86. 342.

    controller = Controller.new controller.params = {nums: [2,4,6]} controller.index controller.params =

    {nums: [10,20,30]} controller.index controller.params = {nums: [4,11]} controller.index controller.params = {nums: [1,3,5,7,9]} controller.index
  87. 343.

    controller = Controller.new controller.params = {nums: [2,4,6]} controller.index controller.params =

    {nums: [10,20,30]} controller.index controller.params = {nums: [4,11]} controller.index controller.params = {nums: [1,3,5,7,9]} controller.index
  88. 344.

    controller = Controller.new controller.params = {nums: [2,4,6]} controller.index controller.params =

    {nums: [10,20,30]} controller.index controller.params = {nums: [4,11]} controller.index controller.params = {nums: [1,3,5,7,9]} controller.index
  89. 353.
  90. 354.

    require "suture" class ItemsController < ApplicationController def update_all Item.all.each do

    |item| Suture.create :gilded_rose, :old => lambda { |item| item.update_quality! item }, :args => [item], :record_calls => true end redirect_to items_path end end
  91. 355.
  92. 356.
  93. 357.
  94. 358.
  95. 359.
  96. 360.
  97. 361.
  98. 362.
  99. 363.
  100. 364.
  101. 365.
  102. 366.
  103. 367.
  104. 368.
  105. 369.
  106. 370.
  107. 373.
  108. 395.
  109. 396.
  110. 407.
  111. 410.
  112. 414.
  113. 418.

    class Calculator def add(left, right) right.times do left += 1

    end left end end Doesn't work for negative values!
  114. 419.
  115. 420.
  116. 421.
  117. 422.
  118. 423.
  119. 424.

    class Calculator def new_add(left, right) return left if right <

    0 # ^ FIXME later left + right end end Retain current behavior exactly, bugs & all
  120. 425.

    class Calculator def new_add(left, right) return left if right <

    0 # ^ FIXME later left + right end end We don't know what else depends on bad behavior
  121. 427.

    class Calculator attr_reader :total def tally(n) @total ||= 0 n.downto(0)

    do |i| if i * 2 == n @total += i * 2 end end return end end
  122. 428.

    class Calculator attr_reader :total def tally(n) @total ||= 0 n.downto(0)

    do |i| if i * 2 == n @total += i * 2 end end return end end Skips odd values!
  123. 429.

    class Calculator def new_tally(n) return if n.odd? # ^ FIXME

    later @total ||= 0 @total += n return end end
  124. 430.

    class Calculator def new_tally(n) return if n.odd? # ^ FIXME

    later @total ||= 0 @total += n return end end
  125. 431.

    class Calculator def new_tally(n) return if n.odd? # ^ FIXME

    later @total ||= 0 @total += n return end end
  126. 432.

    class Calculator def new_tally(n) return if n.odd? # ^ FIXME

    later @total ||= 0 @total += n return end end
  127. 433.

    class Calculator def new_tally(n) return if n.odd? # ^ FIXME

    later @total ||= 0 @total += n return end end Still returns nil
  128. 434.

    class Calculator def new_tally(n) return if n.odd? # ^ FIXME

    later @total ||= 0 @total += n return end end
  129. 435.

    class Calculator def new_tally(n) return if n.odd? # ^ FIXME

    later @total ||= 0 @total += n return end end
  130. 436.

    class Calculator def new_tally(n) return if n.odd? # ^ FIXME

    later @total ||= 0 @total += n return end end
  131. 437.

    class Calculator def new_tally(n) return if n.odd? # ^ FIXME

    later @total ||= 0 @total += n return end end "Make the change easy, then make the easy change" - Beck
  132. 439.
  133. 456.
  134. 457.
  135. 458.

    # Verification of your seam failed! Descriptions of each unsuccessful

    verification follows: ## Failures 1.) Recorded call for seam :tally (ID: 13) ran and failed comparison. Arguments: ``` [<Calculator:@total=nil>, 1] ``` Expected returned value: ``` 0 ``` Actual returned value: ``` nil ``` Ideas to fix this: * Focus on this test by setting ENV var `SUTURE_VERIFY_ONLY=13` * Is the recording wrong? Delete it! `Suture.delete!(13)`
  136. 459.

    1.) Recorded call for seam :tally (ID: 13) ran and

    failed comparison. Arguments: ``` [<Calculator:@total=nil>, 1] ``` Expected returned value: ``` 0 ``` Actual returned value: ``` nil ```
  137. 460.

    Ideas to fix this: * Focus on this test by

    setting ENV var `SUTURE_VERIFY_ONLY=13` * Is the recording wrong? Delete it! `Suture.delete!(13)`
  138. 461.

    Ideas to fix this: * Focus on this test by

    setting ENV var `SUTURE_VERIFY_ONLY=13` * Is the recording wrong? Delete it! `Suture.delete!(13)` Only run this failure
  139. 462.

    Ideas to fix this: * Focus on this test by

    setting ENV var `SUTURE_VERIFY_ONLY=13` * Is the recording wrong? Delete it! `Suture.delete!(13)` Delete bad recordings
  140. 464.

    ### Fixing these failures #### Custom comparator If any comparison

    is failing and you believe the results are equivalent, we suggest you look into creating a custom comparator. See more details here: https://github.com/testdouble/suture#creating-a-custom-comparator #### Random seed Suture runs all verifications in random order by default. If you're seeing an erratic failure, it's possibly due to order-dependent behavior somewhere in your subject's code. To re-run the tests with the same random seed as was used in this run, set the env var `SUTURE_RANDOM_SEED=74735` or the config entry `:random_seed => 74735`. To re-run the tests without added shuffling (that is, in the order the calls were recorded in), then set the random seed explicitly to nil with env var `SUTURE_RANDOM_SEED=nil` or the config entry `:random_seed => nil`.
  141. 465.

    ### Fixing these failures #### Custom comparator If any comparison

    is failing and you believe the results are equivalent, we suggest you look into creating a custom comparator. See more details here: https://github.com/testdouble/ suture#creating-a-custom-comparator
  142. 468.

    ==

  143. 469.

    or

  144. 473.
  145. 474.

    !=

  146. 475.

    !=

  147. 476.

    !=

  148. 478.
  149. 479.
  150. 480.
  151. 481.
  152. 482.
  153. 483.
  154. 484.
  155. 486.

    class CalcPare < Suture::Comparator def call(left, right) if super then

    return true end left.total == right.total end end Suture.verify :tally, subject: ->(calc, n) { calc.new_tally(n) calc.total }, comparator: CalcPare.new
  156. 487.

    class CalcPare < Suture::Comparator def call(left, right) if super then

    return true end left.total == right.total end end Suture.verify :tally, subject: ->(calc, n) { calc.new_tally(n) calc.total }, comparator: CalcPare.new
  157. 488.

    class CalcPare < Suture::Comparator def call(left, right) return true if

    super left.total == right.total end end Suture.verify :tally, subject: ->(calc, n) { calc.new_tally(n) calc.total }, comparator: CalcPare.new
  158. 489.

    class CalcPare < Suture::Comparator def call(left, right) return true if

    super left.total == right.total end end Suture.verify :tally, subject: ->(calc, n) { calc.new_tally(n) calc.total }, comparator: CalcPare.new
  159. 490.

    class CalcPare < Suture::Comparator def call(left, right) return true if

    super left.total == right.total end end Suture.verify :tally, subject: ->(calc, n) { calc.new_tally(n) calc.total }, comparator: CalcPare.new
  160. 491.

    class CalcPare < Suture::Comparator def call(left, right) return true if

    super left.total == right.total end end Suture.verify :tally, subject: ->(calc, n) { calc.new_tally(n) calc.total }, comparator: CalcPare.new
  161. 493.

    #### Random seed Suture runs all verifications in random order

    by default. If you're seeing an erratic failure, it's possibly due to order-dependent behavior somewhere in your subject's code. To re-run the tests with the same random seed as was used in this run, set the env var `SUTURE_RANDOM_SEED=74735` or the config entry `:random_seed => 74735`. To re-run the tests without added shuffling (that is, in the order the calls were recorded in), then set the random seed explicitly to nil with env var `SUTURE_RANDOM_SEED=nil` or the config entry `:random_seed => nil`.
  162. 494.

    #### Random seed Suture runs all verifications in random order

    by default. If you're seeing an erratic failure, it's possibly due to order-dependent behavior somewhere in your subject's code. To re-run the tests with the same random seed as was used in this run, set the env var `SUTURE_RANDOM_SEED=74735` or the config entry `:random_seed => 74735`. To re-run the tests without added shuffling (that is, in the order the calls were recorded in), then set the random seed explicitly to nil with env var `SUTURE_RANDOM_SEED=nil` or the config entry `:random_seed => nil`.
  163. 495.

    #### Random seed Suture runs all verifications in random order

    by default. If you're seeing an erratic failure, it's possibly due to order-dependent behavior somewhere in your subject's code. To re-run the tests with the same random seed as was used in this run, set the env var `SUTURE_RANDOM_SEED=74735` or the config entry `:random_seed => 74735`. To re-run the tests without added shuffling (that is, in the order the calls were recorded in), then set the random seed explicitly to nil with env var `SUTURE_RANDOM_SEED=nil` or the config entry `:random_seed => nil`.
  164. 497.

    # Configuration This is the configuration used by this test

    run: ``` { :comparator => Suture::Comparator.new, :database_path => "db/suture.sqlite3", :fail_fast => false, :call_limit => nil, # (no limit) :time_limit => nil, # (no limit) :error_message_limit => nil, # (no limit) :random_seed => 74735 } ```
  165. 498.

    # Configuration This is the configuration used by this test

    run: ``` { :comparator => Suture::Comparator.new, :database_path => "db/suture.sqlite3", :fail_fast => false, :call_limit => nil, # (no limit) :time_limit => nil, # (no limit) :error_message_limit => nil, # (no limit) :random_seed => 74735 } ```
  166. 499.

    # Configuration This is the configuration used by this test

    run: ``` { :comparator => Suture::Comparator.new, :database_path => "db/suture.sqlite3", :fail_fast => false, :call_limit => nil, # (no limit) :time_limit => nil, # (no limit) :error_message_limit => nil, # (no limit) :random_seed => 74735 } ```
  167. 500.

    # Configuration This is the configuration used by this test

    run: ``` { :comparator => Suture::Comparator.new, :database_path => "db/suture.sqlite3", :fail_fast => false, :call_limit => nil, # (no limit) :time_limit => nil, # (no limit) :error_message_limit => nil, # (no limit) :random_seed => 74735 } ```
  168. 501.

    # Configuration This is the configuration used by this test

    run: ``` { :comparator => Suture::Comparator.new, :database_path => "db/suture.sqlite3", :fail_fast => false, :call_limit => nil, # (no limit) :time_limit => nil, # (no limit) :error_message_limit => nil, # (no limit) :random_seed => 74735 } ```
  169. 502.

    # Configuration This is the configuration used by this test

    run: ``` { :comparator => Suture::Comparator.new, :database_path => "db/suture.sqlite3", :fail_fast => false, :call_limit => nil, # (no limit) :time_limit => nil, # (no limit) :error_message_limit => nil, # (no limit) :random_seed => 74735 } ```
  170. 503.

    # Configuration This is the configuration used by this test

    run: ``` { :comparator => Suture::Comparator.new, :database_path => "db/suture.sqlite3", :fail_fast => false, :call_limit => nil, # (no limit) :time_limit => nil, # (no limit) :error_message_limit => nil, # (no limit) :random_seed => 74735 } ```
  171. 504.

    # Configuration This is the configuration used by this test

    run: ``` { :comparator => Suture::Comparator.new, :database_path => "db/suture.sqlite3", :fail_fast => false, :call_limit => nil, # (no limit) :time_limit => nil, # (no limit) :error_message_limit => nil, # (no limit) :random_seed => 74735 } ```
  172. 506.

    # Result Summary - Passed........12 - Failed........1 - with error..0

    - Skipped.......0 - Total calls...13 ## Progress Here's what your progress to initial completion looks like so far: [••••••••••••••••••••••••••••••••••••••◍◌◌◌◌] Of 13 recorded interactions, 12 are currently passing. That's 92%!
  173. 507.

    # Result Summary - Passed........12 - Failed........1 - with error..0

    - Skipped.......0 - Total calls...13 ## Progress Here's what your progress to initial completion looks like so far: [••••••••••••••••••••••••••••••••••••••◍◌◌◌◌] Of 13 recorded interactions, 12 are currently passing. That's 92%!
  174. 510.

    1.) Recorded call for seam :tally (ID: 13) ran and

    failed comparison. Arguments: ``` [<Calculator:@total=nil>, 1] ``` Expected returned value: ``` 0 ``` Actual returned value: ``` nil ```
  175. 511.

    1.) Recorded call for seam :tally (ID: 13) ran and

    failed comparison. Arguments: ``` [<Calculator:@total=nil>, 1] ``` Expected returned value: ``` 0 ``` Actual returned value: ``` nil ```
  176. 512.

    1.) Recorded call for seam :tally (ID: 13) ran and

    failed comparison. Arguments: ``` [<Calculator:@total=nil>, 1] ``` Expected returned value: ``` 0 ``` Actual returned value: ``` nil ```
  177. 513.

    class Calculator attr_reader :total def new_tally(n) return if n.odd? #

    ^ FIXME later @total ||= 0 @total += n return end end
  178. 514.

    class Calculator attr_reader :total def new_tally(n) return if n.odd? #

    ^ FIXME later @total ||= 0 @total += n return end end
  179. 515.

    class Calculator attr_reader :total def new_tally(n) return if n.odd? #

    ^ FIXME later @total ||= 0 @total += n return end end
  180. 516.

    class Calculator attr_reader :total def new_tally(n) @total ||= 0 return

    if n.odd? # ^ FIXME later @total ||= 0 @total += n return end end
  181. 532.

    class Controller def show calc = Calculator.new @result = Suture.create

    :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], call_both: true end end
  182. 533.

    class Controller def show calc = Calculator.new @result = Suture.create

    :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], call_both: true end end
  183. 534.

    class Controller def show calc = Calculator.new @result = Suture.create

    :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], call_both: true end end
  184. 535.

    class Controller def show calc = Calculator.new @result = Suture.create

    :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], call_both: true end end Calls :new & :old
  185. 536.

    class Controller def show calc = Calculator.new @result = Suture.create

    :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], call_both: true end end Calls :new & :old ✅
  186. 539.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true } @result = calc.total
  187. 540.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true } @result = calc.total
  188. 541.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true } @result = calc.total
  189. 542.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true } @result = calc.total ❌
  190. 544.

    The results from the old & new code paths did

    not match for the seam (Suture::Error::ResultMismatch) :tally and Suture is raising this error because the `:call_both` option is enabled, because both code paths are expected to return the same result. Arguments: ``` [<Calculator:@total=4>, 2] ``` The new code path returned: ``` 2 ``` The old code path returned: ``` 4 ``` Here's what we recommend you do next: 1. Verify that this mismatch does not represent a missed requirement in the new code path. If it does, implement it! 2. If either (or both) code path has a side effect that impacts the return value of the other, consider passing an `:after_old` and/or `:after_new` hook to clean up your application's state well enough to run both paths one-after-the-other safely. 3. If the two return values above are sufficiently similar for the purpose of your application, consider writing your own custom comparator that relaxes the comparison (e.g. only checks equivalence of the attributes that matter). See the README for more info on custom comparators. 4. If the new code path is working as desired (i.e. the old code path had a bug for this argument and you don't want to reimplement it just to make them perfectly in sync with one another), consider writing a one-off comparator for this seam that will ignore the affected range of arguments. See the README for more info on custom comparators. By default, Suture's :call_both mode will log a warning and raise an error when the results of each code path don't match. It is intended for use in any pre-production environment to "try out" the new code path before pushing it to production. If, for whatever reason, this error is too disruptive and logging is sufficient for monitoring results, you may disable this error by setting `:raise_on_result_mismatch` to false.
  191. 545.

    Suture is raising this error because the `:call_both` option is

    enabled, because both code paths are expected to return the same result. Arguments: ``` [<Calculator:@total=2>, 2] ``` The new code path returned: ``` 2 ``` The old code path returned: ``` 4 ```
  192. 546.

    Suture is raising this error because the `:call_both` option is

    enabled, because both code paths are expected to return the same result. Arguments: ``` [<Calculator:@total=2>, 2] ``` The new code path returned: ``` 2 ``` The old code path returned: ``` 4 ```
  193. 547.

    Suture is raising this error because the `:call_both` option is

    enabled, because both code paths are expected to return the same result. Arguments: ``` [<Calculator:@total=2>, 2] ``` The new code path returned: ``` 2 ``` The old code path returned: ``` 4 ```
  194. 548.

    Suture is raising this error because the `:call_both` option is

    enabled, because both code paths are expected to return the same result. Arguments: ``` [<Calculator:@total=2>, 2] ``` The new code path returned: ``` 2 ``` The old code path returned: ``` 4 ```
  195. 549.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total
  196. 550.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total
  197. 551.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total
  198. 552.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total protect from arg mutation
  199. 553.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total protect from arg mutation ☺
  200. 554.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total protect from arg mutation ❌
  201. 555.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total
  202. 556.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total calc never changes now!
  203. 557.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total
  204. 558.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total total is always nil
  205. 559.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total
  206. 560.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total
  207. 561.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total
  208. 562.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total
  209. 563.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total ✅
  210. 564.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total
  211. 567.
  212. 571.

    class Controller def show calc = Calculator.new @result = Suture.create

    :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], call_both: true end end
  213. 572.

    class Controller def show calc = Calculator.new @result = Suture.create

    :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], call_both: true end end
  214. 573.

    class Controller def show calc = Calculator.new @result = Suture.create

    :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end
  215. 574.

    class Controller def show calc = Calculator.new @result = Suture.create

    :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end
  216. 575.

    class Controller def show calc = Calculator.new @result = Suture.create

    :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end Rescues :new with :old
  217. 576.

    class Controller def show calc = Calculator.new @result = Suture.create

    :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end Rescues :new with :old ✅
  218. 578.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total
  219. 579.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], call_both: true, dup_args: true } @result = calc.total
  220. 580.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total
  221. 581.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total
  222. 582.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total ✅
  223. 588.
  224. 592.
  225. 594.
  226. 595.

    class Controller def show calc = Calculator.new @result = Suture.create

    :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end
  227. 596.

    class Controller def show calc = Calculator.new @result = Suture.create

    :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end
  228. 597.

    class Controller def show calc = Calculator.new @result = Suture.create

    :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end
  229. 598.

    class Controller def show calc = Calculator.new @result = Suture.create

    :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end
  230. 599.

    class Controller def show calc = Calculator.new @result = Suture.create

    :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end
  231. 600.

    class Controller def show calc = Calculator.new @result = Suture.create

    :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], fallback_on_error: true end end
  232. 601.

    class Controller def show calc = Calculator.new @result = Suture.create

    :add, old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], ) fallback_on_error: true end end
  233. 602.

    class Controller def show calc = Calculator.new @result = calc.new_add(

    old: calc.method(:add), new: calc.method(:new_add), args: [ params[:left], params[:right] ], ) fallback_on_error: true end end
  234. 607.
  235. 609.
  236. 610.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total
  237. 611.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total
  238. 612.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total
  239. 613.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total
  240. 614.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total
  241. 615.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total
  242. 616.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total
  243. 617.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total
  244. 618.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(m) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total
  245. 619.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(n) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total
  246. 620.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(n) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total
  247. 621.

    calc = Calculator.new params[:nums].each {|n| Suture.create :tally, old: ->(my_calc, m)

    { my_calc.tally(m) calc = my_calc my_calc.total }, new: ->(my_calc, m) { my_calc.new_tally(n) calc = my_calc my_calc.total }, args: [calc, n], fallback_on_error: true, dup_args: true } @result = calc.total
  248. 624.
  249. 630.
  250. 631.
  251. 632.
  252. 633.
  253. 634.
  254. 635.
  255. 637.
  256. 638.
  257. 639.
  258. 640.
  259. 641.
  260. 642.
  261. 643.
  262. 644.
  263. 645.
  264. 646.
  265. 647.
  266. 648.
  267. 649.

  268. 650.