$30 off During Our Annual Pro Sale. View Details »

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. View Slide

  2. Hey, I'm @searls!
    [email protected]

    View Slide

  3. Hey, I'm @searls!
    [email protected]

    View Slide

  4. Hey, I'm @αʔϧζ!
    [email protected]

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  8. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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


    View Slide

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

    View Slide

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

    View Slide

  15. I was very nervous
    about screen size

    View Slide

  16. 16
    9

    View Slide

  17. 4
    3

    View Slide

  18. Wait!


    View Slide

  19. The secret to
    Ruby 3×3!

    View Slide

  20. 3
    3

    View Slide

  21. 3
    3

    View Slide

  22. _____ is a massively
    successful language!

    View Slide

  23. Early success

    View Slide

  24. Early success

    View Slide

  25. Early success

    View Slide

  26. Early success

    View Slide

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

    View Slide

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

    View Slide

  29. Later success

    View Slide

  30. Later success

    View Slide

  31. Later success

    View Slide

  32. Later success

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  37. Today, let's refactor
    some legacy code

    View Slide

  38. Today, let's refactor
    some legacy code

    View Slide

  39. Refactor - verb

    View Slide

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

    View Slide

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

    View Slide

  42. Today, let's refactor
    some legacy code

    View Slide

  43. Today, let's refactor
    some legacy code

    View Slide

  44. Legacy code has
    many definitions

    View Slide

  45. Legacy Code - noun

    View Slide

  46. Legacy Code - noun
    Old code.

    View Slide

  47. Legacy Code - noun
    Code without tests.

    View Slide

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

    View Slide

  49. Today, my definition is:

    View Slide

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

    View Slide

  51. Today, let's refactor
    some legacy code

    View Slide

  52. Refactoring is hard

    View Slide

  53. Refactoring legacy
    code is very hard

    View Slide

  54. Easy to accidentally
    break functionality

    View Slide

  55. Legacy refactors
    often feel unsafe

    View Slide

  56. Legacy refactors
    are hard to sell

    View Slide

  57. View Slide

  58. Business Priority

    View Slide

  59. Business Priority
    Cost/Risk

    View Slide

  60. Business Priority
    Cost/Risk
    New
    Features

    View Slide

  61. Business Priority
    Cost/Risk
    New
    Features
    Bug
    Fixes

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  70. Refactors are hard

    View Slide

  71. Refactors are hard

    View Slide

  72. Refactors are hard

    View Slide

  73. Refactors are hard

    View Slide

  74. As complexity goes up

    View Slide

  75. As complexity goes up

    Greater importance

    View Slide

  76. As complexity goes up

    Less certain

    View Slide

  77. As complexity goes up

    More costly

    View Slide

  78. Make Refactors
    Great Again

    View Slide

  79. View Slide

  80. Make Refactors
    Great for the 1st Time

    View Slide

  81. Business Priority
    Cost/Risk
    Refactoring

    View Slide

  82. Business Priority
    Cost/Risk
    Refactoring

    View Slide

  83. Business Priority
    Cost/Risk
    Refactoring

    View Slide

  84. Selling refactoring
    to businesspeople


    View Slide

  85. Selling refactoring
    to businesspeople



    View Slide

  86. Selling refactoring
    to businesspeople




    View Slide

  87. 1. Scare them!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  93. 2. Absorb the cost

    View Slide

  94. 2. Absorb the cost
    New Feature Activities

    View Slide

  95. 2. Absorb the cost
    Planning
    New Feature Activities

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  102. 3. Take hostages

    View Slide

  103. 3. Take hostages
    Feature #1

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  111. Refactoring
    is hard to sell

    View Slide

  112. Business Priority
    Cost/Risk
    Refactoring

    View Slide

  113. Business Priority
    Cost/Risk
    Refactoring

    View Slide

  114. Business Priority
    Cost/Risk
    Refactoring

    View Slide

  115. Business Priority
    Cost/Risk
    Refactoring

    View Slide

  116. Too much pressure!

    View Slide

  117. Too much pressure!

    View Slide

  118. Too much pressure!


    View Slide

  119. Too much pressure!


    View Slide

  120. Refactors are scary!

    View Slide

  121. You should
    buy my book!

    View Slide

  122. THE
    FRIGHTENED
    PROGRAMMER
    JUSTIN SEARLS


    UGH SOFTWARE

    View Slide

  123. Business Priority
    Cost/Risk
    Refactoring

    View Slide

  124. Business Priority
    Cost/Risk
    Refactoring

    View Slide

  125. 1. Refactoring Patterns

    View Slide

  126. 1. Refactoring Patterns

    View Slide

  127. 1. Refactoring Patterns
    • Extract method

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  131. View Slide

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

    View Slide

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

    View Slide

  134. 2. Characterization Testing

    View Slide

  135. 2. Characterization Testing

    View Slide

  136. 2. Characterization Testing

    View Slide

  137. 2. Characterization Testing

    View Slide

  138. 2. Characterization Testing

    View Slide

  139. 2. Characterization Testing

    View Slide

  140. 2. Characterization Testing

    View Slide

  141. 2. Characterization Testing

    View Slide

  142. 2. Characterization Testing

    View Slide

  143. 2. Characterization Testing

    View Slide

  144. 2. Characterization Testing

    View Slide

  145. 2. Characterization Testing

    View Slide

  146. 2. Characterization Testing
    No wrong answers!

    View Slide

  147. 2. Characterization Testing

    View Slide

  148. 2. Characterization Testing

    View Slide

  149. 2. Characterization Testing

    View Slide

  150. 2. Characterization Testing

    View Slide

  151. 2. Characterization Testing

    View Slide

  152. 2. Characterization Testing

    View Slide

  153. 2. Characterization Testing

    View Slide

  154. 2. Characterization Testing

    View Slide

  155. 2. Characterization Testing

    View Slide

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

    View Slide

  157. 2. Characterization Testing

    View Slide

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

    View Slide

  159. 2. Characterization Testing
    Tempting to quit
    halfway through

    View Slide

  160. 3. A/B Testing / Experiments

    View Slide

  161. 3. A/B Testing / Experiments

    View Slide

  162. 3. A/B Testing / Experiments

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  168. 3. A/B Testing / Experiments

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  173. View Slide

  174. Characterization Testing

    View Slide

  175. View Slide


  176. A/B Experiments

    View Slide

  177. Development

    View Slide

  178. Development
    Testing

    View Slide

  179. Development
    Testing
    Staging

    View Slide

  180. Development
    Testing
    Staging
    Production

    View Slide

  181. Development
    Testing
    Staging
    Production

    View Slide

  182. Development
    Testing
    Staging
    Production
    Development

    View Slide

  183. Development
    Testing
    Staging
    Production
    Development
    Testing

    View Slide

  184. Development
    Testing
    Staging
    Production
    Development
    Testing
    Staging

    View Slide

  185. Development
    Testing
    Staging
    Production
    Development
    Testing
    Staging
    Production

    View Slide

  186. Development
    Testing
    Staging
    Production
    De
    Te
    St
    Pr

    View Slide

  187. Development
    Testing
    Staging
    Production
    De
    Te
    St
    Pr

    View Slide

  188. Development
    Testing
    Staging
    Production
    De
    Te
    St
    Pr
    Development

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide


  193. View Slide

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

    View Slide

  195. View Slide

  196. View Slide



  197. View Slide

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

    View Slide

  199. TDD

    View Slide

  200. TDD
    (Talk-Driven Development)

    View Slide


  201. suture

    View Slide

  202. github.com/testdouble/suture

    View Slide

  203. View Slide

  204. $ gem install suture

    View Slide

  205. Refactors as
    Surgeries

    View Slide

  206. Refactors as Surgeries

    View Slide

  207. Refactors as Surgeries
    Serve a common purpose

    View Slide

  208. Refactors as Surgeries
    Serve a common purpose

    View Slide

  209. Refactors as Surgeries
    Require careful planning

    View Slide

  210. Refactors as Surgeries
    Flexible tools

    View Slide

  211. Refactors as Surgeries
    Flexible tools


    View Slide

  212. Refactors as Surgeries
    Flexible tools


    View Slide

  213. Refactors as Surgeries
    Follow a process

    View Slide

  214. Refactors as Surgeries
    Follow a process

    View Slide

  215. Refactors as Surgeries
    Follow a process


    View Slide

  216. Refactors as Surgeries
    Multiple Observations

    View Slide

  217. Refactors as Surgeries
    Multiple Observations


    View Slide

  218. Refactors as Surgeries
    Multiple Observations


    View Slide

  219. 9

    View Slide

  220. 9
    F
    E
    A
    T
    U
    R
    E
    S

    View Slide


  221. Plan

    View Slide


  222. Plan

    Cut

    View Slide


  223. Plan

    Cut

    Record

    View Slide


  224. Plan

    Cut

    Record

    Validate

    View Slide


  225. Plan

    Cut

    Record

    Validate

    Refactor

    View Slide


  226. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    View Slide


  227. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare

    View Slide


  228. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    View Slide


  229. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    Delete

    View Slide


  230. Plan

    View Slide

  231. Two Bug Fixes:

    View Slide

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

    View Slide

  233. Pure function

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  248. Mutation


    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide


  264. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    Delete

    View Slide


  265. Cut

    View Slide

  266. Pure function

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  276. Mutation


    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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!

    View Slide

  283. How to design a seam

    View Slide

  284. ❄Pure functions are easy

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  291. Mutation is hard


    View Slide

  292. Mutation is hard
    Calculator#tally(n)


    View Slide

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


    View Slide

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


    View Slide

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


    (4)

    View Slide

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


    (4) 8

    View Slide

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


    (4) 8
    @total=

    View Slide

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


    (4) 8

    View Slide

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


    (4) 8
    (calc@0,4)

    View Slide

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


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

    View Slide

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


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

    View Slide

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


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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide


  310. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    Delete

    View Slide


  311. Record

    View Slide

  312. Pure function

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  317. Record some calls!

    View Slide

  318. Record via CLI

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  327. Record via browser

    View Slide

  328. Record via browser
    add(4,5)

    View Slide

  329. Record via browser
    add(4,5)

    View Slide

  330. Record in production!

    View Slide

  331. Record in production!


    View Slide

  332. Record in production!



    View Slide

  333. Mutation


    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  345. Where does it go?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  351. What about Rails?

    View Slide

  352. Gilded Rose Kata

    View Slide

  353. View Slide

  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

    View Slide

  355. View Slide

  356. View Slide

  357. View Slide

  358. View Slide

  359. View Slide

  360. View Slide

  361. View Slide

  362. View Slide

  363. View Slide

  364. View Slide

  365. View Slide

  366. View Slide

  367. View Slide

  368. View Slide

  369. View Slide

  370. View Slide

  371. It apparently works

    View Slide


  372. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    Delete

    View Slide


  373. Validate

    View Slide

  374. Pure function

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  380. 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  385. Mutation


    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  392. Finally, a
    good use
    for code
    coverage!

    View Slide

  393. Gilded Rose Kata

    View Slide

  394. Trial # 1
    Characterization tests

    View Slide

  395. View Slide

  396. View Slide

  397. Trial # 2
    Suture.verify

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  406. Check coverage
    before continuing

    View Slide

  407. View Slide

  408. 100% Coverage
    and zero tests

    View Slide


  409. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    Delete

    View Slide


  410. Refactor

    View Slide

  411. I'm no refactoring expert

    View Slide


  412. I'm no refactoring expert

    View Slide


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

    View Slide

  414. View Slide

  415. Unlike my book,
    this book exists

    View Slide

  416. Pure function

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  426. Mutation


    View Slide

  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

    View Slide

  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!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide


  438. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    Delete

    View Slide


  439. Verify

    View Slide

  440. Pure function

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  447. Mutation


    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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


    View Slide

  455. Judge a library
    by its messages

    View Slide

  456. View Slide

  457. View Slide

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

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  463. Failure advice

    View Slide

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

    View Slide

  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

    View Slide

  466. Comparing Results

    View Slide

  467. Default Comparator

    View Slide

  468. ==

    View Slide

  469. or

    View Slide

  470. )

    (
    Marshall.dump
    Marshall.dump
    ==
    )

    (

    View Slide

  471. What about
    ActiveRecord?!

    View Slide

  472. .attributes
    ==

    AR

    AR .attributes

    View Slide

  473. View Slide

  474. !=

    View Slide

  475. !=

    View Slide


  476. !=

    View Slide

  477. Custom Comparators
    p q r s t

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  485. Classes also exist!

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  492. Returning to the
    error message

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  496. Discoverable
    configuration

    View Slide

  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
    }
    ```

    View Slide

  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
    }
    ```

    View Slide

  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
    }
    ```

    View Slide

  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
    }
    ```

    View Slide

  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
    }
    ```

    View Slide

  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
    }
    ```

    View Slide

  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
    }
    ```

    View Slide

  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
    }
    ```

    View Slide

  505. A sense of progress

    View Slide

  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%!

    View Slide

  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%!

    View Slide

  508. Judge a library
    by its messages

    View Slide

  509. Wait, why did
    verification fail?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide


  519. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    Delete

    View Slide


  520. Compare

    View Slide

  521. Our mission

    View Slide

  522. Our mission
    Development

    View Slide

  523. Our mission
    Development
    Testing

    View Slide

  524. Our mission
    Development
    Testing
    Staging

    View Slide

  525. Our mission
    Development
    Testing
    Staging
    Production

    View Slide

  526. Our progress

    View Slide

  527. ✅Development
    Our progress

    View Slide

  528. ✅Development
    ✅Testing
    Our progress

    View Slide

  529. ✅Development
    ✅Testing
    ❓Staging
    Our progress

    View Slide

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

    View Slide

  531. Pure function

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  537. You will find surprising
    inputs & outputs

    View Slide

  538. Mutation


    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  543. Another huge
    error message

    View Slide

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

  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: ```
    [, 2]
    ```
    The new code path returned: ```
    2
    ```
    The old code path returned: ```
    4
    ```

    View Slide

  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: ```
    [, 2]
    ```
    The new code path returned: ```
    2
    ```
    The old code path returned: ```
    4
    ```

    View Slide

  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: ```
    [, 2]
    ```
    The new code path returned: ```
    2
    ```
    The old code path returned: ```
    4
    ```

    View Slide

  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: ```
    [, 2]
    ```
    The new code path returned: ```
    2
    ```
    The old code path returned: ```
    4
    ```

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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!

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  565. Remember: every
    mode is optional!

    View Slide


  566. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    Delete

    View Slide

  567. Fallback

    View Slide

  568. Make change
    safe for users

    View Slide

  569. New path errored?
    Try the old one!

    View Slide

  570. Pure function

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  577. Mutation


    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  583. Faster than
    call_both

    View Slide

  584. Fewer side effects

    View Slide

  585. All errors are logged


    View Slide

  586. Allow certain errors via
    expected_error_types

    View Slide


  587. Plan

    Cut

    Record

    Validate

    Refactor

    Verify

    Compare
    Fallback

    Delete

    View Slide

  588. Delete

    View Slide

  589. Like stitches, remove
    once the wound heals

    View Slide

  590. Pure function

    View Slide

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

    View Slide

  592. View Slide

  593. Suture.delete_all!(:add)

    View Slide

  594. View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  605. Mutation


    View Slide

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

    View Slide

  607. View Slide

  608. Suture.delete_all!(:tally)

    View Slide

  609. View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  624. We did it!

    View Slide

  625. Suture is ready to use!

    View Slide

  626. Suture is ready to use!

    github.com/testdouble/suture

    View Slide

  627. Suture is ready to use!

    1.0.0

    View Slide

  628. Together, let's make
    refactors less scary

    View Slide

  629. One last thing…

    View Slide

  630. View Slide

  631. View Slide

  632. View Slide

  633. View Slide

  634. View Slide

  635. View Slide

  636. My homestay brother
    was also a programmer

    View Slide

  637. View Slide

  638. View Slide

  639. View Slide

  640. View Slide

  641. View Slide

  642. View Slide

  643. View Slide

  644. View Slide

  645. View Slide

  646. View Slide

  647. View Slide

  648. View Slide



  649. View Slide

  650. օ͞Μ΁ɺ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide


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

    View Slide