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

Polly Want A Message (DeconstructConf)

Polly Want A Message (DeconstructConf)

Object-oriented languages have opinions about how best to arrange code. These opinions lead OO to naturally offer certain _affordances_. Just as round doorknobs expect to be grasped and rotated, OO expects messages, polymorphism, loose-coupling and factories. The key to creating intuitive and maintainable OO applications is to understand and embrace these built-in affordances.

Sandi Metz

May 21, 2018
Tweet

More Decks by Sandi Metz

Other Decks in Programming

Transcript

  1. May 2018
    @sandimetz
    Sandi Metz
    Polly
    want a
    Message

    View Slide

  2. @sandimetz May 2018
    Path to Pain

    View Slide

  3. @sandimetz May 2018
    Design Stamina
    Hypothesis
    - Martin Fowler

    View Slide

  4. @sandimetz May 2018
    #1
    Design Stamina
    Hypothesis
    - Martin Fowler

    View Slide

  5. @sandimetz May 2018

    View Slide

  6. @sandimetz May 2018

    View Slide

  7. @sandimetz May 2018
    Over Design

    View Slide

  8. @sandimetz May 2018

    View Slide

  9. @sandimetz May 2018

    View Slide

  10. @sandimetz May 2018
    Under Design

    View Slide

  11. @sandimetz May 2018

    View Slide

  12. @sandimetz May 2018

    View Slide

  13. @sandimetz May 2018

    View Slide

  14. @sandimetz May 2018

    View Slide

  15. @sandimetz May 2018

    View Slide

  16. @sandimetz May 2018
    ???

    View Slide

  17. @sandimetz May 2018

    View Slide

  18. @sandimetz May 2018

    View Slide

  19. @sandimetz May 2018
    Ouch

    View Slide

  20. @sandimetz May 2018
    Procedures vs OO
    -Me

    View Slide

  21. @sandimetz May 2018
    #2
    Procedures vs OO
    -Me

    View Slide

  22. @sandimetz May 2018

    View Slide

  23. @sandimetz May 2018

    View Slide

  24. @sandimetz May 2018

    View Slide

  25. @sandimetz May 2018

    View Slide

  26. @sandimetz May 2018

    View Slide

  27. @sandimetz May 2018

    View Slide

  28. @sandimetz May 2018
    Ouch

    View Slide

  29. @sandimetz May 2018
    Churn vs. Complexity
    - Michael Feathers

    View Slide

  30. @sandimetz May 2018
    #3
    Churn vs. Complexity
    - Michael Feathers

    View Slide

  31. @sandimetz May 2018

    View Slide

  32. @sandimetz May 2018
    Ouch

    View Slide

  33. @sandimetz May 2018
    What matters, suffers
    -Me

    View Slide

  34. @sandimetz May 2018
    #4
    What matters, suffers
    -Me

    View Slide

  35. @sandimetz May 2018

    View Slide

  36. @sandimetz May 2018
    GPA 1.8

    View Slide

  37. @sandimetz May 2018
    GPA 1.8

    View Slide

  38. @sandimetz May 2018
    GPA 1.8

    View Slide

  39. @sandimetz May 2018

    View Slide

  40. @sandimetz May 2018
    GPA 2.58

    View Slide

  41. @sandimetz May 2018
    GPA 2.58

    View Slide

  42. @sandimetz May 2018
    GPA 2.58

    View Slide

  43. @sandimetz May 2018

    View Slide

  44. @sandimetz May 2018
    GPA 2.97

    View Slide

  45. @sandimetz May 2018
    GPA 2.97

    View Slide

  46. @sandimetz May 2018
    GPA 2.97

    View Slide

  47. @sandimetz May 2018

    View Slide

  48. @sandimetz May 2018
    #1 Design Stamina Hypothesis

    View Slide

  49. @sandimetz May 2018
    #1 Design Stamina Hypothesis
    #2 Procedures vs OO

    View Slide

  50. @sandimetz May 2018
    #1 Design Stamina Hypothesis
    #2 Procedures vs OO
    #3 Churn vs Complexity

    View Slide

  51. @sandimetz May 2018
    #1 Design Stamina Hypothesis
    #2 Procedures vs OO
    #3 Churn vs Complexity
    #4 What matters, suffers

    View Slide

  52. @sandimetz May 2018
    Interlude

    View Slide

  53. @sandimetz May 2018
    Wherein things go
    badly wrong
    Interlude

    View Slide

  54. @sandimetz May 2018
    File.read("/path/to/filename").split("\n")

    View Slide

  55. @sandimetz May 2018
    Change #1

    View Slide

  56. @sandimetz May 2018
    Change #1
    Read from
    Git Tag

    View Slide

  57. @sandimetz May 2018
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end

    View Slide

  58. @sandimetz May 2018
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end

    View Slide

  59. @sandimetz May 2018
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end

    View Slide

  60. @sandimetz May 2018
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end

    View Slide

  61. @sandimetz May 2018
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end

    View Slide

  62. @sandimetz May 2018
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end

    View Slide

  63. @sandimetz May 2018
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end

    View Slide

  64. @sandimetz May 2018
    def lines
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end
    end

    View Slide

  65. @sandimetz May 2018
    class Listing
    attr_reader :filename, :repository, :tag, :git_cmd
    def initialize(filename:, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end
    end
    end

    View Slide

  66. @sandimetz May 2018
    class Listing
    attr_reader :filename, :repository, :tag, :git_cmd
    def initialize(filename:, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end
    end
    end

    View Slide

  67. @sandimetz May 2018
    class Listing
    attr_reader :filename, :repository, :tag, :git_cmd
    def initialize(filename:, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end
    end
    end

    View Slide

  68. @sandimetz May 2018
    class Listing
    attr_reader :filename, :repository, :tag, :git_cmd
    def initialize(filename:, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end
    end
    end

    View Slide

  69. @sandimetz May 2018
    class Listing
    attr_reader :filename, :repository, :tag, :git_cmd
    def initialize(filename:, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end
    end
    end

    View Slide

  70. @sandimetz May 2018
    Change #2

    View Slide

  71. @sandimetz May 2018
    Change #2
    Partial listings

    View Slide

  72. @sandimetz May 2018
    Change #2
    Partial listings
    "1, 3-4, 15, 37-50"

    View Slide

  73. @sandimetz May 2018
    Easy
    is the enemy of
    simple

    View Slide

  74. @sandimetz May 2018
    class Listing
    attr_reader :filename, :repository, :tag, :git_cmd
    def initialize(filename:, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    # ...
    end

    View Slide

  75. @sandimetz May 2018
    class Listing
    attr_reader :filename, :repository, :tag, :git_cmd
    def initialize(filename:, repository: nil, tag: nil,
    git_cmd: nil)
    @filename = filename
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    # ...
    end

    View Slide

  76. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, repository: nil, tag: nil,
    git_cmd: nil)
    @filename = filename
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    # ...
    end

    View Slide

  77. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, repository: nil, tag: nil,
    git_cmd: nil)
    @filename = filename
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    # ...
    end

    View Slide

  78. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, repository: nil, tag: nil,
    git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    # ...
    end

    View Slide

  79. @sandimetz May 2018
    class Listing
    # ...

    View Slide

  80. @sandimetz May 2018
    class Listing
    # ...
    def lines
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end
    end

    View Slide

  81. @sandimetz May 2018
    class Listing
    # ...
    def lines
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end
    end New Conditional Here

    View Slide

  82. @sandimetz May 2018
    class Listing
    # ...
    def lines
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end
    end

    View Slide

  83. @sandimetz May 2018
    class Listing
    # ...
    def lines
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end
    end
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end

    View Slide

  84. @sandimetz May 2018
    class Listing
    # ...
    def lines
    if git_cmd
    git_lines
    else
    File.read(filename).split("\n")
    end
    end
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end

    View Slide

  85. @sandimetz May 2018
    class Listing
    # ...
    def lines
    if git_cmd
    git_lines
    else
    File.read(filename).split("\n")
    end
    end

    View Slide

  86. @sandimetz May 2018
    class Listing
    # ...
    def lines
    if git_cmd
    git_lines
    else
    File.read(filename).split("\n")
    end
    end

    View Slide

  87. @sandimetz May 2018
    class Listing
    # ...
    def lines
    if git_cmd
    git_lines
    else
    File.read(filename).split("\n")
    end
    end

    View Slide

  88. @sandimetz May 2018
    class Listing
    # ...
    def lines
    if git_cmd
    git_lines
    else
    File.read(filename).split("\n")
    end
    end
    def file_lines
    File.read(filename).split("\n")
    end

    View Slide

  89. @sandimetz May 2018
    class Listing
    # ...
    def lines
    if git_cmd
    git_lines
    else
    file_lines
    end
    end
    def file_lines
    File.read(filename).split("\n")
    end

    View Slide

  90. @sandimetz May 2018
    class Listing
    # ...
    def lines
    if git_cmd
    git_lines
    else
    file_lines
    end
    end

    View Slide

  91. @sandimetz May 2018
    class Listing
    # ...
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    end

    View Slide

  92. @sandimetz May 2018
    class Listing
    # ...
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    end

    View Slide

  93. @sandimetz May 2018
    class Listing
    # ...
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end

    View Slide

  94. @sandimetz May 2018
    class Listing
    # ...
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end

    View Slide

  95. @sandimetz May 2018
    # "1, 3-4, 15, 37-50"
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end

    View Slide

  96. @sandimetz May 2018
    # "1, 3-4, 15, 37-50"
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end

    View Slide

  97. @sandimetz May 2018
    # "1, 3-4, 15, 37-50"
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end

    View Slide

  98. @sandimetz May 2018
    # "1, 3-4, 15, 37-50"
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end

    View Slide

  99. @sandimetz May 2018
    # "1, 3-4, 15, 37-50"
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end

    View Slide

  100. @sandimetz May 2018
    # "1, 3-4, 15, 37-50"
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end
    Ouch

    View Slide

  101. @sandimetz May 2018
    # "1, 3-4, 15, 37-50"
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end

    View Slide

  102. @sandimetz May 2018
    Change #3

    View Slide

  103. @sandimetz May 2018
    Change #3
    Dynamic Comments

    View Slide

  104. @sandimetz May 2018
    Change #3
    Dynamic Comments
    "1, 3-4, 15, 37-50"

    View Slide

  105. @sandimetz May 2018
    Change #3
    Dynamic Comments
    "1, 3-4, 15, 37-50"

    View Slide

  106. @sandimetz May 2018
    Change #3
    Dynamic Comments
    "1, #4, 3-4, #6, 15, 37-50"

    View Slide

  107. @sandimetz May 2018
    # "1, 3-4, 15, 37-50"
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end

    View Slide

  108. @sandimetz May 2018
    # "1, #4, 3-4, #6, 15, 37-50"
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end

    View Slide

  109. @sandimetz May 2018
    # "1, #4, 3-4, #6, 15, 37-50"
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end

    View Slide

  110. @sandimetz May 2018
    # "1, #4, 3-4, #6, 15, 37-50"
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end New Conditional Here

    View Slide

  111. @sandimetz May 2018
    # "1, #4, 3-4, #6, 15, 37-50"
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end New Conditional Here

    View Slide

  112. @sandimetz May 2018
    # "1, #4, 3-4, #6, 15, 37-50"
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end

    View Slide

  113. @sandimetz May 2018
    # "1, #4, 3-4, #6, 15, 37-50"
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end

    View Slide

  114. @sandimetz May 2018
    # "1, #4, 3-4, #6, 15, 37-50"
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    else
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end

    View Slide

  115. @sandimetz May 2018
    # "1, #4, 3-4, #6, 15, 37-50"
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end

    View Slide

  116. @sandimetz May 2018
    Change #4

    View Slide

  117. @sandimetz May 2018
    Change #4
    Left Justification

    View Slide

  118. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, repository: nil, tag: nil,
    git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    # ...

    View Slide

  119. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil,
    repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    # ...

    View Slide

  120. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :left_just, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, left_justify: false,
    repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    # ...

    View Slide

  121. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :left_just, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, left_justify: false,
    repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    # ...

    View Slide

  122. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :left_just, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, left_justify: false,
    repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @left_just = left_justify
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    # ...

    View Slide

  123. @sandimetz May 2018
    class Listing
    # ...

    View Slide

  124. @sandimetz May 2018
    class Listing
    # ...
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end

    View Slide

  125. @sandimetz May 2018
    class Listing
    # ...
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end Yup

    View Slide

  126. @sandimetz May 2018
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end

    View Slide

  127. @sandimetz May 2018
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end

    View Slide

  128. @sandimetz May 2018
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end

    View Slide

  129. @sandimetz May 2018
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    end

    View Slide

  130. @sandimetz May 2018
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    end

    View Slide

  131. @sandimetz May 2018
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    end

    View Slide

  132. @sandimetz May 2018
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end

    View Slide

  133. @sandimetz May 2018
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end

    View Slide

  134. @sandimetz May 2018
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces_to_remove(lines)..-1) || ""}
    end
    def num_leading_spaces_to_remove(lines)
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end

    View Slide

  135. @sandimetz May 2018
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces_to_remove(lines)..-1) || ""}
    end
    def num_leading_spaces_to_remove(lines)
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end

    View Slide

  136. @sandimetz May 2018
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces_to_remove(lines)..-1) || ""}
    end
    def num_leading_spaces_to_remove(lines)
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end

    View Slide

  137. @sandimetz May 2018
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces_to_remove(lines)..-1) || ""}
    end
    def num_leading_spaces_to_remove(lines)
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    Ouch

    View Slide

  138. @sandimetz May 2018
    Progression

    View Slide

  139. @sandimetz May 2018
    1 File or Git Tag

    View Slide

  140. @sandimetz May 2018
    class Listing
    attr_reader :filename, :repository, :tag, :git_cmd
    def initialize(filename:, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    1
    Lines: 29

    View Slide

  141. @sandimetz May 2018
    class Listing
    attr_reader :filename, :repository, :tag, :git_cmd
    def initialize(filename:, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    1

    View Slide

  142. @sandimetz May 2018
    class Listing
    attr_reader :filename, :repository, :tag, :git_cmd
    def initialize(filename:, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    1
    Execution
    Paths: 2

    View Slide

  143. @sandimetz May 2018
    class Listing
    attr_reader :filename, :repository, :tag, :git_cmd
    def initialize(filename:, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    1

    View Slide

  144. @sandimetz May 2018
    2All or Some Lines

    View Slide

  145. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end
    private
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    2
    Lines: 50

    View Slide

  146. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end
    private
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    2
    Execution
    Paths:

    View Slide

  147. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end
    private
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    2
    Execution
    Paths: 4

    View Slide

  148. @sandimetz May 2018
    class Listing
    attr_reader :filename, :repository, :tag, :git_cmd
    def initialize(filename:, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    class Listing
    attr_reader :filename, :line_numbers, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end
    private
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    1 2

    View Slide

  149. @sandimetz May 2018
    3Code Line or Comment

    View Slide

  150. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end
    private
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    3
    Lines: 55

    View Slide

  151. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end
    private
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    3
    Execution
    Paths:

    View Slide

  152. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end
    private
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    3
    Execution
    Paths: 8

    View Slide

  153. @sandimetz May 2018
    class Listing
    attr_reader :filename, :repository, :tag, :git_cmd
    def initialize(filename:, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    class Listing
    attr_reader :filename, :line_numbers, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end
    private
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    1 2
    class Listing
    attr_reader :filename, :line_numbers, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end
    private
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    3

    View Slide

  154. @sandimetz May 2018
    4Raw or Left Just

    View Slide

  155. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :left_just, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, left_justify: false, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @left_just = left_justify
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    private
    ####################
    # Reading
    ####################
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    ####################
    # Subsetting
    ####################
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    ####################
    # Justification
    ####################
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces_to_remove(lines)..-1) || "" }
    end
    def num_leading_spaces_to_remove(lines)
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    # plus 90+ lines of error handling
    4
    Lines: 83

    View Slide

  156. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :left_just, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, left_justify: false, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @left_just = left_justify
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    private
    ####################
    # Reading
    ####################
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    ####################
    # Subsetting
    ####################
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    ####################
    # Justification
    ####################
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces_to_remove(lines)..-1) || "" }
    end
    def num_leading_spaces_to_remove(lines)
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    # plus 90+ lines of error handling
    4
    Execution
    Paths:

    View Slide

  157. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :left_just, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, left_justify: false, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @left_just = left_justify
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    private
    ####################
    # Reading
    ####################
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    ####################
    # Subsetting
    ####################
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    ####################
    # Justification
    ####################
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces_to_remove(lines)..-1) || "" }
    end
    def num_leading_spaces_to_remove(lines)
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    # plus 90+ lines of error handling
    4
    Execution
    Paths: 16

    View Slide

  158. @sandimetz May 2018
    class Listing
    attr_reader :filename, :repository, :tag, :git_cmd
    def initialize(filename:, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    class Listing
    attr_reader :filename, :line_numbers, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end
    private
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    1 2
    class Listing
    attr_reader :filename, :line_numbers, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end
    private
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    3
    class Listing
    attr_reader :filename, :line_numbers, :left_just, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, left_justify: false, repository: nil, tag:
    nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @left_just = left_justify
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    private
    ####################
    # Reading
    ####################
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    ####################
    # Subsetting
    ####################
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    ####################
    # Justification
    ####################
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces_to_remove(lines)..-1) || "" }
    end
    def num_leading_spaces_to_remove(lines)
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    # plus 90+ lines of error handling
    4

    View Slide

  159. @sandimetz May 2018

    View Slide

  160. @sandimetz May 2018
    Ouch

    View Slide

  161. @sandimetz May 2018

    View Slide

  162. @sandimetz May 2018
    Ouch

    View Slide

  163. @sandimetz May 2018

    View Slide

  164. @sandimetz May 2018
    Ouch

    View Slide

  165. @sandimetz May 2018

    View Slide

  166. @sandimetz May 2018
    What brought you success

    View Slide

  167. @sandimetz May 2018
    What brought you success
    will doom you to failure.

    View Slide

  168. @sandimetz May 2018
    Affordances

    View Slide

  169. @sandimetz May 2018
    "Doorknob" by photonooner CC BY-NC-ND

    View Slide

  170. @sandimetz May 2018
    "Doorknob" by photonooner CC BY-NC-ND
    "Door Handle" by www.trek.today CC BY-NC-ND

    View Slide

  171. @sandimetz May 2018
    "Doorknob" by photonooner CC BY-NC-ND
    "Door Handle" by www.trek.today CC BY-NC-ND
    "pull" by various brennemans CC BY-SA

    View Slide

  172. @sandimetz May 2018
    I'm a loser and lost the attribution. Sorry.

    View Slide

  173. @sandimetz May 2018
    "pull" by greenkozi CC BY-NC-ND
    I'm a loser and lost the attribution. Sorry.

    View Slide

  174. @sandimetz May 2018
    "pull" by greenkozi CC BY-NC-ND
    "door push plate" by stu_spivack CC BY-SA
    I'm a loser and lost the attribution. Sorry.

    View Slide

  175. @sandimetz May 2018
    OO
    Affords

    View Slide

  176. @sandimetz May 2018
    OO
    Affords
    Anthropomorphic

    View Slide

  177. @sandimetz May 2018
    OO
    Affords
    Anthropomorphic
    Polymorphic

    View Slide

  178. @sandimetz May 2018
    OO
    Affords
    Anthropomorphic
    Polymorphic
    Loosely-coupled

    View Slide

  179. @sandimetz May 2018
    OO
    Affords
    Anthropomorphic
    Polymorphic
    Loosely-coupled
    Role-playing

    View Slide

  180. @sandimetz May 2018
    OO
    Affords
    Anthropomorphic
    Polymorphic
    Loosely-coupled
    Role-playing
    Factory-created

    View Slide

  181. @sandimetz May 2018
    OO
    Affords
    Anthropomorphic
    Polymorphic
    Loosely-coupled
    Role-playing
    Factory-created
    Message-sending

    View Slide

  182. @sandimetz May 2018
    OO
    Affords
    Anthropomorphic
    Polymorphic
    Loosely-coupled
    Role-playing
    Factory-created
    Message-sending
    Objects

    View Slide

  183. @sandimetz May 2018
    Anthropomorphism

    View Slide

  184. @sandimetz May 2018
    Anthropomorphism
    -the attribution of human traits, emotions or
    intentions to non-human entities.

    View Slide

  185. @sandimetz May 2018

    View Slide

  186. @sandimetz May 2018
    Polymorphism

    View Slide

  187. @sandimetz May 2018
    Polymorphism
    -the condition of occurring in
    several different forms

    View Slide

  188. @sandimetz May 2018

    View Slide

  189. @sandimetz May 2018

    View Slide

  190. @sandimetz May 2018

    View Slide

  191. @sandimetz May 2018
    Loosely-Coupled

    View Slide

  192. @sandimetz May 2018
    Loosely-Coupled
    -objects strive for independence

    View Slide

  193. @sandimetz May 2018

    View Slide

  194. @sandimetz May 2018
    Role-playing

    View Slide

  195. @sandimetz May 2018
    Role-playing
    -objects are more players of their roles
    than instances of their types

    View Slide

  196. @sandimetz May 2018

    View Slide

  197. @sandimetz May 2018
    Factory-created

    View Slide

  198. @sandimetz May 2018
    Factory-created
    -factories hide the rules for picking
    the right player of a role

    View Slide

  199. @sandimetz May 2018

    View Slide

  200. @sandimetz May 2018

    View Slide

  201. @sandimetz May 2018
    Message-sending

    View Slide

  202. @sandimetz May 2018
    Message-sending
    -I know what I want,
    you know how to do it

    View Slide

  203. @sandimetz May 2018

    View Slide

  204. @sandimetz May 2018
    OO
    Affords
    Anthropomorphic
    Polymorphic
    Loosely-coupled
    Role-playing
    Factory-created
    Message-sending
    Objects

    View Slide

  205. @sandimetz May 2018
    Resolution

    View Slide

  206. @sandimetz May 2018
    Where all ends well
    Resolution

    View Slide

  207. @sandimetz May 2018
    class Listing
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end

    View Slide

  208. @sandimetz May 2018
    class Listing
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    2

    View Slide

  209. @sandimetz May 2018
    class Listing
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    2
    2x2

    View Slide

  210. @sandimetz May 2018
    class Listing
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    2
    2x2
    2x2x2

    View Slide

  211. @sandimetz May 2018
    class Listing
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/,
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collec
    range_of_line_numbers = (edges
    range_of_line_numbers.collect
    end
    }.flatten.compact
    end
    2
    2x2
    2x2x2

    View Slide

  212. @sandimetz May 2018
    class Listing
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/,
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collec
    range_of_line_numbers = (edges
    range_of_line_numbers.collect
    end
    }.flatten.compact
    end
    2
    2x2
    2x2x2
    2x2x2x2

    View Slide

  213. @sandimetz May 2018
    class Listing
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end

    View Slide

  214. @sandimetz May 2018
    class Listing
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    If you know enough
    to inject this

    View Slide

  215. @sandimetz May 2018
    class Listing
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    Inject a
    smarter
    thing

    View Slide

  216. @sandimetz May 2018
    class Listing
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    1 File or Git Tag

    View Slide

  217. @sandimetz May 2018
    class Listing
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    1 Role = Source

    View Slide

  218. @sandimetz May 2018
    Source

    View Slide

  219. @sandimetz May 2018
    Source #lines

    View Slide

  220. @sandimetz May 2018
    Source #lines
    File
    Source
    GitTag
    Source

    View Slide

  221. @sandimetz May 2018
    class FileSource
    end
    class GitTagSource
    end
    S
    O
    U
    R
    C
    E
    S

    View Slide

  222. @sandimetz May 2018
    class FileSource
    end
    class GitTagSource
    end
    S
    O
    U
    R
    C
    E
    S

    View Slide

  223. @sandimetz May 2018
    class FileSource
    def lines
    end
    # ...
    end
    class GitTagSource
    def lines
    end
    # ...
    end
    S
    O
    U
    R
    C
    E
    S

    View Slide

  224. @sandimetz May 2018
    class FileSource
    def lines
    end
    # ...
    end
    class GitTagSource
    def lines
    end
    # ...
    end
    S
    O
    U
    R
    C
    E
    S

    View Slide

  225. @sandimetz May 2018
    class FileSource
    def lines
    end
    # ...
    end
    class GitTagSource
    def lines
    end
    # ...
    end
    S
    O
    U
    R
    C
    E
    S
    Polymorphism

    View Slide

  226. @sandimetz May 2018
    class FileSource
    def lines
    File.read(filename).split("\n")
    end
    # ...
    end
    class GitTagSource
    def lines
    end
    # ...
    end
    S
    O
    U
    R
    C
    E
    S

    View Slide

  227. @sandimetz May 2018
    class FileSource
    def lines
    File.read(filename).split("\n")
    end
    # ...
    end
    class GitTagSource
    def lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    # ...
    end
    S
    O
    U
    R
    C
    E
    S

    View Slide

  228. @sandimetz May 2018
    Isolate the things
    you want to vary

    View Slide

  229. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :left_just, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, left_justify: false, repository: nil, tag: nil,
    git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @left_just = left_justify
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset = # ...
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end

    View Slide

  230. @sandimetz May 2018
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    v
    v
    v
    class Listing
    attr_reader :filename, :line_numbers, :left_just, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, left_justify: false, repository: nil, tag: nil,
    git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @left_just = left_justify
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset = # ...

    View Slide

  231. @sandimetz May 2018
    class Listing
    attr_reader :line_numbers, :left_just
    def initialize( line_numbers: nil, left_justify: false)
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset = # ...

    View Slide

  232. @sandimetz May 2018
    class Listing
    attr_reader :line_numbers, :left_just
    def initialize( line_numbers: nil, left_justify: false)
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset = # ...

    View Slide

  233. @sandimetz May 2018
    class Listing
    attr_reader :line_numbers, :left_just
    def initialize( line_numbers: nil, left_justify: false)
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset = # ...

    View Slide

  234. @sandimetz May 2018
    class Listing
    attr_reader :line_numbers, :left_just
    def initialize( line_numbers: nil, left_justify: false)
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset = # ...
    Inject a Source

    View Slide

  235. @sandimetz May 2018
    class Listing
    attr_reader :source, :line_numbers, :left_just
    def initialize(source:, line_numbers: nil, left_justify: false)
    @source = source
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset = # ...

    View Slide

  236. @sandimetz May 2018
    class Listing
    attr_reader :source, :line_numbers, :left_just
    def initialize(source:, line_numbers: nil, left_justify: false)
    @source = source
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset = # ...

    View Slide

  237. @sandimetz May 2018
    class Listing
    attr_reader :source, :line_numbers, :left_just
    def initialize(source:, line_numbers: nil, left_justify: false)
    @source = source
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset = # ...

    View Slide

  238. @sandimetz May 2018
    class Listing
    attr_reader :source, :line_numbers, :left_just
    def initialize(source:, line_numbers: nil, left_justify: false)
    @source = source
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset = # ...

    View Slide

  239. @sandimetz May 2018
    class Listing
    attr_reader :source, :line_numbers, :left_just
    def initialize(source:, line_numbers: nil, left_justify: false)
    @source = source
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset = # ...

    View Slide

  240. @sandimetz May 2018
    class Listing
    attr_reader :source, :line_numbers, :left_just
    def initialize(source:, line_numbers: nil, left_justify: false)
    @source = source
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset = # ...

    View Slide

  241. @sandimetz May 2018
    class Listing
    attr_reader :source, :line_numbers, :left_just
    def initialize(source:, line_numbers: nil, left_justify: false)
    @source = source
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset = # ...

    View Slide

  242. @sandimetz May 2018
    class Listing
    attr_reader :source, :line_numbers, :left_just
    def initialize(source:, line_numbers: nil, left_justify: false)
    @source = source
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset = # ...

    View Slide

  243. @sandimetz May 2018
    class Listing
    attr_reader :source, :line_numbers, :left_just
    def initialize(source:, line_numbers: nil, left_justify: false)
    @source = source
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset = # ...
    Execution
    Paths: 16?

    View Slide

  244. @sandimetz May 2018
    class Listing
    attr_reader :source, :line_numbers, :left_just
    def initialize(source:, line_numbers: nil, left_justify: false)
    @source = source
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset = # ...
    Execution
    Paths: 8

    View Slide

  245. @sandimetz May 2018
    Push conditionals
    back on the stack

    View Slide

  246. @sandimetz May 2018
    class Listing
    attr_reader :source, :line_numbers, :left_just
    def initialize(source:, line_numbers: nil, left_justify: false)
    @source = source
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset =

    View Slide

  247. @sandimetz May 2018
    class Listing
    attr_reader :source, :line_numbers, :left_just
    def initialize(source:, line_numbers: nil, left_justify: false)
    @source = source
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end

    View Slide

  248. @sandimetz May 2018
    class Listing
    attr_reader :source, :line_numbers, :left_just
    def initialize(source:, line_numbers: nil, left_justify: false)
    @source = source
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end

    View Slide

  249. @sandimetz May 2018
    class Listing
    attr_reader :source, :line_numbers, :left_just
    def initialize(source:, line_numbers: nil, left_justify: false)
    @source = source
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    2
    All or Some Lines

    View Slide

  250. @sandimetz May 2018
    class Listing
    attr_reader :source, :line_numbers, :left_just
    def initialize(source:, line_numbers: nil, left_justify: false)
    @source = source
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    2
    Subset

    View Slide

  251. @sandimetz May 2018
    class Listing
    attr_reader :source, :line_numbers, :left_just
    def initialize(source:, line_numbers: nil, left_justify: false)
    @source = source
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    Inject a smarter thing

    View Slide

  252. @sandimetz May 2018
    Subset

    View Slide

  253. @sandimetz May 2018
    Subset #lines

    View Slide

  254. @sandimetz May 2018
    Subset #lines
    Everything
    Line
    Number

    View Slide

  255. @sandimetz May 2018
    module Subset
    class Everything
    end
    class LineNumber
    end
    end
    S
    U
    B
    S
    E
    T

    View Slide

  256. @sandimetz May 2018
    module Subset
    class Everything
    end
    class LineNumber
    end
    S
    U
    B
    S
    E
    T

    View Slide

  257. @sandimetz May 2018
    module Subset
    class Everything
    def lines(everything)
    end
    end
    class LineNumber
    end
    S
    U
    B
    S
    E
    T

    View Slide

  258. @sandimetz May 2018
    module Subset
    class Everything
    def lines(everything)
    everything
    end
    end
    class LineNumber
    end
    S
    U
    B
    S
    E
    T

    View Slide

  259. @sandimetz May 2018
    module Subset
    class Everything
    end
    class LineNumber
    end
    S
    U
    B
    S
    E
    T

    View Slide

  260. @sandimetz May 2018
    module Subset
    class Everything; end
    class LineNumber
    end
    S
    U
    B
    S
    E
    T

    View Slide

  261. @sandimetz May 2018
    module Subset
    class Everything; end
    class LineNumber
    # ...
    def lines_to_print(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..ed
    range_of_line_numbers.collect {|i| all_line
    end
    }.flatten.compact
    end
    end
    S
    U
    B
    S
    E
    T

    View Slide

  262. @sandimetz May 2018
    module Subset
    class Everything; end
    class LineNumber
    # ...
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..ed
    range_of_line_numbers.collect {|i| all_line
    end
    }.flatten.compact
    end
    end
    S
    U
    B
    S
    E
    T

    View Slide

  263. @sandimetz May 2018
    module Subset
    class Everything; end
    class LineNumber
    # ...
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..ed
    range_of_line_numbers.collect {|i| all_line
    end
    }.flatten.compact
    end
    end
    S
    U
    B
    S
    E
    T
    ?

    View Slide

  264. @sandimetz May 2018
    class Listing
    attr_reader :source, :line_numbers, :left_just
    def initialize(source:, line_numbers: nil, left_justify: false)
    @source = source
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete
    (" " * num_spaces) + "#
    else
    edges = spec.split('-').
    range_of_line_numbers =
    range_of_line_numbers.co
    end
    }.flatten.compact
    end

    View Slide

  265. @sandimetz May 2018
    class Listing
    attr_reader :source, :line_numbers, :left_just
    def initialize(source:, line_numbers: nil, left_justify: false)
    @source = source
    @line_numbers = line_numbers
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete
    (" " * num_spaces) + "#
    else
    edges = spec.split('-').
    range_of_line_numbers =
    range_of_line_numbers.co
    end
    }.flatten.compact
    end

    View Slide

  266. @sandimetz May 2018
    class Listing
    attr_reader :source, :left_just
    def initialize(source:, left_justify: false)
    @source = source
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end

    View Slide

  267. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :left_just
    def initialize(source:, subsetter:, left_justify: false)
    @source = source
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end

    View Slide

  268. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :left_just
    def initialize(source:, subsetter:, left_justify: false)
    @source = source
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end

    View Slide

  269. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :left_just
    def initialize(source:, subsetter:, left_justify: false)
    @source = source
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end

    View Slide

  270. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :left_just
    def initialize(source:, subsetter:, left_justify: false)
    @source = source
    @subsetter = subsetter
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end

    View Slide

  271. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :left_just
    def initialize(source:, subsetter:, left_justify: false)
    @source = source
    @subsetter = subsetter
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end

    View Slide

  272. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :left_just
    def initialize(source:, subsetter:, left_justify: false)
    @source = source
    @subsetter = subsetter
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end

    View Slide

  273. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :left_just
    def initialize(source:, subsetter:, left_justify: false)
    @source = source
    @subsetter = subsetter
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset = subsetter.lines(all_lines)
    if left_just
    return justify(subset)
    end
    subset
    end

    View Slide

  274. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :left_just
    def initialize(source:, subsetter:, left_justify: false)
    @source = source
    @subsetter = subsetter
    @left_just = left_justify
    end
    def lines
    all_lines = source.lines
    subset = subsetter.lines(all_lines)
    if left_just
    return justify(subset)
    end
    subset
    end

    View Slide

  275. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :left_just
    def initialize(source:, subsetter:, left_justify: false)
    @source = source
    @subsetter = subsetter
    @left_just = left_justify
    end
    def lines
    subset = subsetter.lines(source.lines)
    if left_just
    return justify(subset)
    end
    subset
    end

    View Slide

  276. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :left_just
    def initialize(source:, subsetter:, left_justify: false)
    @source = source
    @subsetter = subsetter
    @left_just = left_justify
    end
    def lines
    subset = subsetter.lines(source.lines)
    if left_just
    return justify(subset)
    end
    subset
    end
    Execution
    Paths: 4

    View Slide

  277. @sandimetz May 2018
    Dependency injection
    is your friend

    View Slide

  278. @sandimetz May 2018
    As long as
    you inject smart things

    View Slide

  279. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :left_just
    def initialize(source:, subsetter:, left_justify: false)
    @source = source
    @subsetter = subsetter
    @left_just = left_justify
    end
    def lines
    subset = subsetter.lines(source.lines)
    if left_just
    return justify(subset)
    end
    subset
    end

    View Slide

  280. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :left_just
    def initialize(source:, subsetter:, left_justify: false)
    @source = source
    @subsetter = subsetter
    @left_just = left_justify
    end
    def lines
    subset = subsetter.lines(source.lines)
    if left_just
    return justify(subset)
    end
    subset
    end

    View Slide

  281. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :left_just
    def initialize(source:, subsetter:, left_justify: false)
    @source = source
    @subsetter = subsetter
    @left_just = left_justify
    end
    def lines
    subset = subsetter.lines(source.lines)
    if left_just
    return justify(subset)
    end
    subset
    end
    4 Raw or Left Just

    View Slide

  282. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :left_just
    def initialize(source:, subsetter:, left_justify: false)
    @source = source
    @subsetter = subsetter
    @left_just = left_justify
    end
    def lines
    subset = subsetter.lines(source.lines)
    if left_just
    return justify(subset)
    end
    subset
    end
    4 Justifier

    View Slide

  283. @sandimetz May 2018
    Justifier

    View Slide

  284. @sandimetz May 2018
    Justifier #justify

    View Slide

  285. @sandimetz May 2018
    Justifier #justify
    None
    Block
    Left

    View Slide

  286. @sandimetz May 2018
    module Justification
    class None
    def justify(lines)
    lines
    end
    end
    class BlockLeft
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces
    end
    def num_leading_spaces_to_remove; end
    def num_leading_spaces(line); end
    end
    end
    J
    U
    S
    T
    I
    F
    I
    E
    R

    View Slide

  287. @sandimetz May 2018
    module Justification
    class None
    def justify(lines)
    lines
    end
    end
    class BlockLeft
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces
    end
    def num_leading_spaces_to_remove; end
    def num_leading_spaces(line); end
    end
    end
    J
    U
    S
    T
    I
    F
    I
    E
    R

    View Slide

  288. @sandimetz May 2018
    module Justification
    class None
    def justify(lines)
    lines
    end
    end
    class BlockLeft
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces
    end
    def num_leading_spaces_to_remove; end
    def num_leading_spaces(line); end
    end
    end
    J
    U
    S
    T
    I
    F
    I
    E
    R

    View Slide

  289. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :left_just
    def initialize(source:, subsetter:, left_justify: false)
    @source = source
    @subsetter = subsetter
    @left_just = left_justify
    end
    def lines
    subset = subsetter.lines(source.lines)
    if left_just
    return justify(subset)
    end
    subset
    end
    end

    View Slide

  290. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :left_just
    def initialize(source:, subsetter:, left_justify: false)
    @source = source
    @subsetter = subsetter
    @left_just = left_justify
    end
    def lines
    subset = subsetter.lines(source.lines)
    if left_just
    return justify(subset)
    end
    subset
    end
    end

    View Slide

  291. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    subset = subsetter.lines(source.lines)
    if left_just
    return justify(subset)
    end
    subset
    end
    end

    View Slide

  292. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    subset = subsetter.lines(source.lines)
    if left_just
    return justify(subset)
    end
    subset
    end
    end

    View Slide

  293. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    subset = subsetter.lines(source.lines)
    justifier.justify(subset)
    end
    end

    View Slide

  294. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    subset = subsetter.lines(source.lines)
    justifier.justify(subset)
    end
    end

    View Slide

  295. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    subset = subsetter.lines(source.lines)
    justifier.justify(subsetter.lines(source.lines))
    end
    end

    View Slide

  296. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    justifier.justify(subsetter.lines(source.lines))
    end
    end

    View Slide

  297. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    justifier.justify(subsetter.lines(source.lines))
    end
    end

    View Slide

  298. @sandimetz May 2018
    But, conditionals?

    View Slide

  299. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    justifier.justify(subsetter.lines(source.lines))
    end
    end
    3
    Code Line or
    Comment

    View Slide

  300. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    justifier.justify(subsetter.lines(source.lines))
    end
    end
    module Subset
    class LineNumber
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(
    range_of_line_numbers = (edges.m
    range_of_line_numbers.collect {|
    end
    }.flatten.compact
    end
    3
    Code Line or
    Comment

    View Slide

  301. @sandimetz May 2018
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    justifier.justify(subsetter.lines(source.lines))
    end
    end
    module Subset
    class LineNumber
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(
    range_of_line_numbers = (edges.m
    range_of_line_numbers.collect {|
    end
    }.flatten.compact
    end
    3
    Clump

    View Slide

  302. @sandimetz May 2018
    Clump

    View Slide

  303. @sandimetz May 2018
    Clump #lines

    View Slide

  304. @sandimetz May 2018
    Clump #lines
    Comment
    Line
    Number

    View Slide

  305. @sandimetz May 2018
    class Clump
    # initialize with spec and input
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.c
    end
    def expand_clump(spec)
    # ...
    end
    end
    end
    C
    L
    U
    M
    P

    View Slide

  306. @sandimetz May 2018
    class Clump
    # initialize with spec and input
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.c
    end
    def expand_clump(spec)
    # ...
    end
    end
    end
    C
    L
    U
    M
    P

    View Slide

  307. @sandimetz May 2018
    class Clump
    # initialize with spec and input
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.c
    end
    def expand_clump(spec)
    # ...
    end
    end
    end
    C
    L
    U
    M
    P

    View Slide

  308. @sandimetz May 2018
    class Clump
    # initialize with spec and input
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.c
    end
    def expand_clump(spec)
    # ...
    end
    end
    end
    C
    L
    U
    M
    P

    View Slide

  309. @sandimetz May 2018
    class Clump
    # initialize with spec and input
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.c
    end
    def expand_clump(spec)
    # ...
    end
    end
    end
    C
    L
    U
    M
    P

    View Slide

  310. @sandimetz May 2018
    class Clump
    # initialize with spec and input
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.c
    end
    def expand_clump(spec)
    # ...
    end
    end
    end
    C
    L
    U
    M
    P

    View Slide

  311. @sandimetz May 2018
    class Clump
    # initialize with spec and input
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.c
    end
    def expand_clump(spec)
    # ...
    end
    end
    end
    C
    L
    U
    M
    P

    View Slide

  312. @sandimetz May 2018
    class Clump
    # initialize with spec and input
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.c
    end
    def expand_clump(spec)
    # ...
    end
    end
    end
    C
    L
    U
    M
    P

    View Slide

  313. @sandimetz May 2018
    module Subset
    class LineNumber
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    S
    U
    B
    S
    E
    T

    View Slide

  314. @sandimetz May 2018
    module Subset
    class LineNumber
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    S
    U
    B
    S
    E
    T

    View Slide

  315. @sandimetz May 2018
    module Subset
    class LineNumber
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    else
    end
    }.flatten.compact
    end
    S
    U
    B
    S
    E
    T

    View Slide

  316. @sandimetz May 2018
    module Subset
    class LineNumber
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    else
    end
    }.flatten.compact
    end
    S
    U
    B
    S
    E
    T

    View Slide

  317. @sandimetz May 2018
    module Subset
    class LineNumber
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    Clump::Comment.new(...).lines
    else
    end
    }.flatten.compact
    end
    S
    U
    B
    S
    E
    T

    View Slide

  318. @sandimetz May 2018
    module Subset
    class LineNumber
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    Clump::Comment.new(...).lines
    else
    Clump::LineNumber.new(...).lines
    end
    }.flatten.compact
    end
    S
    U
    B
    S
    E
    T

    View Slide

  319. @sandimetz May 2018
    module Subset
    class LineNumber
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    Clump::Comment.new(...).lines
    else
    Clump::LineNumber.new(...).lines
    end
    }.flatten.compact
    end
    Polymorphic
    S
    U
    B
    S
    E
    T

    View Slide

  320. @sandimetz May 2018
    module Subset
    class LineNumber
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    Clump::Comment.new(...).lines
    else
    Clump::LineNumber.new(...).lines
    end
    }.flatten.compact
    end
    Role-playing
    S
    U
    B
    S
    E
    T

    View Slide

  321. @sandimetz May 2018
    module Subset
    class LineNumber
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    Clump::Comment.new(...).lines
    else
    Clump::LineNumber.new(...).lines
    end
    }.flatten.compact
    end
    Rule ??
    S
    U
    B
    S
    E
    T

    View Slide

  322. @sandimetz May 2018
    module Subset
    class LineNumber
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    Clump::Comment.new(...).lines
    else
    Clump::LineNumber.new(...).lines
    end
    }.flatten.compact
    end
    Tightly-coupled
    S
    U
    B
    S
    E
    T

    View Slide

  323. @sandimetz May 2018
    module Subset
    class LineNumber
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    Clump::Comment.new(...).lines
    else
    Clump::LineNumber.new(...).lines
    end
    }.flatten.compact
    end
    S
    U
    B
    S
    E
    T

    View Slide

  324. @sandimetz May 2018
    module Subset
    class LineNumber
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    Clump::Comment.new(...).lines
    else
    Clump::LineNumber.new(...).lines
    end
    }.flatten.compact
    end
    Factory!
    S
    U
    B
    S
    E
    T

    View Slide

  325. @sandimetz May 2018
    class Clump
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    # ...
    end
    C
    L
    U
    M
    P

    View Slide

  326. @sandimetz May 2018
    class Clump
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    # ...
    end
    C
    L
    U
    M
    P

    View Slide

  327. @sandimetz May 2018
    class Clump
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    # ...
    end
    C
    L
    U
    M
    P

    View Slide

  328. @sandimetz May 2018
    class Clump
    def self.lines(spec:, possibilities: [])
    self.for(spec: spec, possibilities: possibilities).lines
    end
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    # ...
    end
    C
    L
    U
    M
    P

    View Slide

  329. @sandimetz May 2018
    class Clump
    def self.lines(spec:, possibilities: [])
    self.for(spec: spec, possibilities: possibilities).lines
    end
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    # ...
    end
    C
    L
    U
    M
    P
    Factory-created

    View Slide

  330. @sandimetz May 2018
    module Subset
    class LineNumber
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    Clump::Comment.new(...).lines
    else
    Clump::LineNumber.new(...).lines
    end
    }.flatten.compact
    end
    S
    U
    B
    S
    E
    T

    View Slide

  331. @sandimetz May 2018
    module Subset
    class LineNumber
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    Clump::Comment.new(...).lines
    else
    Clump::LineNumber.new(...).lines
    end
    }.flatten.compact
    end
    S
    U
    B
    S
    E
    T

    View Slide

  332. @sandimetz May 2018
    module Subset
    class LineNumber
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    }.flatten.compact
    end
    S
    U
    B
    S
    E
    T

    View Slide

  333. @sandimetz May 2018
    module Subset
    class LineNumber
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    Clump.lines(spec: spec, possibilities: possibilities)
    }.flatten.compact
    end
    S
    U
    B
    S
    E
    T

    View Slide

  334. @sandimetz May 2018
    module Subset
    class LineNumber
    def lines(possibilities)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    Clump.lines(spec: spec, possibilities: possibilities)
    }.flatten.compact
    end
    S
    U
    B
    S
    E
    T

    View Slide

  335. @sandimetz May 2018
    Reprise

    View Slide

  336. @sandimetz May 2018
    class Listing
    attr_reader :filename, :repository, :tag, :git_cmd
    def initialize(filename:, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    if git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    else
    File.read(filename).split("\n")
    end
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    class Listing
    attr_reader :filename, :line_numbers, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end
    private
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    edges = spec.split('-').collect(&:to_i)
    individual_numbers = (edges.min.to_i..edges.max.to_i).to_a
    individual_numbers.collect {|i| all_lines[i - 1]}.compact
    }.flatten.compact
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    1 2
    class Listing
    attr_reader :filename, :line_numbers, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, repository: nil, tag: nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    if line_numbers
    return lines_to_print(all_lines)
    end
    all_lines
    end
    private
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    3
    class Listing
    attr_reader :filename, :line_numbers, :left_just, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, left_justify: false, repository: nil, tag:
    nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @left_just = left_justify
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    private
    ####################
    # Reading
    ####################
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    ####################
    # Subsetting
    ####################
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    ####################
    # Justification
    ####################
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces_to_remove(lines)..-1) || "" }
    end
    def num_leading_spaces_to_remove(lines)
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    # plus 90+ lines of error handling
    4

    View Slide

  337. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :left_just, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, left_justify: false, repository: nil, tag:
    nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @left_just = left_justify
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    private
    ####################
    # Reading
    ####################
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    ####################
    # Subsetting
    ####################
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    ####################
    # Justification
    ####################
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces_to_remove(lines)..-1) || "" }
    end
    def num_leading_spaces_to_remove(lines)
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    # plus 90+ lines of error handling
    Lines: 83
    Execution
    Paths: 16
    Procedure

    View Slide

  338. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :left_just, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, left_justify: false, repository: nil, tag:
    nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @left_just = left_justify
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    private
    ####################
    # Reading
    ####################
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    ####################
    # Subsetting
    ####################
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    ####################
    # Justification
    ####################
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces_to_remove(lines)..-1) || "" }
    end
    def num_leading_spaces_to_remove(lines)
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    # plus 90+ lines of error handling
    Lines: 83
    Execution
    Paths: 16
    Procedure OO

    View Slide

  339. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :left_just, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, left_justify: false, repository: nil, tag:
    nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @left_just = left_justify
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    private
    ####################
    # Reading
    ####################
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    ####################
    # Subsetting
    ####################
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    ####################
    # Justification
    ####################
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces_to_remove(lines)..-1) || "" }
    end
    def num_leading_spaces_to_remove(lines)
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    # plus 90+ lines of error handling
    Lines: 83
    Execution
    Paths: 16
    Procedure
    module Source
    class File
    attr_reader :filename
    def initialize(filename:)
    @filename = filename
    end
    def lines
    ::File.read(filename).split("\n")
    end
    end
    class GitTag
    def self.git_cmd
    GitCmd.new
    end
    attr_reader :filename, :tag, :repository, :git_cmd
    def initialize(filename:, repository:, tag:, git_cmd: self.class.git_cmd)
    @git_cmd = git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    end
    def lines
    git_cmd.show.split("\n")
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    end
    end
    Source:
    File
    GitTag
    OO

    View Slide

  340. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :left_just, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, left_justify: false, repository: nil, tag:
    nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @left_just = left_justify
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    private
    ####################
    # Reading
    ####################
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    ####################
    # Subsetting
    ####################
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    ####################
    # Justification
    ####################
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces_to_remove(lines)..-1) || "" }
    end
    def num_leading_spaces_to_remove(lines)
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    # plus 90+ lines of error handling
    Lines: 83
    Execution
    Paths: 16
    Procedure
    module Source
    class File
    attr_reader :filename
    def initialize(filename:)
    @filename = filename
    end
    def lines
    ::File.read(filename).split("\n")
    end
    end
    class GitTag
    def self.git_cmd
    GitCmd.new
    end
    attr_reader :filename, :tag, :repository, :git_cmd
    def initialize(filename:, repository:, tag:, git_cmd: self.class.git_cmd)
    @git_cmd = git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    end
    def lines
    git_cmd.show.split("\n")
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    end
    end
    OO
    module Subset
    class Everything
    def lines(everything)
    everything
    end
    end
    class LineNumber
    attr_reader :line_numbers
    def initialize(line_numbers:)
    @line_numbers = line_numbers
    end
    def lines(possibilities)
    clump_specs.collect {|spec| clump_for(spec, possibilities) }.flatten.compact
    end
    def clump_specs
    line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    end
    def clump_for(spec, possibilities)
    Clump.lines(spec: spec, possibilities: possibilities)
    end
    end
    end
    Subset:
    Everything
    LineNumber
    Source:

    View Slide

  341. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :left_just, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, left_justify: false, repository: nil, tag:
    nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @left_just = left_justify
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    private
    ####################
    # Reading
    ####################
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    ####################
    # Subsetting
    ####################
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    ####################
    # Justification
    ####################
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces_to_remove(lines)..-1) || "" }
    end
    def num_leading_spaces_to_remove(lines)
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    # plus 90+ lines of error handling
    Lines: 83
    Execution
    Paths: 16
    Procedure
    module Source
    class File
    attr_reader :filename
    def initialize(filename:)
    @filename = filename
    end
    def lines
    ::File.read(filename).split("\n")
    end
    end
    class GitTag
    def self.git_cmd
    GitCmd.new
    end
    attr_reader :filename, :tag, :repository, :git_cmd
    def initialize(filename:, repository:, tag:, git_cmd: self.class.git_cmd)
    @git_cmd = git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    end
    def lines
    git_cmd.show.split("\n")
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    end
    end
    OO
    module Subset
    class Everything
    def lines(everything)
    everything
    end
    end
    class LineNumber
    attr_reader :line_numbers
    def initialize(line_numbers:)
    @line_numbers = line_numbers
    end
    def lines(possibilities)
    clump_specs.collect {|spec| clump_for(spec, possibilities) }.flatten.compact
    end
    def clump_specs
    line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    end
    def clump_for(spec, possibilities)
    Clump.lines(spec: spec, possibilities: possibilities)
    end
    end
    end
    Subset:
    Source:
    class Clump
    def self.lines(spec:, possibilities: [])
    self.for(spec: spec, possibilities: possibilities).lines
    end
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    attr_reader :spec, :input
    def initialize(spec:, input: [])
    @spec = spec
    @input = input
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.compact
    end
    def expand_clump(spec)
    edges = spec.split('-').collect(&:to_i)
    (edges.min.to_i..edges.max.to_i).to_a
    end
    end
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    end
    Clump:
    Comment
    LineNumber

    View Slide

  342. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :left_just, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, left_justify: false, repository: nil, tag:
    nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @left_just = left_justify
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    private
    ####################
    # Reading
    ####################
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    ####################
    # Subsetting
    ####################
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    ####################
    # Justification
    ####################
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces_to_remove(lines)..-1) || "" }
    end
    def num_leading_spaces_to_remove(lines)
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    # plus 90+ lines of error handling
    Lines: 83
    Execution
    Paths: 16
    Procedure
    module Source
    class File
    attr_reader :filename
    def initialize(filename:)
    @filename = filename
    end
    def lines
    ::File.read(filename).split("\n")
    end
    end
    class GitTag
    def self.git_cmd
    GitCmd.new
    end
    attr_reader :filename, :tag, :repository, :git_cmd
    def initialize(filename:, repository:, tag:, git_cmd: self.class.git_cmd)
    @git_cmd = git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    end
    def lines
    git_cmd.show.split("\n")
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    end
    end
    OO
    module Subset
    class Everything
    def lines(everything)
    everything
    end
    end
    class LineNumber
    attr_reader :line_numbers
    def initialize(line_numbers:)
    @line_numbers = line_numbers
    end
    def lines(possibilities)
    clump_specs.collect {|spec| clump_for(spec, possibilities) }.flatten.compact
    end
    def clump_specs
    line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    end
    def clump_for(spec, possibilities)
    Clump.lines(spec: spec, possibilities: possibilities)
    end
    end
    end
    Subset:
    Source:
    class Clump
    def self.lines(spec:, possibilities: [])
    self.for(spec: spec, possibilities: possibilities).lines
    end
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    attr_reader :spec, :input
    def initialize(spec:, input: [])
    @spec = spec
    @input = input
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.compact
    end
    def expand_spec(spec)
    edges = spec.split('-').collect(&:to_i)
    (edges.min.to_i..edges.max.to_i).to_a
    end
    end
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    end
    Clump:
    module Justification
    class None
    def self.justify(lines)
    lines
    end
    end
    class BlockLeft
    def self.justify(lines)
    new(lines).justify
    end
    attr_reader :lines
    def initialize(lines)
    @lines = lines
    end
    def justify
    lines.map {|line| line.slice(num_leading_spaces_to_remove..-1) || "" }
    end
    private
    def num_leading_spaces_to_remove
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    end
    Justification:
    None
    BlockLeft

    View Slide

  343. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :left_just, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, left_justify: false, repository: nil, tag:
    nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @left_just = left_justify
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    private
    ####################
    # Reading
    ####################
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    ####################
    # Subsetting
    ####################
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    ####################
    # Justification
    ####################
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces_to_remove(lines)..-1) || "" }
    end
    def num_leading_spaces_to_remove(lines)
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    # plus 90+ lines of error handling
    Lines: 83
    Execution
    Paths: 16
    Procedure
    module Source
    class File
    attr_reader :filename
    def initialize(filename:)
    @filename = filename
    end
    def lines
    ::File.read(filename).split("\n")
    end
    end
    class GitTag
    def self.git_cmd
    GitCmd.new
    end
    attr_reader :filename, :tag, :repository, :git_cmd
    def initialize(filename:, repository:, tag:, git_cmd: self.class.git_cmd)
    @git_cmd = git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    end
    def lines
    git_cmd.show.split("\n")
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    end
    end
    OO
    module Subset
    class Everything
    def lines(everything)
    everything
    end
    end
    class LineNumber
    attr_reader :line_numbers
    def initialize(line_numbers:)
    @line_numbers = line_numbers
    end
    def lines(possibilities)
    clump_specs.collect {|spec| clump_for(spec, possibilities) }.flatten.compact
    end
    def clump_specs
    line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    end
    def clump_for(spec, possibilities)
    Clump.lines(spec: spec, possibilities: possibilities)
    end
    end
    end
    Subset:
    Source:
    class Clump
    def self.lines(spec:, possibilities: [])
    self.for(spec: spec, possibilities: possibilities).lines
    end
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    attr_reader :spec, :input
    def initialize(spec:, input: [])
    @spec = spec
    @input = input
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.compact
    end
    def expand_clump(spec)
    edges = spec.split('-').collect(&:to_i)
    (edges.min.to_i..edges.max.to_i).to_a
    end
    end
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    end
    Clump:
    module Justification
    class None
    def self.justify(lines)
    lines
    end
    end
    class BlockLeft
    def self.justify(lines)
    new(lines).justify
    end
    attr_reader :lines
    def initialize(lines)
    @lines = lines
    end
    def justify
    lines.map {|line| line.slice(num_leading_spaces_to_remove..-1) || "" }
    end
    private
    def num_leading_spaces_to_remove
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    end
    Justification:
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    justifier.justify(subsetter.lines(source.lines))
    end
    end

    View Slide

  344. @sandimetz May 2018
    class Listing
    attr_reader :filename, :line_numbers, :left_just, :repository, :tag, :git_cmd
    def initialize(filename:, line_numbers: nil, left_justify: false, repository: nil, tag:
    nil, git_cmd: nil)
    @filename = filename
    @line_numbers = line_numbers
    @left_just = left_justify
    @repository = repository
    @tag = tag
    @git_cmd = git_cmd
    end
    def lines
    all_lines =
    if git_cmd
    git_lines
    else
    file_lines
    end
    subset =
    if line_numbers
    lines_to_print(all_lines)
    else
    all_lines
    end
    if left_just
    return justify(subset)
    end
    subset
    end
    private
    ####################
    # Reading
    ####################
    def git_lines
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    git_cmd.show.split("\n")
    end
    def file_lines
    File.read(filename).split("\n")
    end
    ####################
    # Subsetting
    ####################
    def lines_to_print(all_lines)
    specs = line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    specs.collect {|spec|
    if spec.include?('#')
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    else
    edges = spec.split('-').collect(&:to_i)
    range_of_line_numbers = (edges.min.to_i..edges.max.to_i).to_a
    range_of_line_numbers.collect {|i| all_lines[i - 1]}.compact
    end
    }.flatten.compact
    end
    ####################
    # Justification
    ####################
    def justify(lines)
    lines.map {|line| line.slice(num_leading_spaces_to_remove(lines)..-1) || "" }
    end
    def num_leading_spaces_to_remove(lines)
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    private
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    # plus 90+ lines of error handling
    Lines: 83
    Execution
    Paths: 16
    Procedure
    module Source
    class File
    attr_reader :filename
    def initialize(filename:)
    @filename = filename
    end
    def lines
    ::File.read(filename).split("\n")
    end
    end
    class GitTag
    def self.git_cmd
    GitCmd.new
    end
    attr_reader :filename, :tag, :repository, :git_cmd
    def initialize(filename:, repository:, tag:, git_cmd: self.class.git_cmd)
    @git_cmd = git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    end
    def lines
    git_cmd.show.split("\n")
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    end
    end
    OO
    module Subset
    class Everything
    def lines(everything)
    everything
    end
    end
    class LineNumber
    attr_reader :line_numbers
    def initialize(line_numbers:)
    @line_numbers = line_numbers
    end
    def lines(possibilities)
    clump_specs.collect {|spec| clump_for(spec, possibilities) }.flatten.compact
    end
    def clump_specs
    line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    end
    def clump_for(spec, possibilities)
    Clump.lines(spec: spec, possibilities: possibilities)
    end
    end
    end
    Subset:
    Source:
    class Clump
    def self.lines(spec:, possibilities: [])
    self.for(spec: spec, possibilities: possibilities).lines
    end
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    attr_reader :spec, :input
    def initialize(spec:, input: [])
    @spec = spec
    @input = input
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.compact
    end
    def expand_clump(spec)
    edges = spec.split('-').collect(&:to_i)
    (edges.min.to_i..edges.max.to_i).to_a
    end
    end
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    end
    Clump:
    module Justification
    class None
    def self.justify(lines)
    lines
    end
    end
    class BlockLeft
    def self.justify(lines)
    new(lines).justify
    end
    attr_reader :lines
    def initialize(lines)
    @lines = lines
    end
    def justify
    lines.map {|line| line.slice(num_leading_spaces_to_remove..-1) || "" }
    end
    private
    def num_leading_spaces_to_remove
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    end
    Justification:
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    justifier.justify(subsetter.lines(source.lines))
    end
    end
    Factories

    View Slide

  345. @sandimetz May 2018
    Lines: 83
    Execution
    Paths: 16
    Procedure
    module Source
    class File
    attr_reader :filename
    def initialize(filename:)
    @filename = filename
    end
    def lines
    ::File.read(filename).split("\n")
    end
    end
    class GitTag
    def self.git_cmd
    GitCmd.new
    end
    attr_reader :filename, :tag, :repository, :git_cmd
    def initialize(filename:, repository:, tag:, git_cmd: self.class.git_cmd)
    @git_cmd = git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    end
    def lines
    git_cmd.show.split("\n")
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    end
    end
    OO
    module Subset
    class Everything
    def lines(everything)
    everything
    end
    end
    class LineNumber
    attr_reader :line_numbers
    def initialize(line_numbers:)
    @line_numbers = line_numbers
    end
    def lines(possibilities)
    clump_specs.collect {|spec| clump_for(spec, possibilities) }.flatten.compact
    end
    def clump_specs
    line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    end
    def clump_for(spec, possibilities)
    Clump.lines(spec: spec, possibilities: possibilities)
    end
    end
    end
    Subset:
    Source:
    class Clump
    def self.lines(spec:, possibilities: [])
    self.for(spec: spec, possibilities: possibilities).lines
    end
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    attr_reader :spec, :input
    def initialize(spec:, input: [])
    @spec = spec
    @input = input
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.compact
    end
    def expand_clump(spec)
    edges = spec.split('-').collect(&:to_i)
    (edges.min.to_i..edges.max.to_i).to_a
    end
    end
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    end
    Clump:
    module Justification
    class None
    def self.justify(lines)
    lines
    end
    end
    class BlockLeft
    def self.justify(lines)
    new(lines).justify
    end
    attr_reader :lines
    def initialize(lines)
    @lines = lines
    end
    def justify
    lines.map {|line| line.slice(num_leading_spaces_to_remove..-1) || "" }
    end
    private
    def num_leading_spaces_to_remove
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    end
    Justification:
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    justifier.justify(subsetter.lines(source.lines))
    end
    end
    Factories

    View Slide

  346. @sandimetz May 2018
    Lines: 83
    Execution
    Paths: 16
    module Source
    class File
    attr_reader :filename
    def initialize(filename:)
    @filename = filename
    end
    def lines
    ::File.read(filename).split("\n")
    end
    end
    class GitTag
    def self.git_cmd
    GitCmd.new
    end
    attr_reader :filename, :tag, :repository, :git_cmd
    def initialize(filename:, repository:, tag:, git_cmd: self.class.git_cmd)
    @git_cmd = git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    end
    def lines
    git_cmd.show.split("\n")
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    end
    end
    OO
    module Subset
    class Everything
    def lines(everything)
    everything
    end
    end
    class LineNumber
    attr_reader :line_numbers
    def initialize(line_numbers:)
    @line_numbers = line_numbers
    end
    def lines(possibilities)
    clump_specs.collect {|spec| clump_for(spec, possibilities) }.flatten.compact
    end
    def clump_specs
    line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    end
    def clump_for(spec, possibilities)
    Clump.lines(spec: spec, possibilities: possibilities)
    end
    end
    end
    Subset:
    Source:
    class Clump
    def self.lines(spec:, possibilities: [])
    self.for(spec: spec, possibilities: possibilities).lines
    end
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    attr_reader :spec, :input
    def initialize(spec:, input: [])
    @spec = spec
    @input = input
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.compact
    end
    def expand_clump(spec)
    edges = spec.split('-').collect(&:to_i)
    (edges.min.to_i..edges.max.to_i).to_a
    end
    end
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    end
    Clump:
    module Justification
    class None
    def self.justify(lines)
    lines
    end
    end
    class BlockLeft
    def self.justify(lines)
    new(lines).justify
    end
    attr_reader :lines
    def initialize(lines)
    @lines = lines
    end
    def justify
    lines.map {|line| line.slice(num_leading_spaces_to_remove..-1) || "" }
    end
    private
    def num_leading_spaces_to_remove
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    end
    Justification:
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    justifier.justify(subsetter.lines(source.lines))
    end
    end
    Factories
    Procedure

    View Slide

  347. @sandimetz May 2018
    Lines: 83
    Execution
    Paths: 16
    module Source
    class File
    attr_reader :filename
    def initialize(filename:)
    @filename = filename
    end
    def lines
    ::File.read(filename).split("\n")
    end
    end
    class GitTag
    def self.git_cmd
    GitCmd.new
    end
    attr_reader :filename, :tag, :repository, :git_cmd
    def initialize(filename:, repository:, tag:, git_cmd: self.class.git_cmd)
    @git_cmd = git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    end
    def lines
    git_cmd.show.split("\n")
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    end
    end
    OO
    module Subset
    class Everything
    def lines(everything)
    everything
    end
    end
    class LineNumber
    attr_reader :line_numbers
    def initialize(line_numbers:)
    @line_numbers = line_numbers
    end
    def lines(possibilities)
    clump_specs.collect {|spec| clump_for(spec, possibilities) }.flatten.compact
    end
    def clump_specs
    line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    end
    def clump_for(spec, possibilities)
    Clump.lines(spec: spec, possibilities: possibilities)
    end
    end
    end
    Subset:
    Source:
    class Clump
    def self.lines(spec:, possibilities: [])
    self.for(spec: spec, possibilities: possibilities).lines
    end
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    attr_reader :spec, :input
    def initialize(spec:, input: [])
    @spec = spec
    @input = input
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.compact
    end
    def expand_clump(spec)
    edges = spec.split('-').collect(&:to_i)
    (edges.min.to_i..edges.max.to_i).to_a
    end
    end
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    end
    Clump:
    module Justification
    class None
    def self.justify(lines)
    lines
    end
    end
    class BlockLeft
    def self.justify(lines)
    new(lines).justify
    end
    attr_reader :lines
    def initialize(lines)
    @lines = lines
    end
    def justify
    lines.map {|line| line.slice(num_leading_spaces_to_remove..-1) || "" }
    end
    private
    def num_leading_spaces_to_remove
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    end
    Justification:
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    justifier.justify(subsetter.lines(source.lines))
    end
    end
    Factories
    Procedure
    Total Lines: 134

    View Slide

  348. @sandimetz May 2018
    Lines: 83
    Execution
    Paths: 16
    module Source
    class File
    attr_reader :filename
    def initialize(filename:)
    @filename = filename
    end
    def lines
    ::File.read(filename).split("\n")
    end
    end
    class GitTag
    def self.git_cmd
    GitCmd.new
    end
    attr_reader :filename, :tag, :repository, :git_cmd
    def initialize(filename:, repository:, tag:, git_cmd: self.class.git_cmd)
    @git_cmd = git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    end
    def lines
    git_cmd.show.split("\n")
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    end
    end
    OO
    module Subset
    class Everything
    def lines(everything)
    everything
    end
    end
    class LineNumber
    attr_reader :line_numbers
    def initialize(line_numbers:)
    @line_numbers = line_numbers
    end
    def lines(possibilities)
    clump_specs.collect {|spec| clump_for(spec, possibilities) }.flatten.compact
    end
    def clump_specs
    line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    end
    def clump_for(spec, possibilities)
    Clump.lines(spec: spec, possibilities: possibilities)
    end
    end
    end
    Subset:
    Source:
    class Clump
    def self.lines(spec:, possibilities: [])
    self.for(spec: spec, possibilities: possibilities).lines
    end
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    attr_reader :spec, :input
    def initialize(spec:, input: [])
    @spec = spec
    @input = input
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.compact
    end
    def expand_clump(spec)
    edges = spec.split('-').collect(&:to_i)
    (edges.min.to_i..edges.max.to_i).to_a
    end
    end
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    end
    Clump:
    module Justification
    class None
    def self.justify(lines)
    lines
    end
    end
    class BlockLeft
    def self.justify(lines)
    new(lines).justify
    end
    attr_reader :lines
    def initialize(lines)
    @lines = lines
    end
    def justify
    lines.map {|line| line.slice(num_leading_spaces_to_remove..-1) || "" }
    end
    private
    def num_leading_spaces_to_remove
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    end
    Justification:
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    justifier.justify(subsetter.lines(source.lines))
    end
    end
    Factories
    Procedure
    Total Lines: 134
    Execution Paths: 1

    View Slide

  349. @sandimetz May 2018
    Lines: 83
    Execution
    Paths: 16
    module Source
    class File
    attr_reader :filename
    def initialize(filename:)
    @filename = filename
    end
    def lines
    ::File.read(filename).split("\n")
    end
    end
    class GitTag
    def self.git_cmd
    GitCmd.new
    end
    attr_reader :filename, :tag, :repository, :git_cmd
    def initialize(filename:, repository:, tag:, git_cmd: self.class.git_cmd)
    @git_cmd = git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    end
    def lines
    git_cmd.show.split("\n")
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    end
    end
    OO
    module Subset
    class Everything
    def lines(everything)
    everything
    end
    end
    class LineNumber
    attr_reader :line_numbers
    def initialize(line_numbers:)
    @line_numbers = line_numbers
    end
    def lines(possibilities)
    clump_specs.collect {|spec| clump_for(spec, possibilities) }.flatten.compact
    end
    def clump_specs
    line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    end
    def clump_for(spec, possibilities)
    Clump.lines(spec: spec, possibilities: possibilities)
    end
    end
    end
    Subset:
    Source:
    class Clump
    def self.lines(spec:, possibilities: [])
    self.for(spec: spec, possibilities: possibilities).lines
    end
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    attr_reader :spec, :input
    def initialize(spec:, input: [])
    @spec = spec
    @input = input
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.compact
    end
    def expand_clump(spec)
    edges = spec.split('-').collect(&:to_i)
    (edges.min.to_i..edges.max.to_i).to_a
    end
    end
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    end
    Clump:
    module Justification
    class None
    def self.justify(lines)
    lines
    end
    end
    class BlockLeft
    def self.justify(lines)
    new(lines).justify
    end
    attr_reader :lines
    def initialize(lines)
    @lines = lines
    end
    def justify
    lines.map {|line| line.slice(num_leading_spaces_to_remove..-1) || "" }
    end
    private
    def num_leading_spaces_to_remove
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    end
    Justification:
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    justifier.justify(subsetter.lines(source.lines))
    end
    end
    Factories
    Procedure
    Total Lines: 134
    Execution Paths: 1

    View Slide

  350. @sandimetz May 2018
    Lines: 83
    Execution
    Paths: 16
    module Source
    class File
    attr_reader :filename
    def initialize(filename:)
    @filename = filename
    end
    def lines
    ::File.read(filename).split("\n")
    end
    end
    class GitTag
    def self.git_cmd
    GitCmd.new
    end
    attr_reader :filename, :tag, :repository, :git_cmd
    def initialize(filename:, repository:, tag:, git_cmd: self.class.git_cmd)
    @git_cmd = git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    end
    def lines
    git_cmd.show.split("\n")
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    end
    end
    OO
    module Subset
    class Everything
    def lines(everything)
    everything
    end
    end
    class LineNumber
    attr_reader :line_numbers
    def initialize(line_numbers:)
    @line_numbers = line_numbers
    end
    def lines(possibilities)
    clump_specs.collect {|spec| clump_for(spec, possibilities) }.flatten.compact
    end
    def clump_specs
    line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    end
    def clump_for(spec, possibilities)
    Clump.lines(spec: spec, possibilities: possibilities)
    end
    end
    end
    Subset:
    Source:
    class Clump
    def self.lines(spec:, possibilities: [])
    self.for(spec: spec, possibilities: possibilities).lines
    end
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    attr_reader :spec, :input
    def initialize(spec:, input: [])
    @spec = spec
    @input = input
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.compact
    end
    def expand_clump(spec)
    edges = spec.split('-').collect(&:to_i)
    (edges.min.to_i..edges.max.to_i).to_a
    end
    end
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    end
    Clump:
    module Justification
    class None
    def self.justify(lines)
    lines
    end
    end
    class BlockLeft
    def self.justify(lines)
    new(lines).justify
    end
    attr_reader :lines
    def initialize(lines)
    @lines = lines
    end
    def justify
    lines.map {|line| line.slice(num_leading_spaces_to_remove..-1) || "" }
    end
    private
    def num_leading_spaces_to_remove
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    end
    Justification:
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    justifier.justify(subsetter.lines(source.lines))
    end
    end
    Factories
    Procedure
    Classes: 9
    Total Lines: 134
    Execution Paths: 1

    View Slide

  351. @sandimetz May 2018
    Lines: 83
    Execution
    Paths: 16
    module Source
    class File
    attr_reader :filename
    def initialize(filename:)
    @filename = filename
    end
    def lines
    ::File.read(filename).split("\n")
    end
    end
    class GitTag
    def self.git_cmd
    GitCmd.new
    end
    attr_reader :filename, :tag, :repository, :git_cmd
    def initialize(filename:, repository:, tag:, git_cmd: self.class.git_cmd)
    @git_cmd = git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    end
    def lines
    git_cmd.show.split("\n")
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    end
    end
    OO
    module Subset
    class Everything
    def lines(everything)
    everything
    end
    end
    class LineNumber
    attr_reader :line_numbers
    def initialize(line_numbers:)
    @line_numbers = line_numbers
    end
    def lines(possibilities)
    clump_specs.collect {|spec| clump_for(spec, possibilities) }.flatten.compact
    end
    def clump_specs
    line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    end
    def clump_for(spec, possibilities)
    Clump.lines(spec: spec, possibilities: possibilities)
    end
    end
    end
    Subset:
    Source:
    class Clump
    def self.lines(spec:, possibilities: [])
    self.for(spec: spec, possibilities: possibilities).lines
    end
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    attr_reader :spec, :input
    def initialize(spec:, input: [])
    @spec = spec
    @input = input
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.compact
    end
    def expand_clump(spec)
    edges = spec.split('-').collect(&:to_i)
    (edges.min.to_i..edges.max.to_i).to_a
    end
    end
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    end
    Clump:
    module Justification
    class None
    def self.justify(lines)
    lines
    end
    end
    class BlockLeft
    def self.justify(lines)
    new(lines).justify
    end
    attr_reader :lines
    def initialize(lines)
    @lines = lines
    end
    def justify
    lines.map {|line| line.slice(num_leading_spaces_to_remove..-1) || "" }
    end
    private
    def num_leading_spaces_to_remove
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    end
    Justification:
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    justifier.justify(subsetter.lines(source.lines))
    end
    end
    Factories
    Procedure
    Classes: 9
    Biggest: 18 loc
    Total Lines: 134
    Execution Paths: 1

    View Slide

  352. @sandimetz May 2018
    Lines: 83
    Execution
    Paths: 16
    module Source
    class File
    attr_reader :filename
    def initialize(filename:)
    @filename = filename
    end
    def lines
    ::File.read(filename).split("\n")
    end
    end
    class GitTag
    def self.git_cmd
    GitCmd.new
    end
    attr_reader :filename, :tag, :repository, :git_cmd
    def initialize(filename:, repository:, tag:, git_cmd: self.class.git_cmd)
    @git_cmd = git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    end
    def lines
    git_cmd.show.split("\n")
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    end
    end
    OO
    module Subset
    class Everything
    def lines(everything)
    everything
    end
    end
    class LineNumber
    attr_reader :line_numbers
    def initialize(line_numbers:)
    @line_numbers = line_numbers
    end
    def lines(possibilities)
    clump_specs.collect {|spec| clump_for(spec, possibilities) }.flatten.compact
    end
    def clump_specs
    line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    end
    def clump_for(spec, possibilities)
    Clump.lines(spec: spec, possibilities: possibilities)
    end
    end
    end
    Subset:
    Source:
    class Clump
    def self.lines(spec:, possibilities: [])
    self.for(spec: spec, possibilities: possibilities).lines
    end
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    attr_reader :spec, :input
    def initialize(spec:, input: [])
    @spec = spec
    @input = input
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.compact
    end
    def expand_clump(spec)
    edges = spec.split('-').collect(&:to_i)
    (edges.min.to_i..edges.max.to_i).to_a
    end
    end
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    end
    Clump:
    module Justification
    class None
    def self.justify(lines)
    lines
    end
    end
    class BlockLeft
    def self.justify(lines)
    new(lines).justify
    end
    attr_reader :lines
    def initialize(lines)
    @lines = lines
    end
    def justify
    lines.map {|line| line.slice(num_leading_spaces_to_remove..-1) || "" }
    end
    private
    def num_leading_spaces_to_remove
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    end
    Justification:
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    justifier.justify(subsetter.lines(source.lines))
    end
    end
    Factories
    Procedure

    View Slide

  353. @sandimetz May 2018
    class Clump
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    # ...
    end

    View Slide

  354. @sandimetz May 2018
    class Clump
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    # ...
    end
    Factories are shared
    Factories are isolated
    Factories are simple
    Factories are ignorable

    View Slide

  355. @sandimetz May 2018
    module Source
    class File
    attr_reader :filename
    def initialize(filename:)
    @filename = filename
    end
    def lines
    ::File.read(filename).split("\n")
    end
    end
    class GitTag
    def self.git_cmd
    GitCmd.new
    end
    attr_reader :filename, :tag, :repository, :git_cmd
    def initialize(filename:, repository:, tag:, git_cmd: self.class.git_cmd)
    @git_cmd = git_cmd
    git_cmd.repository = repository
    git_cmd.tagname = tag
    git_cmd.filename = filename
    end
    def lines
    git_cmd.show.split("\n")
    end
    class GitCmd
    attr_accessor :repository, :tagname, :filename
    def show
    `git #{git_dir} show #{tagname}:#{filename}`
    end
    def git_dir
    %Q[--git-dir="#{repository}"]
    end
    end
    end
    end
    OO
    module Subset
    class Everything
    def lines(everything)
    everything
    end
    end
    class LineNumber
    attr_reader :line_numbers
    def initialize(line_numbers:)
    @line_numbers = line_numbers
    end
    def lines(possibilities)
    clump_specs.collect {|spec| clump_for(spec, possibilities) }.flatten.compact
    end
    def clump_specs
    line_numbers.gsub(/[‘|’]/, "").gsub(/ /,'').split(",")
    end
    def clump_for(spec, possibilities)
    Clump.lines(spec: spec, possibilities: possibilities)
    end
    end
    end
    Subset:
    Source:
    class Clump
    def self.lines(spec:, possibilities: [])
    self.for(spec: spec, possibilities: possibilities).lines
    end
    def self.for(spec:, possibilities: [])
    if spec.include?('#')
    Clump::Comment
    else
    Clump::LineNumber
    end.new(spec: spec, input: possibilities)
    end
    attr_reader :spec, :input
    def initialize(spec:, input: [])
    @spec = spec
    @input = input
    end
    class LineNumber < Clump
    def lines
    expand_clump(spec).collect {|i| input[i - 1]}.compact
    end
    def expand_clump(spec)
    edges = spec.split('-').collect(&:to_i)
    (edges.min.to_i..edges.max.to_i).to_a
    end
    end
    class Comment < Clump
    def lines
    num_spaces = spec.delete("#").to_i
    (" " * num_spaces) + "# ..."
    end
    end
    end
    Clump:
    module Justification
    class None
    def self.justify(lines)
    lines
    end
    end
    class BlockLeft
    def self.justify(lines)
    new(lines).justify
    end
    attr_reader :lines
    def initialize(lines)
    @lines = lines
    end
    def justify
    lines.map {|line| line.slice(num_leading_spaces_to_remove..-1) || "" }
    end
    private
    def num_leading_spaces_to_remove
    @num ||=
    lines.reduce(999999) {|current_min, line|
    line.empty? ? current_min : [current_min, num_leading_spaces(line)].min
    }
    end
    def num_leading_spaces(line)
    line[/\A */].size
    end
    end
    end
    Justification:
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    justifier.justify(subsetter.lines(source.lines))
    end
    end
    Factories

    View Slide

  356. @sandimetz May 2018
    OO
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    justifier.justify(subsetter.lines(source.lines))
    end
    end

    View Slide

  357. @sandimetz May 2018
    OO
    class Listing
    attr_reader :source, :subsetter, :justifier
    def initialize(source:, subsetter:, justifier:)
    @source = source
    @subsetter = subsetter
    @justifier = justifier
    end
    def lines
    justifier.justify(subsetter.lines(source.lines))
    end
    end

    View Slide

  358. @sandimetz May 2018
    Anthropomorphic
    Polymorphic
    Loosely-coupled
    Role-playing
    Factory-created
    Message-sending

    View Slide

  359. @sandimetz
    Thanks
    May 2018

    View Slide

  360. @sandimetz
    http://poodr.com
    May 2018

    View Slide

  361. @sandimetz
    http://99bottlesbook.com
    May 2018

    View Slide

  362. @sandimetz
    Sandi Metz
    @sandimetz
    http://sandimetz.com
    May 2018

    View Slide