Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Surgical Refactors

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

Justin Searls

September 09, 2016
Tweet

More Decks by Justin Searls

Other Decks in Programming

Transcript

  1. ൃԻ͕೉͍͠ͷͰɺ೔ຊޠ
    Ͱδϡʔεͱਃ͠·͢ɻ
    [email protected]

    View full-size slide

  2. ൃԻ͕೉͍͠ͷͰɺ೔ຊޠ
    Ͱδϡʔεͱਃ͠·͢ɻ
    [email protected]

    View full-size slide

  3. I come from @testdouble.
    We are software consultants.
    Say [email protected].

    View full-size slide

  4. 1. I talk fast when
    I'm nervous

    View full-size slide

  5. 1. I talk fast when
    I'm nervous

    View full-size slide

  6. 1. I talk fast when
    I'm nervous
    2. I am always
    nervous

    View full-size slide

  7. 1. I talk fast when
    I'm nervous
    2. I am always
    nervous


    View full-size slide

  8. օ͞Μ΁ɺ͝ΊΜͳ͍
    $

    View full-size slide

  9. օ͞Μ΁ɺ͝ΊΜͳ͍
    ؤுͬͯԼ͍͞ʂ
    $

    View full-size slide

  10. I was very nervous
    about screen size

    View full-size slide

  11. The secret to
    Ruby 3×3!

    View full-size slide

  12. _____ is a massively
    successful language!

    View full-size slide

  13. Early success

    View full-size slide

  14. Early success

    View full-size slide

  15. Early success

    View full-size slide

  16. Early success

    View full-size slide

  17. Early success:
    Making it easy to
    make new things

    View full-size slide

  18. Early success:
    Making it easy to
    make new things

    View full-size slide

  19. Later success

    View full-size slide

  20. Later success

    View full-size slide

  21. Later success

    View full-size slide

  22. Later success

    View full-size slide

  23. Later success:
    Making it easy to
    maintain old things

    View full-size slide

  24. Later success:
    Making it easy to
    maintain old things

    View full-size slide

  25. Can we make it easier
    to maintain old Ruby?
    Today's Question:

    View full-size slide

  26. Can we make it easier
    to maintain old Ruby?
    Today's Question:

    View full-size slide

  27. Today, let's refactor
    some legacy code

    View full-size slide

  28. Today, let's refactor
    some legacy code

    View full-size slide

  29. Refactor - verb

    View full-size slide

  30. Refactor - verb
    To change the design of
    code without changing
    its observable behavior.

    View full-size slide

  31. Refactor - verb
    To change in advance of
    a new feature or bug fix,
    making the job easier.

    View full-size slide

  32. Today, let's refactor
    some legacy code

    View full-size slide

  33. Today, let's refactor
    some legacy code

    View full-size slide

  34. Legacy code has
    many definitions

    View full-size slide

  35. Legacy Code - noun

    View full-size slide

  36. Legacy Code - noun
    Old code.

    View full-size slide

  37. Legacy Code - noun
    Code without tests.

    View full-size slide

  38. Legacy Code - noun
    Code that we don't like.

    View full-size slide

  39. Today, my definition is:

    View full-size slide

  40. Legacy Code - noun
    Code we don't understand
    well enough to change
    confidently.

    View full-size slide

  41. Today, let's refactor
    some legacy code

    View full-size slide

  42. Refactoring is hard

    View full-size slide

  43. Refactoring legacy
    code is very hard

    View full-size slide

  44. Easy to accidentally
    break functionality

    View full-size slide

  45. Legacy refactors
    often feel unsafe

    View full-size slide

  46. Legacy refactors
    are hard to sell

    View full-size slide

  47. Business Priority

    View full-size slide

  48. Business Priority
    Cost/Risk

    View full-size slide

  49. Business Priority
    Cost/Risk
    New
    Features

    View full-size slide

  50. Business Priority
    Cost/Risk
    New
    Features
    Bug
    Fixes

    View full-size slide

  51. Business Priority
    Cost/Risk
    New
    Features
    Bug
    Fixes
    Testing

    View full-size slide

  52. Business Priority
    Cost/Risk
    New
    Features
    Bug
    Fixes
    Testing

    View full-size slide

  53. Business Priority
    Cost/Risk
    New
    Features
    Bug
    Fixes
    Testing ???

    View full-size slide

  54. Business Priority
    Cost/Risk
    New
    Features
    Bug
    Fixes
    Testing Refactoring

    View full-size slide

  55. Business Priority
    Cost/Risk
    New
    Features
    Bug
    Fixes
    Testing Refactoring
    No selling
    Needed

    View full-size slide

  56. Business Priority
    Cost/Risk
    New
    Features
    Bug
    Fixes
    Testing Refactoring
    Easy
    to sell

    View full-size slide

  57. Business Priority
    Cost/Risk
    New
    Features
    Bug
    Fixes
    Testing Refactoring
    Can often sell

    View full-size slide

  58. Business Priority
    Cost/Risk
    New
    Features
    Bug
    Fixes
    Testing Refactoring Very hard
    to sell

    View full-size slide

  59. Refactors are hard

    View full-size slide

  60. Refactors are hard

    View full-size slide

  61. Refactors are hard

    View full-size slide

  62. Refactors are hard

    View full-size slide

  63. As complexity goes up

    View full-size slide

  64. As complexity goes up

    Greater importance

    View full-size slide

  65. As complexity goes up

    Less certain

    View full-size slide

  66. As complexity goes up

    More costly

    View full-size slide

  67. Make Refactors
    Great Again

    View full-size slide

  68. Make Refactors
    Great for the 1st Time

    View full-size slide

  69. Business Priority
    Cost/Risk
    Refactoring

    View full-size slide

  70. Business Priority
    Cost/Risk
    Refactoring

    View full-size slide

  71. Business Priority
    Cost/Risk
    Refactoring

    View full-size slide

  72. Selling refactoring
    to businesspeople


    View full-size slide

  73. Selling refactoring
    to businesspeople



    View full-size slide

  74. Selling refactoring
    to businesspeople




    View full-size slide

  75. 1. Scare them!

    View full-size slide

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

    View full-size slide

  77. 1. Scare them!
    "If we don't refactor,
    then .
    !"
    to rewrite everything
    someday we'll need

    View full-size slide

  78. 1. Scare them!
    "If we don't refactor,
    then .
    !"
    to rewrite everything
    someday we'll need
    Far in the future

    View full-size slide

  79. 1. Scare them!
    "If we don't refactor,
    then .
    !"
    costs will be much higher
    your maintenance

    View full-size slide

  80. 1. Scare them!
    "If we don't refactor,
    then .
    !"
    costs will be much higher
    your maintenance
    Hard to quantify

    View full-size slide

  81. 2. Absorb the cost

    View full-size slide

  82. 2. Absorb the cost
    New Feature Activities

    View full-size slide

  83. 2. Absorb the cost
    Planning
    New Feature Activities

    View full-size slide

  84. 2. Absorb the cost
    Development
    Planning
    New Feature Activities

    View full-size slide

  85. 2. Absorb the cost
    Development
    Testing
    Planning
    New Feature Activities

    View full-size slide

  86. 2. Absorb the cost
    Development
    Testing
    Planning
    New Feature Activities

    View full-size slide

  87. 2. Absorb the cost
    Development
    Testing
    Planning
    New Feature Activities
    Refactoring

    View full-size slide

  88. 2. Absorb the cost
    Development
    Testing
    Planning
    Refactoring
    Requires extreme discipline

    View full-size slide

  89. 2. Absorb the cost
    Development
    Testing
    Planning
    Refactoring
    Collapses under pressure

    View full-size slide

  90. 3. Take hostages

    View full-size slide

  91. 3. Take hostages
    Feature #1

    View full-size slide

  92. 3. Take hostages
    Feature #1
    Feature #2

    View full-size slide

  93. 3. Take hostages
    Feature #1
    Feature #2
    Feature #3

    View full-size slide

  94. 3. Take hostages
    Feature #1
    Feature #2
    Feature #3
    Feature #4

    View full-size slide

  95. 3. Take hostages
    Feature #1
    Feature #2
    Feature #3
    Technical.Debt

    View full-size slide

  96. 3. Take hostages
    Feature #1
    Feature #2
    Technical Debt
    Technical.Debt

    View full-size slide

  97. 3. Take hostages
    Feature #1
    Feature #2
    Blames
    business
    for rushing
    Technical Debt
    Technical.Debt

    View full-size slide

  98. 3. Take hostages
    Feature #1
    Feature #2
    Erodes
    trust in
    the team
    Technical Debt
    Technical.Debt

    View full-size slide

  99. Refactoring
    is hard to sell

    View full-size slide

  100. Business Priority
    Cost/Risk
    Refactoring

    View full-size slide

  101. Business Priority
    Cost/Risk
    Refactoring

    View full-size slide

  102. Business Priority
    Cost/Risk
    Refactoring

    View full-size slide

  103. Business Priority
    Cost/Risk
    Refactoring

    View full-size slide

  104. Too much pressure!

    View full-size slide

  105. Too much pressure!

    View full-size slide

  106. Too much pressure!


    View full-size slide

  107. Too much pressure!


    View full-size slide

  108. Refactors are scary!

    View full-size slide

  109. You should
    buy my book!

    View full-size slide

  110. THE
    FRIGHTENED
    PROGRAMMER
    JUSTIN SEARLS


    UGH SOFTWARE

    View full-size slide

  111. Business Priority
    Cost/Risk
    Refactoring

    View full-size slide

  112. Business Priority
    Cost/Risk
    Refactoring

    View full-size slide

  113. 1. Refactoring Patterns

    View full-size slide

  114. 1. Refactoring Patterns

    View full-size slide

  115. 1. Refactoring Patterns
    • Extract method

    View full-size slide

  116. 1. Refactoring Patterns
    • Extract method
    • Pull up / push down

    View full-size slide

  117. 1. Refactoring Patterns
    • Extract method
    • Pull up / push down
    • Split loop

    View full-size slide

  118. 1. Refactoring Patterns
    • Extract method
    • Pull up / push down
    • Split loop
    Safer with
    good tools

    View full-size slide

  119. 1. Refactoring Patterns
    • Extract method
    • Pull up / push down
    • Split loop

    View full-size slide

  120. 1. Refactoring Patterns
    Not very
    expressive
    • Extract method
    • Pull up / push down
    • Split loop

    View full-size slide

  121. 2. Characterization Testing

    View full-size slide

  122. 2. Characterization Testing

    View full-size slide

  123. 2. Characterization Testing

    View full-size slide

  124. 2. Characterization Testing

    View full-size slide

  125. 2. Characterization Testing

    View full-size slide

  126. 2. Characterization Testing

    View full-size slide

  127. 2. Characterization Testing

    View full-size slide

  128. 2. Characterization Testing

    View full-size slide

  129. 2. Characterization Testing

    View full-size slide

  130. 2. Characterization Testing

    View full-size slide

  131. 2. Characterization Testing

    View full-size slide

  132. 2. Characterization Testing

    View full-size slide

  133. 2. Characterization Testing
    No wrong answers!

    View full-size slide

  134. 2. Characterization Testing

    View full-size slide

  135. 2. Characterization Testing

    View full-size slide

  136. 2. Characterization Testing

    View full-size slide

  137. 2. Characterization Testing

    View full-size slide

  138. 2. Characterization Testing

    View full-size slide

  139. 2. Characterization Testing

    View full-size slide

  140. 2. Characterization Testing

    View full-size slide

  141. 2. Characterization Testing

    View full-size slide

  142. 2. Characterization Testing

    View full-size slide

  143. 2. Characterization Testing
    That's a lot
    of testing!

    View full-size slide

  144. 2. Characterization Testing

    View full-size slide

  145. 2. Characterization Testing
    It's hard to let go of
    characterization tests

    View full-size slide

  146. 2. Characterization Testing
    Tempting to quit
    halfway through

    View full-size slide

  147. 3. A/B Testing / Experiments

    View full-size slide

  148. 3. A/B Testing / Experiments

    View full-size slide

  149. 3. A/B Testing / Experiments

    View full-size slide

  150. 3. A/B Testing / Experiments
    Old code

    View full-size slide

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

    View full-size slide

  152. 3. A/B Testing / Experiments
    Old code New code
    if rand < 0.2

    View full-size slide

  153. 3. A/B Testing / Experiments
    Old code New code
    if rand < 0.2
    true

    View full-size slide

  154. 3. A/B Testing / Experiments
    Old code New code
    if rand < 0.2
    false true

    View full-size slide

  155. 3. A/B Testing / Experiments

    View full-size slide

  156. 3. A/B Testing / Experiments
    Old code New code
    if rand < 0.2
    false true

    View full-size slide

  157. 3. A/B Testing / Experiments
    Old code New code
    if rand < 0.2
    false true
    Rewriting in big steps is
    confusing & error-prone

    View full-size slide

  158. 3. A/B Testing / Experiments
    Old code New code
    if rand < 0.2
    false true
    Heavy monitoring
    & analysis required

    View full-size slide

  159. 3. A/B Testing / Experiments
    Old code New code
    if rand < 0.2
    false true
    Experimenting on
    humans is risky

    View full-size slide

  160. Characterization Testing

    View full-size slide


  161. A/B Experiments

    View full-size slide

  162. Development
    Testing

    View full-size slide

  163. Development
    Testing
    Staging

    View full-size slide

  164. Development
    Testing
    Staging
    Production

    View full-size slide

  165. Development
    Testing
    Staging
    Production

    View full-size slide

  166. Development
    Testing
    Staging
    Production
    Development

    View full-size slide

  167. Development
    Testing
    Staging
    Production
    Development
    Testing

    View full-size slide

  168. Development
    Testing
    Staging
    Production
    Development
    Testing
    Staging

    View full-size slide

  169. Development
    Testing
    Staging
    Production
    Development
    Testing
    Staging
    Production

    View full-size slide

  170. Development
    Testing
    Staging
    Production
    De
    Te
    St
    Pr

    View full-size slide

  171. Development
    Testing
    Staging
    Production
    De
    Te
    St
    Pr

    View full-size slide

  172. Development
    Testing
    Staging
    Production
    De
    Te
    St
    Pr
    Development

    View full-size slide

  173. Development
    Testing
    Staging
    Production
    De
    Te
    St
    Pr
    Development
    Testing

    View full-size slide

  174. Development
    Testing
    Staging
    Production
    De
    Te
    St
    Pr
    Development
    Testing
    Staging

    View full-size slide

  175. Development
    Testing
    Staging
    Production
    De
    Te
    St
    Pr
    Development
    Testing
    Staging
    Production

    View full-size slide

  176. Development
    Testing
    Staging
    Production
    De
    Te
    St
    Pr
    Development
    Testing
    Staging
    Production

    View full-size slide

  177. "Oh no, I have to
    give a talk on this"

    View full-size slide

  178. "Instead of slides,
    I'll write a gem!"

    View full-size slide

  179. TDD
    (Talk-Driven Development)

    View full-size slide

  180. github.com/testdouble/suture

    View full-size slide

  181. $ gem install suture

    View full-size slide

  182. Refactors as
    Surgeries

    View full-size slide

  183. Refactors as Surgeries

    View full-size slide

  184. Refactors as Surgeries
    Serve a common purpose

    View full-size slide

  185. Refactors as Surgeries
    Serve a common purpose

    View full-size slide

  186. Refactors as Surgeries
    Require careful planning

    View full-size slide

  187. Refactors as Surgeries
    Flexible tools

    View full-size slide

  188. Refactors as Surgeries
    Flexible tools


    View full-size slide

  189. Refactors as Surgeries
    Flexible tools


    View full-size slide

  190. Refactors as Surgeries
    Follow a process

    View full-size slide

  191. Refactors as Surgeries
    Follow a process

    View full-size slide

  192. Refactors as Surgeries
    Follow a process


    View full-size slide

  193. Refactors as Surgeries
    Multiple Observations

    View full-size slide

  194. Refactors as Surgeries
    Multiple Observations


    View full-size slide

  195. Refactors as Surgeries
    Multiple Observations


    View full-size slide


  196. Plan

    Cut

    Record

    View full-size slide


  197. Plan

    Cut

    Record

    Validate

    View full-size slide


  198. Plan

    Cut

    Record

    Validate

    Refactor

    View full-size slide


  199. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    View full-size slide


  200. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare

    View full-size slide


  201. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    View full-size slide


  202. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    Delete

    View full-size slide

  203. Two Bug Fixes:

    View full-size slide

  204. Calculator service
    doesn't add negative
    numbers correctly.
    #1

    View full-size slide

  205. Pure function

    View full-size slide

  206. class Controller
    def show
    calc = Calculator.new
    @result = calc.add(
    params[:left],
    params[:right]
    )
    end
    end

    View full-size slide

  207. class Controller
    def show
    calc = Calculator.new
    @result = calc.add(
    params[:left],
    params[:right]
    )
    end
    end

    View full-size slide

  208. class Controller
    def show
    calc = Calculator.new
    @result = calc.add(
    params[:left],
    params[:right]
    )
    end
    end

    View full-size slide

  209. class Controller
    def show
    calc = Calculator.new
    @result = calc.add(
    params[:left],
    params[:right]
    )
    end
    end

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  217. class Controller
    def show
    calc = Calculator.new
    @result = calc.add(
    params[:left],
    params[:right]
    )
    end
    end

    View full-size slide

  218. class Controller
    def show
    calc = Calculator.new
    @result = calc.add(
    params[:left],
    params[:right]
    )
    end
    end
    We will create
    our "seam" here

    View full-size slide

  219. Calculator tally service
    doesn't handle odd
    numbers correctly.
    #2

    View full-size slide

  220. class Controller
    def index
    calc = Calculator.new
    params[:nums].each {|n|
    calc.tally(n)
    }
    @result = calc.total
    end
    end

    View full-size slide

  221. class Controller
    def index
    calc = Calculator.new
    params[:nums].each {|n|
    calc.tally(n)
    }
    @result = calc.total
    end
    end

    View full-size slide

  222. class Controller
    def index
    calc = Calculator.new
    params[:nums].each {|n|
    calc.tally(n)
    }
    @result = calc.total
    end
    end

    View full-size slide

  223. class Controller
    def index
    calc = Calculator.new
    params[:nums].each {|n|
    calc.tally(n)
    }
    @result = calc.total
    end
    end

    View full-size slide

  224. class Controller
    def index
    calc = Calculator.new
    params[:nums].each {|n|
    calc.tally(n)
    }
    @result = calc.total
    end
    end

    View full-size slide

  225. 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

    View full-size slide

  226. 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

    View full-size slide

  227. 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

    View full-size slide

  228. 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

    View full-size slide

  229. 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

    View full-size slide

  230. 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

    View full-size slide

  231. 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

    View full-size slide

  232. class Controller
    def index
    calc = Calculator.new
    params[:nums].each {|n|
    calc.tally(n)
    }
    @result = calc.total
    end
    end

    View full-size slide

  233. class Controller
    def index
    calc = Calculator.new
    params[:nums].each {|n|
    calc.tally(n)
    }
    @result = calc.total
    end
    end

    View full-size slide

  234. class Controller
    def index
    calc = Calculator.new
    params[:nums].each {|n|
    calc.tally(n)
    }
    @result = calc.total
    end
    end
    This seam is
    more complex

    View full-size slide


  235. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    Delete

    View full-size slide

  236. Pure function

    View full-size slide

  237. class Controller
    def show
    calc = Calculator.new
    @result = calc.add(
    params[:left],
    params[:right]
    )
    end
    end

    View full-size slide

  238. ss Controller
    ef show
    calc = Calculator.new
    @result = calc.add(
    params[:left],
    params[:right]
    )
    nd

    View full-size slide

  239. calc = Calculator.new
    @result = calc.add(eate :add,
    params[:left],
    params[:right]
    ) params[:left],
    params[:right]
    ]

    View full-size slide

  240. calc = Calculator.new
    @result = Suture.create :add,
    old: calc.method(:add),
    args: [
    params[:left],
    params[:right]
    ]

    View full-size slide

  241. calc = Calculator.new
    @result = Suture.create :add,
    old: calc.method(:add),
    args: [
    params[:left],
    params[:right]
    ]

    View full-size slide

  242. calc = Calculator.new
    @result = Suture.create :add,
    old: calc.method(:add),
    args: [
    params[:left],
    params[:right]
    ]

    View full-size slide

  243. calc = Calculator.new
    @result = Suture.create :add,
    old: calc.method(:add),
    args: [
    params[:left],
    params[:right]
    ]
    :old must
    respond_to?(:call)

    View full-size slide

  244. calc = Calculator.new
    @result = Suture.create :add,
    old: calc.method(:add),
    args: [
    params[:left],
    params[:right]
    ]

    View full-size slide

  245. calc = Calculator.new
    @result = Suture.create :add,
    old: calc.method(:add),
    args: [
    params[:left],
    params[:right]
    ]
    Initially a no-op;
    verify it still works

    View full-size slide

  246. class Controller
    def index
    calc = Calculator.new
    params[:nums].each {|n|
    calc.tally(n)
    }
    @result = calc.total
    end
    end

    View full-size slide

  247. ef index
    calc = Calculator.new
    params[:nums].each {|n|
    calc.tally(n)
    }
    @result = calc.total
    nd

    View full-size slide

  248. 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

    View full-size slide

  249. 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

    View full-size slide

  250. 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

    View full-size slide

  251. 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!

    View full-size slide

  252. How to design a seam

    View full-size slide

  253. ❄Pure functions are easy

    View full-size slide

  254. ❄Pure functions are easy
    Calculator#add(a,b)

    View full-size slide

  255. ❄Pure functions are easy
    Calculator#add(a,b)
    (2,8)

    View full-size slide

  256. ❄Pure functions are easy
    Calculator#add(a,b)
    (2,8) 10

    View full-size slide

  257. ❄Pure functions are easy
    Calculator#add(a,b)
    (2,8) 10
    (2,8)

    View full-size slide

  258. ❄Pure functions are easy
    Calculator#add(a,b)
    (2,8) 10
    (2,8) 10

    View full-size slide

  259. ❄Pure functions are easy
    Calculator#add(a,b)
    (2,8) 10
    (2,8) 10
    Repeatable input & output

    View full-size slide

  260. Mutation is hard


    View full-size slide

  261. Mutation is hard
    Calculator#tally(n)


    View full-size slide

  262. Mutation is hard
    Calculator#tally(n)
    (4)


    View full-size slide

  263. Mutation is hard
    Calculator#tally(n)
    (4) 4


    View full-size slide

  264. Mutation is hard
    Calculator#tally(n)
    (4) 4


    (4)

    View full-size slide

  265. Mutation is hard
    Calculator#tally(n)
    (4) 4


    (4) 8

    View full-size slide

  266. Mutation is hard
    Calculator#tally(n)
    (4) 4


    (4) 8
    @total=

    View full-size slide

  267. Mutation is hard
    Calculator#tally(n)
    (4) 4


    (4) 8

    View full-size slide

  268. Mutation is hard
    Calculator#tally(n)
    4


    (4) 8
    (calc@0,4)

    View full-size slide

  269. Mutation is hard
    Calculator#tally(n)
    4


    8
    (calc@0,4)
    (calc@0,4)

    View full-size slide

  270. Mutation is hard
    Calculator#tally(n)
    4


    (calc@0,4)
    (calc@0,4) 4

    View full-size slide

  271. Mutation is hard
    Calculator#tally(n)
    4


    (calc@0,4)
    (calc@0,4) 4
    Repeatable input & output

    View full-size slide

  272. 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

    View full-size slide

  273. 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

    View full-size slide

  274. 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

    View full-size slide

  275. 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

    View full-size slide

  276. 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

    View full-size slide

  277. 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

    View full-size slide

  278. 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

    View full-size slide


  279. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    Delete

    View full-size slide

  280. Pure function

    View full-size slide

  281. calc = Calculator.new
    @result = Suture.create :add,
    old: calc.method(:add),
    args: [
    params[:left],
    params[:right]
    ]

    View full-size slide

  282. calc = Calculator.new
    @result = Suture.create :add,
    old: calc.method(:add),
    args: [
    params[:left],
    params[:right]
    ],
    record_calls: true

    View full-size slide

  283. calc = Calculator.new
    @result = Suture.create :add,
    old: calc.method(:add),
    args: [
    params[:left],
    params[:right]
    ],
    record_calls: true

    View full-size slide

  284. 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

    View full-size slide

  285. Record some calls!

    View full-size slide

  286. Record via CLI

    View full-size slide

  287. 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

    View full-size slide

  288. 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

    View full-size slide

  289. 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

    View full-size slide

  290. 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

    View full-size slide

  291. 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

    View full-size slide

  292. 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

    View full-size slide

  293. 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

    View full-size slide

  294. 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

    View full-size slide

  295. Record via browser

    View full-size slide

  296. Record via browser
    add(4,5)

    View full-size slide

  297. Record via browser
    add(4,5)

    View full-size slide

  298. Record in production!

    View full-size slide

  299. Record in production!


    View full-size slide

  300. Record in production!



    View full-size slide

  301. 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

    View full-size slide

  302. 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

    View full-size slide

  303. 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

    View full-size slide

  304. 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

    View full-size slide

  305. 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

    View full-size slide

  306. 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

    View full-size slide

  307. 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

    View full-size slide

  308. 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

    View full-size slide

  309. 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

    View full-size slide

  310. 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

    View full-size slide

  311. 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

    View full-size slide

  312. Where does it go?

    View full-size slide

  313. Suture.config({
    database_path:
    "db/suture.sqlite3"
    })

    View full-size slide

  314. Suture.config({
    database_path:
    "db/suture.sqlite3"
    })

    View full-size slide

  315. Suture.config({
    database_path:
    "db/suture.sqlite3"
    })

    View full-size slide

  316. Suture.config({
    database_path:
    "db/suture.sqlite3"
    })
    I heard Ruby was
    getting a database!

    View full-size slide

  317. Suture.config({
    database_path:
    "db/suture.sqlite3"
    })
    Marshal.dump

    View full-size slide

  318. What about Rails?

    View full-size slide

  319. Gilded Rose Kata

    View full-size slide

  320. 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

    View full-size slide

  321. It apparently works

    View full-size slide


  322. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    Delete

    View full-size slide

  323. Pure function

    View full-size slide

  324. def test_validate_old_add
    calc = Calculator.new
    Suture.verify :add,
    subject: calc.method(:add)
    end

    View full-size slide

  325. def test_validate_old_add
    calc = Calculator.new
    Suture.verify :add,
    subject: calc.method(:add)
    end

    View full-size slide

  326. def test_validate_old_add
    calc = Calculator.new
    Suture.verify :add,
    subject: calc.method(:add)
    end

    View full-size slide

  327. def test_validate_old_add
    calc = Calculator.new
    Suture.verify :add,
    subject: calc.method(:add)
    end

    View full-size slide

  328. def test_validate_old_add
    calc = Calculator.new
    Suture.verify :add,
    subject: calc.method(:add)
    end

    View full-size slide

  329. def test_validate_old_add
    calc = Calculator.new
    Suture.verify :add,
    subject: calc.method(:add)
    end
    Verifies each recorded args
    yield the recorded result

    View full-size slide

  330. def test_validate_old_add
    calc = Calculator.new
    Suture.verify :add,
    subject: calc.method(:add)
    end

    View full-size slide

  331. def test_validate_old_add
    calc = Calculator.new
    Suture.verify :add,
    subject: calc.method(:add)
    end

    View full-size slide

  332. def test_validate_old_add
    calc = Calculator.new
    Suture.verify :add,
    subject: calc.method(:add)
    end

    View full-size slide

  333. def test_validate_old_add
    calc = Calculator.new
    Suture.verify :add,
    subject: calc.method(:add)
    end
    Cheap tests!

    View full-size slide

  334. def test_old_tally
    Suture.verify:tally,
    subject: ->(calc, n){
    calc.tally(n)
    calc.total
    }
    end

    View full-size slide

  335. def test_old_tally
    Suture.verify:tally,
    subject: ->(calc, n){
    calc.tally(n)
    calc.total
    }
    end

    View full-size slide

  336. def test_old_tally
    Suture.verify:tally,
    subject: ->(calc, n){
    calc.tally(n)
    calc.total
    }
    end

    View full-size slide

  337. def test_old_tally
    Suture.verify:tally,
    subject: ->(calc, n){
    calc.tally(n)
    calc.total
    }
    end

    View full-size slide

  338. def test_old_tally
    Suture.verify:tally,
    subject: ->(calc, n){
    calc.tally(n)
    calc.total
    }
    end

    View full-size slide

  339. def test_old_tally
    Suture.verify:tally,
    subject: ->(calc, n){
    calc.tally(n)
    calc.total
    }
    end Duplicate the
    lambda exactly

    View full-size slide

  340. Finally, a
    good use
    for code
    coverage!

    View full-size slide

  341. Gilded Rose Kata

    View full-size slide

  342. Trial # 1
    Characterization tests

    View full-size slide

  343. Trial # 2
    Suture.verify

    View full-size slide

  344. def test_gilded_rose_old
    Suture.verify :rose,
    subject: ->(items) {
    update_quality(items)
    items
    },
    fail_fast: true
    end

    View full-size slide

  345. def test_gilded_rose_old
    Suture.verify :rose,
    subject: ->(items) {
    update_quality(items)
    items
    },
    fail_fast: true
    end

    View full-size slide

  346. def test_gilded_rose_old
    Suture.verify :rose,
    subject: ->(items) {
    update_quality(items)
    items
    },
    fail_fast: true
    end

    View full-size slide

  347. def test_gilded_rose_old
    Suture.verify :rose,
    subject: ->(items) {
    update_quality(items)
    items
    },
    fail_fast: true
    end

    View full-size slide

  348. def test_gilded_rose_old
    Suture.verify :rose,
    subject: ->(items) {
    update_quality(items)
    items
    },
    fail_fast: true
    end

    View full-size slide

  349. def test_gilded_rose_old
    Suture.verify :rose,
    subject: ->(items) {
    update_quality(items)
    items
    },
    fail_fast: true
    end
    Items in,
    mutated
    items out

    View full-size slide

  350. def test_gilded_rose_old
    Suture.verify :rose,
    subject: ->(items) {
    update_quality(items)
    items
    },
    fail_fast: true
    end

    View full-size slide

  351. def test_gilded_rose_old
    Suture.verify :rose,
    subject: ->(items) {
    update_quality(items)
    items
    },
    fail_fast: true
    end All recordings
    expected to pass

    View full-size slide

  352. Check coverage
    before continuing

    View full-size slide

  353. 100% Coverage
    and zero tests

    View full-size slide


  354. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    Delete

    View full-size slide

  355. I'm no refactoring expert

    View full-size slide


  356. I'm no refactoring expert

    View full-size slide


  357. I'm no refactoring expert
    That's why I needed this tool

    View full-size slide

  358. Unlike my book,
    this book exists

    View full-size slide

  359. Pure function

    View full-size slide

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

    View full-size slide

  361. class Calculator
    def add(left, right)
    right.times do
    left += 1
    end
    left
    end
    end
    Doesn't work for
    negative values!

    View full-size slide

  362. class Calculator
    def new_add(left, right)
    return left if right < 0
    # ^ FIXME later
    left + right
    end
    end

    View full-size slide

  363. class Calculator
    def new_add(left, right)
    return left if right < 0
    # ^ FIXME later
    left + right
    end
    end

    View full-size slide

  364. class Calculator
    def new_add(left, right)
    return left if right < 0
    # ^ FIXME later
    left + right
    end
    end

    View full-size slide

  365. class Calculator
    def new_add(left, right)
    return left if right < 0
    # ^ FIXME later
    left + right
    end
    end

    View full-size slide

  366. class Calculator
    def new_add(left, right)
    return left if right < 0
    # ^ FIXME later
    left + right
    end
    end

    View full-size slide

  367. class Calculator
    def new_add(left, right)
    return left if right < 0
    # ^ FIXME later
    left + right
    end
    end Retain current behavior
    exactly, bugs & all

    View full-size slide

  368. 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

    View full-size slide

  369. 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

    View full-size slide

  370. 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!

    View full-size slide

  371. class Calculator
    def new_tally(n)
    return if n.odd?
    # ^ FIXME later
    @total ||= 0
    @total += n
    return
    end
    end

    View full-size slide

  372. class Calculator
    def new_tally(n)
    return if n.odd?
    # ^ FIXME later
    @total ||= 0
    @total += n
    return
    end
    end

    View full-size slide

  373. class Calculator
    def new_tally(n)
    return if n.odd?
    # ^ FIXME later
    @total ||= 0
    @total += n
    return
    end
    end

    View full-size slide

  374. class Calculator
    def new_tally(n)
    return if n.odd?
    # ^ FIXME later
    @total ||= 0
    @total += n
    return
    end
    end

    View full-size slide

  375. class Calculator
    def new_tally(n)
    return if n.odd?
    # ^ FIXME later
    @total ||= 0
    @total += n
    return
    end
    end
    Still returns nil

    View full-size slide

  376. class Calculator
    def new_tally(n)
    return if n.odd?
    # ^ FIXME later
    @total ||= 0
    @total += n
    return
    end
    end

    View full-size slide

  377. class Calculator
    def new_tally(n)
    return if n.odd?
    # ^ FIXME later
    @total ||= 0
    @total += n
    return
    end
    end

    View full-size slide

  378. class Calculator
    def new_tally(n)
    return if n.odd?
    # ^ FIXME later
    @total ||= 0
    @total += n
    return
    end
    end

    View full-size slide

  379. 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

    View full-size slide


  380. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    Delete

    View full-size slide

  381. Pure function

    View full-size slide

  382. def test_new_add
    calc = Calculator.new
    Suture.verify :add,
    subject: calc.method(:new_add)
    end

    View full-size slide

  383. def test_new_add
    calc = Calculator.new
    Suture.verify :add,
    subject: calc.method(:new_add)
    end

    View full-size slide

  384. def test_new_add
    calc = Calculator.new
    Suture.verify :add,
    subject: calc.method(:new_add)
    end

    View full-size slide

  385. def test_new_add
    calc = Calculator.new
    Suture.verify :add,
    subject: calc.method(:new_add)
    end

    View full-size slide

  386. def test_new_add
    calc = Calculator.new
    Suture.verify :add,
    subject: calc.method(:new_add)
    end

    View full-size slide

  387. def test_new_add
    calc = Calculator.new
    Suture.verify :add,
    subject: calc.method(:new_add)
    end

    View full-size slide

  388. def test_new_tally
    Suture.verify :tally,
    subject: ->(calc, n){
    calc.new_tally(n)
    calc.total
    }
    end

    View full-size slide

  389. def test_new_tally
    Suture.verify :tally,
    subject: ->(calc, n){
    calc.new_tally(n)
    calc.total
    }
    end

    View full-size slide

  390. def test_new_tally
    Suture.verify :tally,
    subject: ->(calc, n){
    calc.new_tally(n)
    calc.total
    }
    end

    View full-size slide

  391. def test_new_tally
    Suture.verify :tally,
    subject: ->(calc, n){
    calc.new_tally(n)
    calc.total
    }
    end

    View full-size slide

  392. def test_new_tally
    Suture.verify :tally,
    subject: ->(calc, n){
    calc.new_tally(n)
    calc.total
    }
    end

    View full-size slide

  393. def test_new_tally
    Suture.verify :tally,
    subject: ->(calc, n){
    calc.new_tally(n)
    calc.total
    }
    end

    View full-size slide

  394. def test_new_tally
    Suture.verify :tally,
    subject: ->(calc, n){
    calc.new_tally(n)
    calc.total
    }
    end


    View full-size slide

  395. Judge a library
    by its messages

    View full-size slide

  396. # 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: ```
    [, 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)`

    View full-size slide

  397. 1.) Recorded call for seam :tally
    (ID: 13) ran and failed comparison.
    Arguments: ```
    [, 1]
    ```
    Expected returned value: ```
    0
    ```
    Actual returned value: ```
    nil
    ```

    View full-size slide

  398. 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)`

    View full-size slide

  399. 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

    View full-size slide

  400. 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

    View full-size slide

  401. Failure advice

    View full-size slide

  402. ### 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`.

    View full-size slide

  403. ### 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

    View full-size slide

  404. Comparing Results

    View full-size slide

  405. Default Comparator

    View full-size slide

  406. )

    (
    Marshall.dump
    Marshall.dump
    ==
    )

    (

    View full-size slide

  407. What about
    ActiveRecord?!

    View full-size slide

  408. .attributes
    ==

    AR

    AR .attributes

    View full-size slide

  409. Custom Comparators
    p q r s t

    View full-size slide

  410. What if Calculator
    had many other fields?
    class Calculator
    attr_reader :created_at,
    :memory_val,
    :total
    # …
    end

    View full-size slide

  411. Suture.verify :tally,
    subject: ->(calc, n) {
    calc.new_tally(n)
    calc.total
    },
    comparator: ->(recorded,
    actual) {
    recorded.total ==
    actual.total
    }

    View full-size slide

  412. Suture.verify :tally,
    subject: ->(calc, n) {
    calc.new_tally(n)
    calc.total
    },
    comparator: ->(recorded,
    actual) {
    recorded.total ==
    actual.total
    }

    View full-size slide

  413. Suture.verify :tally,
    subject: ->(calc, n) {
    calc.new_tally(n)
    calc.total
    },
    comparator: ->(recorded,
    actual) {
    recorded.total ==
    actual.total
    }

    View full-size slide

  414. Suture.verify :tally,
    subject: ->(calc, n) {
    calc.new_tally(n)
    calc.total
    },
    comparator: ->(recorded,
    actual) {
    recorded.total ==
    actual.total
    }

    View full-size slide

  415. Suture.verify :tally,
    subject: ->(calc, n) {
    calc.new_tally(n)
    calc.total
    },
    comparator: ->(recorded,
    actual) {
    recorded.total ==
    actual.total
    }

    View full-size slide

  416. Suture.verify :tally,
    subject: ->(calc, n) {
    calc.new_tally(n)
    calc.total
    },
    comparator: ->(recorded,
    actual) {
    recorded.total ==
    actual.total
    }

    View full-size slide

  417. Classes also exist!

    View full-size slide

  418. 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

    View full-size slide

  419. 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

    View full-size slide

  420. 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

    View full-size slide

  421. 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

    View full-size slide

  422. 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

    View full-size slide

  423. 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

    View full-size slide

  424. Returning to the
    error message

    View full-size slide

  425. #### 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`.

    View full-size slide

  426. #### 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`.

    View full-size slide

  427. #### 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`.

    View full-size slide

  428. Discoverable
    configuration

    View full-size slide

  429. # 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
    }
    ```

    View full-size slide

  430. # 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
    }
    ```

    View full-size slide

  431. # 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
    }
    ```

    View full-size slide

  432. # 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
    }
    ```

    View full-size slide

  433. # 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
    }
    ```

    View full-size slide

  434. # 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
    }
    ```

    View full-size slide

  435. # 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
    }
    ```

    View full-size slide

  436. # 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
    }
    ```

    View full-size slide

  437. A sense of progress

    View full-size slide

  438. # 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%!

    View full-size slide

  439. # 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%!

    View full-size slide

  440. Judge a library
    by its messages

    View full-size slide

  441. Wait, why did
    verification fail?

    View full-size slide

  442. 1.) Recorded call for seam :tally
    (ID: 13) ran and failed comparison.
    Arguments: ```
    [, 1]
    ```
    Expected returned value: ```
    0
    ```
    Actual returned value: ```
    nil
    ```

    View full-size slide

  443. 1.) Recorded call for seam :tally
    (ID: 13) ran and failed comparison.
    Arguments: ```
    [, 1]
    ```
    Expected returned value: ```
    0
    ```
    Actual returned value: ```
    nil
    ```

    View full-size slide

  444. 1.) Recorded call for seam :tally
    (ID: 13) ran and failed comparison.
    Arguments: ```
    [, 1]
    ```
    Expected returned value: ```
    0
    ```
    Actual returned value: ```
    nil
    ```

    View full-size slide

  445. class Calculator
    attr_reader :total
    def new_tally(n)
    return if n.odd?
    # ^ FIXME later
    @total ||= 0
    @total += n
    return
    end
    end

    View full-size slide

  446. class Calculator
    attr_reader :total
    def new_tally(n)
    return if n.odd?
    # ^ FIXME later
    @total ||= 0
    @total += n
    return
    end
    end

    View full-size slide

  447. class Calculator
    attr_reader :total
    def new_tally(n)
    return if n.odd?
    # ^ FIXME later
    @total ||= 0
    @total += n
    return
    end
    end

    View full-size slide

  448. class Calculator
    attr_reader :total
    def new_tally(n)
    @total ||= 0
    return if n.odd?
    # ^ FIXME later
    @total ||= 0
    @total += n
    return
    end
    end

    View full-size slide

  449. def test_new_tally
    Suture.verify :tally,
    subject: ->(calc, n){
    calc.new_tally(n)
    calc.total
    }
    end

    View full-size slide

  450. def test_new_tally
    Suture.verify :tally,
    subject: ->(calc, n){
    calc.new_tally(n)
    calc.total
    }
    end

    View full-size slide


  451. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    Delete

    View full-size slide

  452. Our mission
    Development

    View full-size slide

  453. Our mission
    Development
    Testing

    View full-size slide

  454. Our mission
    Development
    Testing
    Staging

    View full-size slide

  455. Our mission
    Development
    Testing
    Staging
    Production

    View full-size slide

  456. Our progress

    View full-size slide

  457. ✅Development
    Our progress

    View full-size slide

  458. ✅Development
    ✅Testing
    Our progress

    View full-size slide

  459. ✅Development
    ✅Testing
    ❓Staging
    Our progress

    View full-size slide

  460. ✅Development
    ✅Testing
    ❓Staging
    ❓Production
    Our progress

    View full-size slide

  461. Pure function

    View full-size slide

  462. 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

    View full-size slide

  463. 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

    View full-size slide

  464. 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

    View full-size slide

  465. 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

    View full-size slide

  466. 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

    View full-size slide

  467. You will find surprising
    inputs & outputs

    View full-size slide

  468. 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

    View full-size slide

  469. 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

    View full-size slide

  470. 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

    View full-size slide

  471. 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

    View full-size slide

  472. Another huge
    error message

    View full-size slide

  473. 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: ```
    [, 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.

    View full-size slide

  474. Suture is raising this error because
    the `:call_both` option is enabled,
    because both code paths are expected
    to return the same result.
    Arguments: ```
    [, 2]
    ```
    The new code path returned: ```
    2
    ```
    The old code path returned: ```
    4
    ```

    View full-size slide

  475. Suture is raising this error because
    the `:call_both` option is enabled,
    because both code paths are expected
    to return the same result.
    Arguments: ```
    [, 2]
    ```
    The new code path returned: ```
    2
    ```
    The old code path returned: ```
    4
    ```

    View full-size slide

  476. Suture is raising this error because
    the `:call_both` option is enabled,
    because both code paths are expected
    to return the same result.
    Arguments: ```
    [, 2]
    ```
    The new code path returned: ```
    2
    ```
    The old code path returned: ```
    4
    ```

    View full-size slide

  477. Suture is raising this error because
    the `:call_both` option is enabled,
    because both code paths are expected
    to return the same result.
    Arguments: ```
    [, 2]
    ```
    The new code path returned: ```
    2
    ```
    The old code path returned: ```
    4
    ```

    View full-size slide

  478. 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

    View full-size slide

  479. 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

    View full-size slide

  480. 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

    View full-size slide

  481. 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

    View full-size slide

  482. 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

    View full-size slide

  483. 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

    View full-size slide

  484. 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

    View full-size slide

  485. 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!

    View full-size slide

  486. 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

    View full-size slide

  487. 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

    View full-size slide

  488. 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

    View full-size slide

  489. 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

    View full-size slide

  490. 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

    View full-size slide

  491. 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

    View full-size slide

  492. 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

    View full-size slide

  493. 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

    View full-size slide

  494. Remember: every
    mode is optional!

    View full-size slide


  495. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    Delete

    View full-size slide

  496. Make change
    safe for users

    View full-size slide

  497. New path errored?
    Try the old one!

    View full-size slide

  498. Pure function

    View full-size slide

  499. 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

    View full-size slide

  500. 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

    View full-size slide

  501. 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

    View full-size slide

  502. 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

    View full-size slide

  503. 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

    View full-size slide

  504. 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

    View full-size slide

  505. 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

    View full-size slide

  506. 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

    View full-size slide

  507. 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

    View full-size slide

  508. 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

    View full-size slide

  509. 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

    View full-size slide

  510. Faster than
    call_both

    View full-size slide

  511. Fewer side effects

    View full-size slide

  512. All errors are logged


    View full-size slide

  513. Allow certain errors via
    expected_error_types

    View full-size slide


  514. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    Delete

    View full-size slide

  515. Like stitches, remove
    once the wound heals

    View full-size slide

  516. Pure function

    View full-size slide

  517. def test_new_add
    calc = Calculator.new
    Suture.verify :add,
    subject: calc.method(:new_add)
    end

    View full-size slide

  518. Suture.delete_all!(:add)

    View full-size slide

  519. 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

    View full-size slide

  520. 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

    View full-size slide

  521. 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

    View full-size slide

  522. 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

    View full-size slide

  523. 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

    View full-size slide

  524. 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

    View full-size slide

  525. 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

    View full-size slide

  526. 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

    View full-size slide

  527. class Controller
    def show
    calc = Calculator.new
    @result = calc.new_add(
    params[:left],
    params[:right]
    )
    end
    end

    View full-size slide

  528. class Controller
    def show
    calc = Calculator.new
    @result = calc.new_add(
    params[:left],
    params[:right]
    )
    end
    end

    View full-size slide

  529. def test_new_tally
    Suture.verify :tally,
    subject: ->(calc, n){
    calc.new_tally(n)
    calc.total
    }
    end

    View full-size slide

  530. Suture.delete_all!(:tally)

    View full-size slide

  531. 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

    View full-size slide

  532. 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

    View full-size slide

  533. 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

    View full-size slide

  534. 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

    View full-size slide

  535. 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

    View full-size slide

  536. 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

    View full-size slide

  537. 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

    View full-size slide

  538. 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

    View full-size slide

  539. 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

    View full-size slide

  540. 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

    View full-size slide

  541. 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

    View full-size slide

  542. 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

    View full-size slide

  543. calc = Calculator.new
    params[:nums].each {|n|
    calc.new_tally(n)
    }
    @result = calc.total

    View full-size slide

  544. calc = Calculator.new
    params[:nums].each {|n|
    calc.new_tally(n)
    }
    @result = calc.total

    View full-size slide

  545. Suture is ready to use!

    View full-size slide

  546. Suture is ready to use!

    github.com/testdouble/suture

    View full-size slide

  547. Suture is ready to use!

    1.0.0

    View full-size slide

  548. Together, let's make
    refactors less scary

    View full-size slide

  549. One last thing…

    View full-size slide

  550. My homestay brother
    was also a programmer

    View full-size slide

  551. օ͞Μ΁ɺ
    ͓࿩͢ΔػձΛ
    ͍͖ͨͩ͋Γ͕ͱ͏ʂ

    View full-size slide

  552. օ͞Μ΁ɺ
    ͓࿩͢ΔػձΛ
    ͍͖ͨͩ͋Γ͕ͱ͏ʂ
    ײँͷؾ࣋ͪͰ
    ͍ͬͺ͍Ͱ͢ʂ

    View full-size slide

  553. օ͞Μ΁ɺ
    ͓࿩͢ΔػձΛ
    ͍͖ͨͩ͋Γ͕ͱ͏ʂ
    ײँͷؾ࣋ͪͰ
    ͍ͬͺ͍Ͱ͢ʂ

    View full-size slide

  554. I'm @searls—tell me
    what you think !

    View full-size slide

  555. I'm in Kansai all month!
    [email protected]

    View full-size slide


  556. ɹ
    ΋͏Ұճ͋Γ͕ͱ͏ʂ

    View full-size slide