Practical Debugging

Practical Debugging

People give ruby a bad reputation for speed, efficiency, weak typing, etc. But one of the biggest benefits of an interpreted language is the ability to debug and introspect quickly without compilation. Oftentimes developers reach for heavy-handed libraries to debug their application when they could just as easily get the information they need by using tools they already have.

In this talk you will learn practical techniques to make debugging easier. You will see how simple techniques from the ruby standard library can greatly increase your ability to keep your codebase clean and bug-free.

8a66c2a7197be751b21ebd35319ec797?s=128

Kevin Deisz

April 25, 2017
Tweet

Transcript

  1. Practical Debugging

  2. ~/debugging $ bin/minesweeper ... board.rb:49:in `block (2 levels) in build_cells':

    undefined method `text' for nil:NilClass (NoMethodError) from ... board.rb:43:in `times' from ... board.rb:43:in `each' from ... board.rb:43:in `map' from ... board.rb:43:in `block in build_cells' from ... board.rb:42:in `times' from ... board.rb:42:in `each' from ... board.rb:42:in `flat_map' from ... board.rb:42:in `build_cells' from ... board.rb:17:in `start' from ... minesweeper.rb:8:in `start' from bin/minesweeper:7:in `<main>’ ~/debugging $ Intro • Interface • State • Flow • Tradeoffs
  3. Intro • Interface • State • Flow • Tradeoffs

  4. Remain calm Intro • Interface • State • Flow •

    Tradeoffs
  5. What would StackOverflow do? Intro • Interface • State •

    Flow • Tradeoffs
  6. What would StackOverflow do? #WWSOD Intro • Interface • State

    • Flow • Tradeoffs
  7. Use pry Use byebug Intro • Interface • State •

    Flow • Tradeoffs
  8. Use pry Use byebug Intro • Interface • State •

    Flow • Tradeoffs
  9. Use pry Use byebug Intro • Interface • State •

    Flow • Tradeoffs
  10. Use pry Use byebug Intro • Interface • State •

    Flow • Tradeoffs
  11. Use pry Use byebug Intro • Interface • State •

    Flow • Tradeoffs
  12. Use pry Use the rub Intro • Interface • State

    • Flow • Tradeoffs
  13. Use pry Use the ruby debugger Intro • Interface •

    State • Flow • Tradeoffs
  14. Use pry Use the ruby debugger Intro • Interface •

    State • Flow • Tradeoffs
  15. Use pry Use the ruby debugger Intro • Interface •

    State • Flow • Tradeoffs
  16. Use pry Use the ruby debugger Intro • Interface •

    State • Flow • Tradeoffs
  17. At the end of the day, people want bug-free code.

    They don’t care how it got that way. Intro • Interface • State • Flow • Tradeoffs
  18. You don’t get style points in debugging. Intro • Interface

    • State • Flow • Tradeoffs
  19. The ruby standard library has every tool you need to

    debug effectively. Intro • Interface • State • Flow • Tradeoffs
  20. Debugging Intro • Interface • State • Flow • Tradeoffs

  21. What kind of problem are you solving? Intro • Interface

    • State • Flow • Tradeoffs
  22. Interface problem Intro • Interface • State • Flow •

    Tradeoffs
  23. Interface problem State problem Intro • Interface • State •

    Flow • Tradeoffs
  24. Interface problem State problem Flow problem Intro • Interface •

    State • Flow • Tradeoffs
  25. Interface problems Intro • Interface • State • Flow •

    Tradeoffs
  26. Interface problems occur when you don’t understand the dependent structure

    of methods or constants. Intro • Interface • State • Flow • Tradeoffs
  27. Why is this thing nil? Intro • Interface • State

    • Flow • Tradeoffs
  28. Why is this thing nil? Why can’t I call the

    method I want? Intro • Interface • State • Flow • Tradeoffs
  29. Why is this thing nil? Why can’t I call the

    method I want? What are the constants I can reference? Intro • Interface • State • Flow • Tradeoffs
  30. Why is this thing nil? Why can’t I call the

    method I want? What are the constants I can reference? What can this object see and do? Intro • Interface • State • Flow • Tradeoffs
  31. Why is this thing nil? Why can’t I call the

    method I want? What are the constants I can reference? What can this object see and do? Oh god oh god what is this gem even doing? Intro • Interface • State • Flow • Tradeoffs
  32. Why is this thing nil? Why can’t I call the

    method I want? What are the constants I can reference? What can this object see and do? Oh god oh god what is this gem even doing?
 I can’t even. Intro • Interface • State • Flow • Tradeoffs
  33. Why is this thing nil? Intro • Interface • State

    • Flow • Tradeoffs
  34. ~/debugging $ bin/minesweeper ... board.rb:57:in `[]': no implicit conversion from

    nil to integer (TypeError) from ... board.rb:57:in `block (3 levels) in build_cells' from ... board.rb:57:in `count' from ... board.rb:57:in `block (2 levels) in build_cells' from ... board.rb:44:in `times' from ... board.rb:44:in `each' from ... board.rb:44:in `map' from ... board.rb:44:in `block in build_cells' from ... board.rb:43:in `times' from ... board.rb:43:in `each' from ... board.rb:43:in `flat_map' from ... board.rb:43:in `build_cells' from ... board.rb:17:in `start' from ... minesweeper.rb:9:in `start' from bin/minesweeper:7:in `<main>' Intro • Interface • State • Flow • Tradeoffs
  35. ~/debugging $ bin/minesweeper ... board.rb:57:in `[]': no implicit conversion from

    nil to integer (TypeError) from ... board.rb:57:in `block (3 levels) in build_cells' from ... board.rb:57:in `count' from ... board.rb:57:in `block (2 levels) in build_cells' from ... board.rb:44:in `times' from ... board.rb:44:in `each' from ... board.rb:44:in `map' from ... board.rb:44:in `block in build_cells' from ... board.rb:43:in `times' from ... board.rb:43:in `each' from ... board.rb:43:in `flat_map' from ... board.rb:43:in `build_cells' from ... board.rb:17:in `start' from ... minesweeper.rb:9:in `start' from bin/minesweeper:7:in `<main>' Intro • Interface • State • Flow • Tradeoffs
  36. module MineSweeper class Board def build_cells(tk_root) ... neighbors = neighbors_for(ycoord,

    xcoord) mine_count = neighbors.count { |neighbor| mines[neighbor] } ... end def neighbors_for(ycoord, xcoord) [ index_for(ycoord - 1, xcoord - 1), index_for(ycoord - 1, xcoord), ... ] end def index_for(ycoord, xcoord) return nil if ycoord < 0 || xcoord < 0 || ycoord >= height || xcoord >= width ycoord * width + xcoord end end end Intro • Interface • State • Flow • Tradeoffs
  37. module MineSweeper class Board def build_cells(tk_root) ... neighbors = neighbors_for(ycoord,

    xcoord) mine_count = neighbors.count { |neighbor| mines[neighbor] } ... end def neighbors_for(ycoord, xcoord) [ index_for(ycoord - 1, xcoord - 1), index_for(ycoord - 1, xcoord), ... ] end def index_for(ycoord, xcoord) return nil if ycoord < 0 || xcoord < 0 || ycoord >= height || xcoord >= width ycoord * width + xcoord end end end Intro • Interface • State • Flow • Tradeoffs
  38. module MineSweeper class Board def build_cells(tk_root) ... neighbors = neighbors_for(ycoord,

    xcoord) mine_count = neighbors.count { |neighbor| mines[neighbor] } ... end def neighbors_for(ycoord, xcoord) [ index_for(ycoord - 1, xcoord - 1), index_for(ycoord - 1, xcoord), ... ] end def index_for(ycoord, xcoord) return nil if ycoord < 0 || xcoord < 0 || ycoord >= height || xcoord >= width ycoord * width + xcoord end end end Intro • Interface • State • Flow • Tradeoffs
  39. module MineSweeper class Board def build_cells(tk_root) ... neighbors = neighbors_for(ycoord,

    xcoord) mine_count = neighbors.count { |neighbor| mines[neighbor] } ... end def neighbors_for(ycoord, xcoord) [ index_for(ycoord - 1, xcoord - 1), index_for(ycoord - 1, xcoord), ... ] end def index_for(ycoord, xcoord) return nil if ycoord < 0 || xcoord < 0 || ycoord >= height || xcoord >= width ycoord * width + xcoord end end end Intro • Interface • State • Flow • Tradeoffs
  40. module MineSweeper class Board def build_cells(tk_root) ... neighbors = neighbors_for(ycoord,

    xcoord) mine_count = neighbors.count { |neighbor| mines[neighbor] } ... end def neighbors_for(ycoord, xcoord) [ index_for(ycoord - 1, xcoord - 1), index_for(ycoord - 1, xcoord), ... ] end def index_for(ycoord, xcoord) return nil if ycoord < 0 || xcoord < 0 || ycoord >= height || xcoord >= width ycoord * width + xcoord end end end Intro • Interface • State • Flow • Tradeoffs
  41. module MineSweeper class Board def build_cells(tk_root) ... neighbors = neighbors_for(ycoord,

    xcoord) mine_count = neighbors.count { |neighbor| mines[neighbor] } ... end def neighbors_for(ycoord, xcoord) [ index_for(ycoord - 1, xcoord - 1), index_for(ycoord - 1, xcoord), ... ] end def index_for(ycoord, xcoord) return nil if ycoord < 0 || xcoord < 0 || ycoord >= height || xcoord >= width ycoord * width + xcoord end end end Intro • Interface • State • Flow • Tradeoffs
  42. module MineSweeper class Board def build_cells(tk_root) ... neighbors = neighbors_for(ycoord,

    xcoord) mine_count = neighbors.count { |neighbor| mines[neighbor] } ... end def neighbors_for(ycoord, xcoord) [ index_for(ycoord - 1, xcoord - 1), index_for(ycoord - 1, xcoord), ... ] end Option[Int] index_for(ycoord, xcoord) return nil if ycoord < 0 || xcoord < 0 || ycoord >= height || xcoord >= width ycoord * width + xcoord end end end Intro • Interface • State • Flow • Tradeoffs
  43. module MineSweeper class Board def build_cells(tk_root) ... neighbors = neighbors_for(ycoord,

    xcoord) mine_count = neighbors.count { |neighbor| mines[neighbor] } ... end def neighbors_for(ycoord, xcoord) [ index_for(ycoord - 1, xcoord - 1), index_for(ycoord - 1, xcoord), ... ] end Optional<Integer> index_for(ycoord, xcoord) return nil if ycoord < 0 || xcoord < 0 || ycoord >= height || xcoord >= width ycoord * width + xcoord end end end Intro • Interface • State • Flow • Tradeoffs
  44. module MineSweeper class Board def build_cells(tk_root) ... neighbors = neighbors_for(ycoord,

    xcoord) mine_count = neighbors.count { |neighbor| mines[neighbor] } ... end def neighbors_for(ycoord, xcoord) [ index_for(ycoord - 1, xcoord - 1), index_for(ycoord - 1, xcoord), ... ] end Maybe Int index_for(ycoord, xcoord) return nil if ycoord < 0 || xcoord < 0 || ycoord >= height || xcoord >= width ycoord * width + xcoord end end end Intro • Interface • State • Flow • Tradeoffs
  45. module MineSweeper class Board def build_cells(tk_root) ... neighbors = neighbors_for(ycoord,

    xcoord) mine_count = neighbors.count { |neighbor| mines[neighbor] } ... end def neighbors_for(ycoord, xcoord) [ index_for(ycoord - 1, xcoord - 1), index_for(ycoord - 1, xcoord), ... ] end def index_for(ycoord, xcoord) return nil if ycoord < 0 || xcoord < 0 || ycoord >= height || xcoord >= width ycoord * width + xcoord end end end Intro • Interface • State • Flow • Tradeoffs
  46. module MineSweeper class Board def build_cells(tk_root) ... neighbors = neighbors_for(ycoord,

    xcoord) mine_count = neighbors.count { |neighbor| mines[neighbor] } ... end def neighbors_for(ycoord, xcoord) [ index_for(ycoord - 1, xcoord - 1), index_for(ycoord - 1, xcoord), ... ].compact end def index_for(ycoord, xcoord) return nil if ycoord < 0 || xcoord < 0 || ycoord >= height || xcoord >= width ycoord * width + xcoord end end end Intro • Interface • State • Flow • Tradeoffs
  47. Why can’t I call the method I want? Intro •

    Interface • State • Flow • Tradeoffs
  48. ~/debugging $ bin/minesweeper ... tk-0.1.2/lib/tk.rb:4984:in `rescue in method_missing': unknown option

    'mines' for #<Tk::Label:0x007fb839800658 @path=".w00200"> (deleted widget?) (NameError) from ... tk-0.1.2/lib/tk.rb:4980:in `method_missing' from ... minesweeper/board.rb:63:in `block in build_status_label' from ... minesweeper/board.rb:62:in `new' from ... minesweeper/board.rb:62:in `build_status_label' from ... minesweeper/board.rb:18:in `start' from ... minesweeper.rb:8:in `start' from bin/minesweeper:7:in `<main>’ ~/debugging $ Intro • Interface • State • Flow • Tradeoffs
  49. ~/debugging $ bin/minesweeper ... tk-0.1.2/lib/tk.rb:4984:in `rescue in method_missing': unknown option

    'mines' for #<Tk::Label:0x007fb839800658 @path=".w00200"> (deleted widget?) (NameError) from ... tk-0.1.2/lib/tk.rb:4980:in `method_missing' from ... minesweeper/board.rb:63:in `block in build_status_label' from ... minesweeper/board.rb:62:in `new' from ... minesweeper/board.rb:62:in `build_status_label' from ... minesweeper/board.rb:18:in `start' from ... minesweeper.rb:8:in `start' from bin/minesweeper:7:in `<main>’ ~/debugging $ Intro • Interface • State • Flow • Tradeoffs
  50. ~/debugging $ bin/minesweeper ... tk-0.1.2/lib/tk.rb:4984:in `rescue in method_missing': unknown option

    'mines' for #<Tk::Label:0x007fb839800658 @path=".w00200"> (deleted widget?) (NameError) from ... tk-0.1.2/lib/tk.rb:4980:in `method_missing' from ... minesweeper/board.rb:63:in `block in build_status_label' from ... minesweeper/board.rb:62:in `new' from ... minesweeper/board.rb:62:in `build_status_label' from ... minesweeper/board.rb:18:in `start' from ... minesweeper.rb:8:in `start' from bin/minesweeper:7:in `<main>’ ~/debugging $ Intro • Interface • State • Flow • Tradeoffs
  51. module MineSweeper class Board attr_reader :width, :mines ... private ...

    def build_status_label(tk_root) TkLabel.new(tk_root) do text "#{mines} mines left" grid(column: 0, row: 0, columnspan: width) end end end end Intro • Interface • State • Flow • Tradeoffs
  52. module MineSweeper class Board attr_reader :width, :mines ... private ...

    def build_status_label(tk_root) TkLabel.new(tk_root) do text "#{mines} mines left" grid(column: 0, row: 0, columnspan: width) end end end end Intro • Interface • State • Flow • Tradeoffs
  53. module MineSweeper class Board attr_reader :width, :mines ... private ...

    def build_status_label(tk_root) TkLabel.new(tk_root) do p methods.grep(/mines/) p methods exit text "#{mines} mines left" grid(column: 0, row: 0, columnspan: width) end end end end Intro • Interface • State • Flow • Tradeoffs
  54. module MineSweeper class Board attr_reader :width, :mines ... private ...

    def build_status_label(tk_root) TkLabel.new(tk_root) do p methods.grep(/mines/) p methods exit text "#{mines} mines left" grid(column: 0, row: 0, columnspan: width) end end end end Intro • Interface • State • Flow • Tradeoffs
  55. module MineSweeper class Board attr_reader :width, :mines ... private ...

    def build_status_label(tk_root) TkLabel.new(tk_root) do p methods.grep(/mines/) p methods exit text "#{mines} mines left" grid(column: 0, row: 0, columnspan: width) end end end end Intro • Interface • State • Flow • Tradeoffs
  56. module MineSweeper class Board attr_reader :width, :mines ... private ...

    def build_status_label(tk_root) TkLabel.new(tk_root) do p methods.grep(/mines/) puts methods.inspect exit text "#{mines} mines left" grid(column: 0, row: 0, columnspan: width) end end end end Intro • Interface • State • Flow • Tradeoffs
  57. module MineSweeper class Board attr_reader :width, :mines ... private ...

    def build_status_label(tk_root) TkLabel.new(tk_root) do p methods.grep(/mines/) p methods exit text "#{mines} mines left" grid(column: 0, row: 0, columnspan: width) end end end end Intro • Interface • State • Flow • Tradeoffs
  58. module MineSweeper class Board attr_reader :width, :mines ... private ...

    def build_status_label(tk_root) TkLabel.new(tk_root) do p methods.grep(/mines/) p methods exit text "#{mines} mines left" grid(column: 0, row: 0, columnspan: width) end end end end Intro • Interface • State • Flow • Tradeoffs
  59. module MineSweeper class Board attr_reader :width, :mines ... private ...

    def build_status_label(tk_root) TkLabel.new(tk_root) do p methods.grep(/mines/) p methods exit text "#{mines} mines left" grid(column: 0, row: 0, columnspan: width) end end end end Intro • Interface • State • Flow • Tradeoffs
  60. module MineSweeper class Board attr_reader :width, :mines ... private ...

    def build_status_label(tk_root) TkLabel.new(tk_root) do p methods.grep(/mines/) p methods exit text "#{mines} mines left" grid(column: 0, row: 0, columnspan: width) end end end end Intro • Interface • State • Flow • Tradeoffs
  61. ~/debugging $ bin/minesweeper [] [:raise, :exist?, :subcommand, :bind_class, :database _classname,

    :database_class, :pack_in, :pack, :unpack , :pack_configure, :pack_config, :pack_propagate, :pa ck_info, :pack_slaves, :grid_in, :grid_anchor, :grid_ bbox, :grid_config, :grid_configure, :grid_columnconf ig, :grid_columnconfigure, :grid_rowconfig, :grid_row configure, :grid_columnconfiginfo, :grid_rowconfiginf o, :grid_column, :grid_row, :grid_info, :grid_locatio n, :thread_wait, :grid_propagate, :pack_forget, :grid _remove, :grid, :grid_slaves, :grid_forget, :ungrid, :place, :grid_size, :place_forget, :unplace, :place_c onfig, :place_configure, :set_focus, :place_in, :lowe r_window, :current_grab, :raise_window, ... ] ~/debugging $ Intro • Interface • State • Flow • Tradeoffs
  62. module MineSweeper class Board attr_reader :width, :mines ... private ...

    def build_status_label(tk_root) TkLabel.new(tk_root) do p methods.grep(/mines/) p methods exit text "#{mines} mines left" grid(column: 0, row: 0, columnspan: width) end end end end Intro • Interface • State • Flow • Tradeoffs
  63. module MineSweeper class Board attr_reader :width, :mines ... private ...

    def build_status_label(tk_root) TkLabel.new(tk_root) do text "#{mines} mines left" grid(column: 0, row: 0, columnspan: width) end end end end Intro • Interface • State • Flow • Tradeoffs
  64. module MineSweeper class Board attr_reader :width, :mines ... private ...

    def build_status_label(tk_root) initial_status = "#{mines} mines left" column_span = width TkLabel.new(tk_root) do text initial_status grid(column: 0, row: 0, columnspan: column_span) end end end end Intro • Interface • State • Flow • Tradeoffs
  65. What are the constants I can reference? Intro • Interface

    • State • Flow • Tradeoffs
  66. module MineSweeper module Cell class Base end end class Cell::Mine

    < Base end class Cell::Neighbor < Base end class Cell::Empty < Base end end Intro • Interface • State • Flow • Tradeoffs
  67. ~/debugging $ ruby examples/module_nesting_1.rb examples/module_nesting_1.rb:7:in `<module:MineSweeper>': uninitialized constant MineSweeper::Base (NameError)

    from examples/module_nesting_1.rb:1:in `<main>' ~/debugging $ Intro • Interface • State • Flow • Tradeoffs
  68. ~/debugging $ ruby examples/module_nesting_1.rb examples/module_nesting_1.rb:7:in `<module:MineSweeper>': uninitialized constant MineSweeper::Base (NameError)

    from examples/module_nesting_1.rb:1:in `<main>' ~/debugging $ Intro • Interface • State • Flow • Tradeoffs
  69. module MineSweeper module Cell class Base end end class Cell::Mine

    < Base end class Cell::Neighbor < Base end class Cell::Empty < Base end end Intro • Interface • State • Flow • Tradeoffs
  70. module MineSweeper module Cell class Base end end puts Module.nesting;

    exit class Cell::Mine < Base end class Cell::Neighbor < Base end class Cell::Empty < Base end end Intro • Interface • State • Flow • Tradeoffs
  71. ~/debugging $ ruby examples/module_nesting_1.rb MineSweeper ~/debugging $ Intro • Interface

    • State • Flow • Tradeoffs
  72. module MineSweeper module Cell class Base end end puts Module.nesting;

    exit class Cell::Mine < Base end class Cell::Neighbor < Base end class Cell::Empty < Base end end Intro • Interface • State • Flow • Tradeoffs
  73. module MineSweeper module Cell class Base end puts Module.nesting; exit

    class Mine < Base end class Neighbor < Base end class Empty < Base end end end Intro • Interface • State • Flow • Tradeoffs
  74. module MineSweeper module Cell class Base end puts Module.nesting; exit

    class Mine < Base end class Neighbor < Base end class Empty < Base end end end Intro • Interface • State • Flow • Tradeoffs
  75. ~/debugging $ ruby examples/module_nesting_2.rb MineSweeper::Cell MineSweeper ~/debugging $ Intro •

    Interface • State • Flow • Tradeoffs
  76. What can this object see and do? Intro • Interface

    • State • Flow • Tradeoffs
  77. def puts_constants(constant) puts constant.name constant.constants.each do |identifier| child_constant = constant.const_get(identifier)

    if child_constant != constant && child_constant.is_a?(Class) puts_constants(child_constant) end end end puts_constants(MineSweeper) Intro • Interface • State • Flow • Tradeoffs
  78. def puts_constants(constant) puts constant.name constant.constants.each do |identifier| child_constant = constant.const_get(identifier)

    if child_constant != constant && child_constant.is_a?(Class) puts_constants(child_constant) end end end puts_constants(MineSweeper) Intro • Interface • State • Flow • Tradeoffs
  79. def puts_constants(constant) puts constant.name constant.constants.each do |identifier| child_constant = constant.const_get(identifier)

    if child_constant != constant && child_constant.is_a?(Class) puts_constants(child_constant) end end end puts_constants(MineSweeper) Intro • Interface • State • Flow • Tradeoffs
  80. def puts_constants(constant) puts constant.name constant.constants.each do |identifier| child_constant = constant.const_get(identifier)

    if child_constant != constant && child_constant.is_a?(Class) puts_constants(child_constant) end end end puts_constants(MineSweeper) Intro • Interface • State • Flow • Tradeoffs
  81. def puts_constants(constant) puts constant.name constant.constants.each do |identifier| child_constant = constant.const_get(identifier)

    if child_constant != constant && child_constant.is_a?(Class) puts_constants(child_constant) end end end puts_constants(MineSweeper) Intro • Interface • State • Flow • Tradeoffs
  82. ~/debugging $ ruby -Ilib -rminesweeper examples/constants.rb MineSweeper MineSweeper::Board MineSweeper::Cell ~/debugging

    $ Intro • Interface • State • Flow • Tradeoffs
  83. def puts_constants(constant) puts constant.name constant.constants.each do |identifier| child_constant = constant.const_get(identifier)

    if child_constant != constant && child_constant.is_a?(Class) puts_constants(child_constant) end end end puts_constants(MineSweeper) Intro • Interface • State • Flow • Tradeoffs
  84. def puts_constants(constant) puts constant.name constant.constants.each do |identifier| child_constant = constant.const_get(identifier)

    if child_constant != constant && child_constant.is_a?(Class) puts_constants(child_constant) end end end puts_constants(ActiveRecord) Intro • Interface • State • Flow • Tradeoffs
  85. ~/debugging $ ruby -Iactiverecord -ractive_record examples/constants.rb ActiveRecord ActiveRecord::LazyAttributeHash ActiveRecord::StatementInvalid ActiveRecord::MigrationProxy

    Process::Tms ActiveRecord::Attribute ActiveRecord::Base ActiveRecord::Store::StringKeyedHashAccessor ActiveRecord::Store::IndifferentHashAccessor ActiveRecord::Store::IndifferentCoder ActiveRecord::Store::HashAccessor ActiveRecord::Reflection::ThroughReflection ... ~/debugging $ Intro • Interface • State • Flow • Tradeoffs
  86. ~/debugging $ ruby -Ilib -rminesweeper -e \ > "p MineSweeper::Board.instance_methods"

    [:status, :start, :width, :height, :mines, :cells, :click, :update_status, :instance_of?, :kind_of?, :is_a?, :tap, :p ublic_send, :remove_instance_variable, :public_method, :si ngleton_method, :instance_variable_set, :define_singleton_ method, :method, :extend, :to_enum, :enum_for, :<=>, :===, :=~, :! ~, :eql?, :respond_to?, :freeze, :inspect, :object_id, :se nd, :display, :to_s, :nil?, :hash, :class, :singleton_clas s, :clone, :dup, :itself, :taint, :tainted?, :untaint, :un trust, :untrusted?, :trust, :frozen?, :methods, :singleton _methods, :protected_methods, :private_methods, ...] ~/debugging $ Intro • Interface • State • Flow • Tradeoffs
  87. ~/debugging $ ruby -Ilib -rminesweeper -e \ > "p MineSweeper::Board.instance_methods"

    [:status, :start, :width, :height, :mines, :cells, :click, :update_status, :instance_of?, :kind_of?, :is_a?, :tap, :p ublic_send, :remove_instance_variable, :public_method, :si ngleton_method, :instance_variable_set, :define_singleton_ method, :method, :extend, :to_enum, :enum_for, :<=>, :===, :=~, :! ~, :eql?, :respond_to?, :freeze, :inspect, :object_id, :se nd, :display, :to_s, :nil?, :hash, :class, :singleton_clas s, :clone, :dup, :itself, :taint, :tainted?, :untaint, :un trust, :untrusted?, :trust, :frozen?, :methods, :singleton _methods, :protected_methods, :private_methods, ...] ~/debugging $ Intro • Interface • State • Flow • Tradeoffs
  88. ~/debugging $ ruby -Ilib -rminesweeper -e \ > "p MineSweeper::Board.instance_methods"

    [:status, :start, :width, :height, :mines, :cells, :click, :update_status, :instance_of?, :kind_of?, :is_a?, :tap, :p ublic_send, :remove_instance_variable, :public_method, :si ngleton_method, :instance_variable_set, :define_singleton_ method, :method, :extend, :to_enum, :enum_for, :<=>, :===, :=~, :! ~, :eql?, :respond_to?, :freeze, :inspect, :object_id, :se nd, :display, :to_s, :nil?, :hash, :class, :singleton_clas s, :clone, :dup, :itself, :taint, :tainted?, :untaint, :un trust, :untrusted?, :trust, :frozen?, :methods, :singleton _methods, :protected_methods, :private_methods, ...] ~/debugging $ Intro • Interface • State • Flow • Tradeoffs
  89. ~/debugging $ ruby -Ilib -rminesweeper -e \ > "p MineSweeper::Board.instance_methods"

    [:status, :start, :width, :height, :mines, :cells, :click, :update_status, :instance_of?, :kind_of?, :is_a?, :tap, :p ublic_send, :remove_instance_variable, :public_method, :si ngleton_method, :instance_variable_set, :define_singleton_ method, :method, :extend, :to_enum, :enum_for, :<=>, :===, :=~, :! ~, :eql?, :respond_to?, :freeze, :inspect, :object_id, :se nd, :display, :to_s, :nil?, :hash, :class, :singleton_clas s, :clone, :dup, :itself, :taint, :tainted?, :untaint, :un trust, :untrusted?, :trust, :frozen?, :methods, :singleton _methods, :protected_methods, :private_methods, ...] ~/debugging $ Intro • Interface • State • Flow • Tradeoffs
  90. ~/debugging $ ruby -Ilib -rminesweeper -e \ > "p MineSweeper::Board.instance_methods(false)"

    [:status, :start, :width, :height, :mines, :cells, :click, :update_status] ~/debugging $ Intro • Interface • State • Flow • Tradeoffs
  91. ~/debugging $ ruby -Ilib -rminesweeper -e \ > "p MineSweeper::Board.private_instance_methods"

    [:initialize, :build_cells, :build_status_label, :index_fo r, :neighbors_for, :DelegateClass, :TkPack, :TkGrid, :TkPl ace, :sprintf, :format, :Integer, :Float, :String, :Array, :Hash, :fail, :iterator?, :__method__, :catch, :__dir__, : loop, :global_variables, :throw, :block_given?, :raise, :_ _callee__, :eval, :Rational, :trace_var, :untrace_var, :Co mplex, :at_exit, :set_trace_func, :gem, :select, :caller, :caller_locations, :`, :test, :fork, :exit, :sleep, :respo nd_to_missing?, :gem_original_require, :load, :exec, :exit !, :system, :spawn, :abort, :syscall, :open, :printf, :pri nt, :putc, :puts, :gets, :readlines, :readline, ...] ~/debugging $ Intro • Interface • State • Flow • Tradeoffs
  92. ~/debugging $ ruby -Ilib -rminesweeper -e \ > "p MineSweeper::Board.private_instance_methods(false)"

    [:initialize, :build_cells, :build_status_label, :index_fo r, :neighbors_for] ~/debugging $ Intro • Interface • State • Flow • Tradeoffs
  93. Oh god oh god what is this gem even doing?

    Intro • Interface • State • Flow • Tradeoffs
  94. ~/debugging $ bin/minesweeper ... tk-0.1.2/lib/tk.rb:2054:in `_invoke': ambiguous option "-colum": must

    be -column, -columnspan, -in, -ipadx, - ipady, -padx, -pady, -row, -rowspan, or -sticky (RuntimeError) from ... tk-0.1.2/lib/tk.rb:2054:in `_ip_invoke_core' from ... tk-0.1.2/lib/tk.rb:2090:in `_tk_call_core' from ... tk-0.1.2/lib/tk.rb:2118:in `tk_call_without_enc' from ... tk-0.1.2/lib/tk/grid.rb:97:in `configure' from ... tk-0.1.2/lib/tk.rb:5301:in `grid' from ... minesweeper/board.rb:67:in `block in build_status_label' from ... minesweeper/board.rb:65:in `new' from ... minesweeper/board.rb:65:in `build_status_label' from ... minesweeper/board.rb:18:in `start' from ... minesweeper.rb:7:in `start' from bin/minesweeper:7:in `<main>' Intro • Interface • State • Flow • Tradeoffs
  95. ~/debugging $ bin/minesweeper ... tk-0.1.2/lib/tk.rb:2054:in `_invoke': ambiguous option "-colum": must

    be -column, -columnspan, -in, -ipadx, - ipady, -padx, -pady, -row, -rowspan, or -sticky (RuntimeError) from ... tk-0.1.2/lib/tk.rb:2054:in `_ip_invoke_core' from ... tk-0.1.2/lib/tk.rb:2090:in `_tk_call_core' from ... tk-0.1.2/lib/tk.rb:2118:in `tk_call_without_enc' from ... tk-0.1.2/lib/tk/grid.rb:97:in `configure' from ... tk-0.1.2/lib/tk.rb:5301:in `grid' from ... minesweeper/board.rb:67:in `block in build_status_label' from ... minesweeper/board.rb:65:in `new' from ... minesweeper/board.rb:65:in `build_status_label' from ... minesweeper/board.rb:18:in `start' from ... minesweeper.rb:7:in `start' from bin/minesweeper:7:in `<main>' Intro • Interface • State • Flow • Tradeoffs
  96. ~/debugging $ bin/minesweeper ... tk-0.1.2/lib/tk.rb:2054:in `_invoke': ambiguous option "-colum": must

    be -column, -columnspan, -in, -ipadx, - ipady, -padx, -pady, -row, -rowspan, or -sticky (RuntimeError) from ... tk-0.1.2/lib/tk.rb:2054:in `_ip_invoke_core' from ... tk-0.1.2/lib/tk.rb:2090:in `_tk_call_core' from ... tk-0.1.2/lib/tk.rb:2118:in `tk_call_without_enc' from ... tk-0.1.2/lib/tk/grid.rb:97:in `configure' from ... tk-0.1.2/lib/tk.rb:5301:in `grid' from ... minesweeper/board.rb:67:in `block in build_status_label' from ... minesweeper/board.rb:65:in `new' from ... minesweeper/board.rb:65:in `build_status_label' from ... minesweeper/board.rb:18:in `start' from ... minesweeper.rb:7:in `start' from bin/minesweeper:7:in `<main>' Intro • Interface • State • Flow • Tradeoffs
  97. module MineSweeper class Board attr_reader :width, :mines ... private ...

    def build_status_label(tk_root) initial_status = "#{mines} mines left" column_span = width TkLabel.new(tk_root) do text initial_status grid(colum: 0, row: 0, columnspan: column_span) end end end end Intro • Interface • State • Flow • Tradeoffs
  98. module MineSweeper class Board attr_reader :width, :mines ... private ...

    def build_status_label(tk_root) initial_status = "#{mines} mines left" column_span = width TkLabel.new(tk_root) do text initial_status grid(colum: 0, row: 0, columnspan: column_span) end end end end Intro • Interface • State • Flow • Tradeoffs
  99. ~/debugging $ bin/console irb(main):001:0> Intro • Interface • State •

    Flow • Tradeoffs
  100. ~/debugging $ bin/console irb(main):001:0> method = TkLabel.instance_method(:grid) => #<UnboundMethod: Tk::Label(TkWindow)#grid>

    irb(main):002:0> Intro • Interface • State • Flow • Tradeoffs
  101. ~/debugging $ bin/console irb(main):001:0> method = TkLabel.instance_method(:grid) => #<UnboundMethod: Tk::Label(TkWindow)#grid>

    irb(main):002:0> method.source_location => ["... ruby/gems/2.4.0/gems/tk-0.1.2/lib/tk.rb", 5298] irb(main):003:0> Intro • Interface • State • Flow • Tradeoffs
  102. ~/debugging $ bin/console irb(main):001:0> method = TkLabel.instance_method(:grid) => #<UnboundMethod: Tk::Label(TkWindow)#grid>

    irb(main):002:0> method.source_location => ["... ruby/gems/2.4.0/gems/tk-0.1.2/lib/tk.rb", 5298] irb(main):003:0> Intro • Interface • State • Flow • Tradeoffs
  103. ~/debugging $ bin/console irb(main):001:0> method = TkLabel.instance_method(:grid) => #<UnboundMethod: Tk::Label(TkWindow)#grid>

    irb(main):002:0> method.source_location => ["... ruby/gems/2.4.0/gems/tk-0.1.2/lib/tk.rb", 5298] irb(main):003:0> Intro • Interface • State • Flow • Tradeoffs
  104. ~/debugging $ bin/console irb(main):001:0> method = TkLabel.instance_method(:grid) => #<UnboundMethod: Tk::Label(TkWindow)#grid>

    irb(main):002:0> method.source_location => ["... ruby/gems/2.4.0/gems/tk-0.1.2/lib/tk.rb", 5298] irb(main):003:0> Intro • Interface • State • Flow • Tradeoffs
  105. class TkWindow<TkObject ... def grid(keys = nil) #tk_call 'grid', epath,

    *hash_kv(keys) if keys TkGrid.configure(self, keys) else TkGrid.configure(self) end self end end Intro • Interface • State • Flow • Tradeoffs
  106. class TkWindow<TkObject ... def grid(keys = nil) #tk_call 'grid', epath,

    *hash_kv(keys) if keys TkGrid.configure(self, keys) else TkGrid.configure(self) end self end end Intro • Interface • State • Flow • Tradeoffs
  107. ~/debugging $ bin/console irb(main):001:0> method = TkLabel.instance_method(:grid) => #<UnboundMethod: Tk::Label(TkWindow)#grid>

    irb(main):002:0> method.source_location => ["... gems/2.4.0/gems/tk-0.1.2/lib/tk.rb", 5298] irb(main):003:0> Intro • Interface • State • Flow • Tradeoffs
  108. ~/debugging $ bin/console irb(main):001:0> method = TkLabel.instance_method(:grid) => #<UnboundMethod: Tk::Label(TkWindow)#grid>

    irb(main):002:0> method.source_location => ["... gems/2.4.0/gems/tk-0.1.2/lib/tk.rb", 5298] irb(main):003:0> method = TkGrid.method(:configure) => #<Method: TkGrid.configure> irb(main):004:0> Intro • Interface • State • Flow • Tradeoffs
  109. ~/debugging $ bin/console irb(main):001:0> method = TkLabel.instance_method(:grid) => #<UnboundMethod: Tk::Label(TkWindow)#grid>

    irb(main):002:0> method.source_location => ["... gems/2.4.0/gems/tk-0.1.2/lib/tk.rb", 5298] irb(main):003:0> method = TkGrid.method(:configure) => #<Method: TkGrid.configure> irb(main):004:0> method.source_location => ["... gems/2.4.0/gems/tk-0.1.2/lib/tk/grid.rb", 59] irb(main):005:0> Intro • Interface • State • Flow • Tradeoffs
  110. ~/debugging $ bin/console irb(main):001:0> method = TkLabel.instance_method(:grid) => #<UnboundMethod: Tk::Label(TkWindow)#grid>

    irb(main):002:0> method.source_location => ["... gems/2.4.0/gems/tk-0.1.2/lib/tk.rb", 5298] irb(main):003:0> method = TkGrid.method(:configure) => #<Method: TkGrid.configure> irb(main):004:0> method.source_location => ["... gems/2.4.0/gems/tk-0.1.2/lib/tk/grid.rb", 59] irb(main):005:0> Intro • Interface • State • Flow • Tradeoffs
  111. module TkGrid ... def configure(*args) ... opts.each{|k, v| params.push("-#{k}") params.push(_epath(v))

    # have to use 'epath' (hash_kv() is unavailable) } if Tk::TCL_MAJOR_VERSION < 8 || (Tk::TCL_MAJOR_VERSION == 8 && Tk::TCL_MINOR_VERSION <= 3) if params[0] == '-' || params[0] == 'x' || params[0] == '^' tk_call_without_enc('grid', *params) else tk_call_without_enc('grid', 'configure', *params) end else tk_call_without_enc('grid', 'configure', *params) end end end Intro • Interface • State • Flow • Tradeoffs
  112. module TkGrid ... def configure(*args) ... opts.each{|k, v| params.push("-#{k}") params.push(_epath(v))

    # have to use 'epath' (hash_kv() is unavailable) } if Tk::TCL_MAJOR_VERSION < 8 || (Tk::TCL_MAJOR_VERSION == 8 && Tk::TCL_MINOR_VERSION <= 3) if params[0] == '-' || params[0] == 'x' || params[0] == '^' tk_call_without_enc('grid', *params) else tk_call_without_enc('grid', 'configure', *params) end else tk_call_without_enc('grid', 'configure', *params) end end end Intro • Interface • State • Flow • Tradeoffs
  113. module TkGrid ... def configure(*args) ... opts.each{|k, v| params.push("-#{k}") params.push(_epath(v))

    # have to use 'epath' (hash_kv() is unavailable) } if Tk::TCL_MAJOR_VERSION < 8 || (Tk::TCL_MAJOR_VERSION == 8 && Tk::TCL_MINOR_VERSION <= 3) if params[0] == '-' || params[0] == 'x' || params[0] == '^' tk_call_without_enc('grid', *params) else tk_call_without_enc('grid', 'configure', *params) end else tk_call_without_enc('grid', 'configure', *params) end end end Intro • Interface • State • Flow • Tradeoffs
  114. module TkGrid ... def configure(*args) ... opts.each{|k, v| params.push("-#{k}") params.push(_epath(v))

    # have to use 'epath' (hash_kv() is unavailable) } p params if Tk::TCL_MAJOR_VERSION < 8 || (Tk::TCL_MAJOR_VERSION == 8 && Tk::TCL_MINOR_VERSION <= 3) if params[0] == '-' || params[0] == 'x' || params[0] == '^' tk_call_without_enc('grid', *params) else tk_call_without_enc('grid', 'configure', *params) end else tk_call_without_enc('grid', 'configure', *params) end end end Intro • Interface • State • Flow • Tradeoffs
  115. ~/debugging $ bin/minesweeper [".w00000", "-column", 0, "-row", 1] [".w00001", "-column",

    1, "-row", 1] ... [".w00197", "-column", 17, "-row", 10] [".w00198", "-column", 18, "-row", 10] [".w00199", "-column", 19, "-row", 10] [".w00200", "-colum", 0, "-row", 0, "-columnspan", 20] ... tk-0.1.2/lib/tk.rb:2054:in `_invoke': ambiguous option "-colum": must be -column, -columnspan, -in, - ipadx, -ipady, -padx, -pady, -row, -rowspan, or -sticky (RuntimeError) ... ~/debugging $ Intro • Interface • State • Flow • Tradeoffs
  116. ~/debugging $ bin/minesweeper [".w00000", "-column", 0, "-row", 1] [".w00001", "-column",

    1, "-row", 1] ... [".w00197", "-column", 17, "-row", 10] [".w00198", "-column", 18, "-row", 10] [".w00199", "-column", 19, "-row", 10] [".w00200", "-colum", 0, "-row", 0, "-columnspan", 20] ... tk-0.1.2/lib/tk.rb:2054:in `_invoke': ambiguous option "-colum": must be -column, -columnspan, -in, - ipadx, -ipady, -padx, -pady, -row, -rowspan, or -sticky (RuntimeError) ... ~/debugging $ Intro • Interface • State • Flow • Tradeoffs
  117. module MineSweeper class Board attr_reader :width, :mines ... private ...

    def build_status_label(tk_root) initial_status = "#{mines} mines left" column_span = width TkLabel.new(tk_root) do text initial_status grid(colum: 0, row: 0, columnspan: column_span) end end end end Intro • Interface • State • Flow • Tradeoffs
  118. module MineSweeper class Board attr_reader :width, :mines ... private ...

    def build_status_label(tk_root) initial_status = "#{mines} mines left" column_span = width TkLabel.new(tk_root) do text initial_status grid(column: 0, row: 0, columnspan: column_span) end end end end Intro • Interface • State • Flow • Tradeoffs
  119. ~/debugging $ bundle exec gem pristine tk Restoring gems to

    pristine condition... Restored tk-0.1.2 ~/debugging $ Intro • Interface • State • Flow • Tradeoffs
  120. Lessons learned Intro • Interface • State • Flow •

    Tradeoffs
  121. Account for every return type Intro • Interface • State

    • Flow • Tradeoffs
  122. Account for constant and method lookup Intro • Interface •

    State • Flow • Tradeoffs
  123. Take advantage of constant and method introspection Intro • Interface

    • State • Flow • Tradeoffs
  124. Take advantage of the fact that gems aren't bytecode Intro

    • Interface • State • Flow • Tradeoffs
  125. State problems Intro • Interface • State • Flow •

    Tradeoffs
  126. State problems occur when the assumptions you made about the

    current state of the program are incorrect. Intro • Interface • State • Flow • Tradeoffs
  127. How does this value change at this point? Intro •

    Interface • State • Flow • Tradeoffs
  128. How does this value change at this point? What has

    been initialized at this point? Intro • Interface • State • Flow • Tradeoffs
  129. How does this value change at this point? What has

    been initialized at this point? How many objects are allocated in this method? Intro • Interface • State • Flow • Tradeoffs
  130. How does this value change at this point? Intro •

    Interface • State • Flow • Tradeoffs
  131. module MineSweeper class Cell attr_reader :button ... private ... def

    update(board) button.text = display(board).to_s board.update_status end end end Intro • Interface • State • Flow • Tradeoffs
  132. module MineSweeper class Cell attr_reader :button ... private ... def

    update(board) button.text = display(board).to_s board.update_status end end end Intro • Interface • State • Flow • Tradeoffs
  133. module MineSweeper class Cell attr_reader :button ... private ... def

    update(board) ivars = instance_variables.map do |ivar| [ivar, instance_variable_get(ivar)] end puts "STATE=#{ivars.to_h.inspect}" puts "CURRENT=#{button.text} | NEW=#{display(board).to_s}" button.text = display(board).to_s board.update_status end end end Intro • Interface • State • Flow • Tradeoffs
  134. module MineSweeper class Cell attr_reader :button ... private ... def

    update(board) ivars = instance_variables.map do |ivar| [ivar, instance_variable_get(ivar)] end puts "STATE=#{ivars.to_h.inspect}" puts "CURRENT=#{button.text} | NEW=#{display(board).to_s}" button.text = display(board).to_s board.update_status end end end Intro • Interface • State • Flow • Tradeoffs
  135. module MineSweeper class Cell attr_reader :button ... private ... def

    update(board) ivars = instance_variables.map do |ivar| [ivar, instance_variable_get(ivar)] end puts "STATE=#{ivars.to_h.inspect}" puts "CURRENT=#{button.text} | NEW=#{display(board).to_s}" button.text = display(board).to_s board.update_status end end end Intro • Interface • State • Flow • Tradeoffs
  136. module MineSweeper class Cell attr_reader :button ... private ... def

    update(board) ivars = instance_variables.map do |ivar| [ivar, instance_variable_get(ivar)] end puts "STATE=#{ivars.to_h.inspect}" puts "CURRENT=#{button.text} | NEW=#{display(board).to_s}" button.text = display(board).to_s board.update_status end end end Intro • Interface • State • Flow • Tradeoffs
  137. module MineSweeper class Cell attr_reader :button ... private ... def

    update(board) ivars = instance_variables.map do |ivar| [ivar, instance_variable_get(ivar)] end puts "STATE=#{ivars.to_h.inspect}" puts "CURRENT=#{button.text} | NEW=#{display(board).to_s}" button.text = display(board).to_s board.update_status end end end Intro • Interface • State • Flow • Tradeoffs
  138. ~/debugging $ bin/minesweeper STATE={:@button=>#<Tk::Button:0x007fedf282ee10 @path=".w00009">, :@mine_count=>1, :@mine=>false, :@neigh bors=>[8, 10,

    28, 29, 30], :@clicked=>true} CURRENT= | NEW=1 Intro • Interface • State • Flow • Tradeoffs
  139. ~/debugging $ bin/minesweeper STATE={:@button=>#<Tk::Button:0x007fedf282ee10 @path=".w00009">, :@mine_count=>1, :@mine=>false, :@neigh bors=>[8, 10,

    28, 29, 30], :@clicked=>true} CURRENT= | NEW=1 Intro • Interface • State • Flow • Tradeoffs
  140. ~/debugging $ bin/minesweeper STATE={:@button=>#<Tk::Button:0x007fedf282ee10 @path=".w00009">, :@mine_count=>1, :@mine=>false, :@neigh bors=>[8, 10,

    28, 29, 30], :@clicked=>true} CURRENT= | NEW=1 Intro • Interface • State • Flow • Tradeoffs
  141. ~/debugging $ bin/minesweeper STATE={:@button=>#<Tk::Button:0x007fedf282ee10 @path=".w00009">, :@mine_count=>1, :@mine=>false, :@neigh bors=>[8, 10,

    28, 29, 30], :@clicked=>true} CURRENT= | NEW=1 Intro • Interface • State • Flow • Tradeoffs
  142. ~/debugging $ bin/minesweeper STATE={:@button=>#<Tk::Button:0x007fedf282ee10 @path=".w00009">, :@mine_count=>1, :@mine=>false, :@neigh bors=>[8, 10,

    28, 29, 30], :@clicked=>true} CURRENT= | NEW=1 Intro • Interface • State • Flow • Tradeoffs
  143. What has been initialized at this point? Intro • Interface

    • State • Flow • Tradeoffs
  144. ~/debugging $ bin/minesweeper ... board.rb:62:in `build_status_label': undefined method `count' for

    nil:NilClass (NoMethodError) from ... board.rb:17:in `start' from ... minesweeper.rb:7:in `start' from bin/minesweeper:7:in `<main>' Intro • Interface • State • Flow • Tradeoffs
  145. ~/debugging $ bin/minesweeper ... board.rb:62:in `build_status_label': undefined method `count' for

    nil:NilClass (NoMethodError) from ... board.rb:17:in `start' from ... minesweeper.rb:7:in `start' from bin/minesweeper:7:in `<main>' Intro • Interface • State • Flow • Tradeoffs
  146. module MineSweeper class Board attr_reader :width, :cells ... private def

    build_status_label(tk_root) initial_status = "#{cells.count(&:mines?)} mines left" column_span = width TkLabel.new(tk_root) do text initial_status grid(column: 0, row: 0, columnspan: column_span) end end end end Intro • Interface • State • Flow • Tradeoffs
  147. module MineSweeper class Board attr_reader :width, :cells ... private def

    build_status_label(tk_root) initial_status = "#{cells.count(&:mines?)} mines left" column_span = width TkLabel.new(tk_root) do text initial_status grid(column: 0, row: 0, columnspan: column_span) end end end end Intro • Interface • State • Flow • Tradeoffs
  148. module MineSweeper class Board attr_reader :width, :cells ... private def

    build_status_label(tk_root) require 'irb'; binding.irb initial_status = "#{cells.count(&:mines?)} mines left" column_span = width TkLabel.new(tk_root) do text initial_status grid(column: 0, row: 0, columnspan: column_span) end end end end Intro • Interface • State • Flow • Tradeoffs
  149. module MineSweeper class Board attr_reader :width, :cells ... private def

    build_status_label(tk_root) require 'irb'; binding.irb initial_status = "#{cells.count(&:mines?)} mines left" column_span = width TkLabel.new(tk_root) do text initial_status grid(column: 0, row: 0, columnspan: column_span) end end end end Intro • Interface • State • Flow • Tradeoffs
  150. module MineSweeper class Board attr_reader :width, :cells ... private def

    build_status_label(tk_root) require 'irb'; binding.irb initial_status = "#{cells.count(&:mines?)} mines left" column_span = width TkLabel.new(tk_root) do text initial_status grid(column: 0, row: 0, columnspan: column_span) end end end end Intro • Interface • State • Flow • Tradeoffs
  151. ~/debugging $ bin/minesweeper irb(#<MineSweeper::Board:0x007ff858b14c90>):001:0> Intro • Interface • State •

    Flow • Tradeoffs
  152. ~/debugging $ bin/minesweeper irb(#<...>):001:0> Intro • Interface • State •

    Flow • Tradeoffs
  153. ~/debugging $ bin/minesweeper irb(#<...>):001:0> instance_variables => [:@width, :@height, :@mines] irb(#<...>):002:0>

    Intro • Interface • State • Flow • Tradeoffs
  154. ~/debugging $ bin/minesweeper irb(#<...>):001:0> instance_variables => [:@width, :@height, :@mines] irb(#<...>):002:0>

    cells => nil irb(#<...>):003:0> Intro • Interface • State • Flow • Tradeoffs
  155. How many objects are allocated in this method? Intro •

    Interface • State • Flow • Tradeoffs
  156. module MineSweeper class Board attr_reader :mines, :cells, :status def update_status

    status.text = if cells.any? { |cell| cell.mine? && cell.clicked? } cells.each(&:disable) 'You lose!' elsif cells.all? { |cell| !cell.mine? || (cell.mine? && cell.guessed?) } cells.each(&:disable) 'You win!' else count = mines - cells.select(&:guessed?).count count.to_s + ' mines left' end end end end Intro • Interface • State • Flow • Tradeoffs
  157. board = MineSweeper::Board.new(mines: 10) Intro • Interface • State •

    Flow • Tradeoffs
  158. Status = Struct.new(:text) board = MineSweeper::Board.new(mines: 10) board.instance_eval do @status

    = Status.new end Intro • Interface • State • Flow • Tradeoffs
  159. Status = Struct.new(:text) board = MineSweeper::Board.new(mines: 10) board.instance_eval do @status

    = Status.new @cells = [ MineSweeper::Cell.new(mine: true) ] end Intro • Interface • State • Flow • Tradeoffs
  160. Status = Struct.new(:text) Button = Struct.new(:foobar) do def command(*) end

    def bind(*) end end board = MineSweeper::Board.new(mines: 10) board.instance_eval do @status = Status.new @cells = [ MineSweeper::Cell.new(mine: true, button: Button.new) ] end Intro • Interface • State • Flow • Tradeoffs
  161. Status = Struct.new(:text) Button = Struct.new(:foobar) do def command(*) end

    def bind(*) end end board = MineSweeper::Board.new(mines: 10) board.instance_eval do @status = Status.new @cells = [ MineSweeper::Cell.new(mine: true, button: Button.new) ] end before = GC.stat[:total_allocated_objects] 10_000.times { board.update_status } puts GC.stat[:total_allocated_objects] - before Intro • Interface • State • Flow • Tradeoffs
  162. Status = Struct.new(:text) Button = Struct.new(:foobar) do def command(*) end

    def bind(*) end end board = MineSweeper::Board.new(mines: 10) board.instance_eval do @status = Status.new @cells = [ MineSweeper::Cell.new(mine: true, button: Button.new) ] end before = GC.stat[:total_allocated_objects] 10_000.times { board.update_status } puts GC.stat[:total_allocated_objects] - before Intro • Interface • State • Flow • Tradeoffs
  163. Status = Struct.new(:text) Button = Struct.new(:foobar) do def command(*) end

    def bind(*) end end board = MineSweeper::Board.new(mines: 10) board.instance_eval do @status = Status.new @cells = [ MineSweeper::Cell.new(mine: true, button: Button.new) ] end before = GC.stat[:total_allocated_objects] 10_000.times { board.update_status } puts GC.stat[:total_allocated_objects] - before Intro • Interface • State • Flow • Tradeoffs
  164. Status = Struct.new(:text) Button = Struct.new(:foobar) do def command(*) end

    def bind(*) end end board = MineSweeper::Board.new(mines: 10) board.instance_eval do @status = Status.new @cells = [ MineSweeper::Cell.new(mine: true, button: Button.new) ] end before = GC.stat[:total_allocated_objects] 10_000.times { board.update_status } puts GC.stat[:total_allocated_objects] - before Intro • Interface • State • Flow • Tradeoffs
  165. Status = Struct.new(:text) Button = Struct.new(:foobar) do def command(*) end

    def bind(*) end end board = MineSweeper::Board.new(mines: 10) board.instance_eval do @status = Status.new @cells = [ MineSweeper::Cell.new(mine: true, button: Button.new) ] end before = GC.stat[:total_allocated_objects] 10_000.times { board.update_status } puts GC.stat[:total_allocated_objects] - before Intro • Interface • State • Flow • Tradeoffs
  166. Status = Struct.new(:text) Button = Struct.new(:foobar) do def command(*) end

    def bind(*) end end board = MineSweeper::Board.new(mines: 10) board.instance_eval do @status = Status.new @cells = [ MineSweeper::Cell.new(mine: true, button: Button.new) ] end before = GC.stat[:total_allocated_objects] 10_000.times { board.update_status } puts GC.stat[:total_allocated_objects] - before Intro • Interface • State • Flow • Tradeoffs
  167. ~/debugging $ ruby -Ilib -rminesweeper examples/gc.rb 70001 ~/debugging $ Intro

    • Interface • State • Flow • Tradeoffs
  168. module MineSweeper class Board attr_reader :mines, :cells, :status def update_status

    status.text = if cells.any? { |cell| cell.mine? && cell.clicked? } cells.each(&:disable) 'You lose!' elsif cells.all? { |cell| !cell.mine? || (cell.mine? && cell.guessed?) } cells.each(&:disable) 'You win!' else count = mines - cells.select(&:guessed?).count count.to_s + ' mines left' end end end end Intro • Interface • State • Flow • Tradeoffs
  169. module MineSweeper class Board attr_reader :mines, :cells, :status def update_status

    status.text = if cells.any? { |cell| cell.mine? && cell.clicked? } cells.each(&:disable) 'You lose!' elsif cells.all? { |cell| !cell.mine? || (cell.mine? && cell.guessed?) } cells.each(&:disable) 'You win!' else count = mines - cells.select(&:guessed?).count count.to_s + ' mines left' end end end end Intro • Interface • State • Flow • Tradeoffs
  170. module MineSweeper class Board attr_reader :mines, :cells, :status def update_status

    status.text = if cells.any? { |cell| cell.mine? && cell.clicked? } cells.each(&:disable) 'You lose!' elsif cells.all? { |cell| !cell.mine? || (cell.mine? && cell.guessed?) } cells.each(&:disable) 'You win!' else count = mines - cells.count(&:guessed?) count.to_s + ' mines left' end end end end Intro • Interface • State • Flow • Tradeoffs
  171. module MineSweeper class Board attr_reader :mines, :cells, :status def update_status

    status.text = if cells.any? { |cell| cell.mine? && cell.clicked? } cells.each(&:disable) 'You lose!' elsif cells.all? { |cell| !cell.mine? || (cell.mine? && cell.guessed?) } cells.each(&:disable) 'You win!' else count = mines - cells.count(&:guessed?) "#{count} mines left" end end end end Intro • Interface • State • Flow • Tradeoffs
  172. ~/debugging $ ruby -Ilib -rminesweeper examples/gc.rb 50001 ~/debugging $ Intro

    • Interface • State • Flow • Tradeoffs
  173. Lessons learned Intro • Interface • State • Flow •

    Tradeoffs
  174. Take advantage of Ruby's quick feedback loops and flexibility. Intro

    • Interface • State • Flow • Tradeoffs
  175. Your ability to see into your program’s state at any

    point is one of your strongest tools in debugging. Intro • Interface • State • Flow • Tradeoffs
  176. Flow problems Intro • Interface • State • Flow •

    Tradeoffs
  177. Flow problems occur when you don’t know how the ruby

    interpreter got to or left a location in code and its associated state. Intro • Interface • State • Flow • Tradeoffs
  178. How did the interpreter get here? Intro • Interface •

    State • Flow • Tradeoffs
  179. How did the interpreter get here? Where does the interpreter

    go from here? Intro • Interface • State • Flow • Tradeoffs
  180. How did the interpreter get here? Where does the interpreter

    go from here? How did this object get here? Intro • Interface • State • Flow • Tradeoffs
  181. How did the interpreter get here? Where does the interpreter

    go from here? How did this object get here? Where is this object being mutated? Intro • Interface • State • Flow • Tradeoffs
  182. How did the interpreter get here? Where does the interpreter

    go from here? How did this object get here? Where is this object being mutated? When does this instance variable get set? Intro • Interface • State • Flow • Tradeoffs
  183. How did the interpreter get here? Intro • Interface •

    State • Flow • Tradeoffs
  184. module MineSweeper class Board ... def update_status status.text = if

    cells.any? { |cell| cell.mine? && cell.clicked? } cells.each(&:disable) 'You lose!' elsif cells.all? { |cell| !cell.mine? || (cell.mine? && cell.guessed?) } cells.each(&:disable) 'You win!' else count = mines - cells.count(&:guessed?) "#{count} mines left" end end end end Intro • Interface • State • Flow • Tradeoffs
  185. module MineSweeper class Board ... def update_status status.text = if

    cells.any? { |cell| cell.mine? && cell.clicked? } cells.each(&:disable) 'You lose!' elsif cells.all? { |cell| !cell.mine? || (cell.mine? && cell.guessed?) } cells.each(&:disable) 'You win!' else count = mines - cells.count(&:guessed?) "#{count} mines left" end end end end Intro • Interface • State • Flow • Tradeoffs
  186. module MineSweeper class Board ... def update_status status.text = if

    cells.any? { |cell| cell.mine? && cell.clicked? } cells.each(&:disable) 'You lose!' elsif cells.all? { |cell| !cell.mine? || (cell.mine? && cell.guessed?) } puts caller cells.each(&:disable) 'You win!' else count = mines - cells.count(&:guessed?) "#{count} mines left" end end end end Intro • Interface • State • Flow • Tradeoffs
  187. module MineSweeper class Board ... def update_status status.text = if

    cells.any? { |cell| cell.mine? && cell.clicked? } cells.each(&:disable) 'You lose!' elsif cells.all? { |cell| !cell.mine? || (cell.mine? && cell.guessed?) } puts caller cells.each(&:disable) 'You win!' else count = mines - cells.count(&:guessed?) "#{count} mines left" end end end end Intro • Interface • State • Flow • Tradeoffs
  188. ~/debugging $ bin/minesweeper ... cell.rb:54:in `update' ... cell.rb:39:in `toggle_mine' ...

    cell.rb:18:in `block in initialize' ... tk-0.1.2/lib/tk/event.rb:495:in `eval_cmd' ... tk-0.1.2/lib/tk/event.rb:495:in `block in install_bind_for_event_class' ... tk-0.1.2/lib/tk.rb:1456:in `eval_cmd' ... tk-0.1.2/lib/tk.rb:1456:in `cb_eval' ... tk-0.1.2/lib/tk.rb:1403:in `call' ... tk-0.1.2/lib/tk.rb:1607:in `block in callback' ... tk-0.1.2/lib/tk.rb:1606:in `catch' ... tk-0.1.2/lib/tk.rb:1606:in `callback' ... tk-0.1.2/lib/tk.rb:1871:in `mainloop' ... tk-0.1.2/lib/tk.rb:1871:in `mainloop' ... board.rb:19:in `start' ... minesweeper.rb:7:in `start' bin/minesweeper:7:in `<main>' Intro • Interface • State • Flow • Tradeoffs
  189. ~/debugging $ bin/minesweeper ... cell.rb:54:in `update' ... cell.rb:39:in `toggle_mine' ...

    cell.rb:18:in `block in initialize' ... tk-0.1.2/lib/tk/event.rb:495:in `eval_cmd' ... tk-0.1.2/lib/tk/event.rb:495:in `block in install_bind_for_event_class' ... tk-0.1.2/lib/tk.rb:1456:in `eval_cmd' ... tk-0.1.2/lib/tk.rb:1456:in `cb_eval' ... tk-0.1.2/lib/tk.rb:1403:in `call' ... tk-0.1.2/lib/tk.rb:1607:in `block in callback' ... tk-0.1.2/lib/tk.rb:1606:in `catch' ... tk-0.1.2/lib/tk.rb:1606:in `callback' ... tk-0.1.2/lib/tk.rb:1871:in `mainloop' ... tk-0.1.2/lib/tk.rb:1871:in `mainloop' ... board.rb:19:in `start' ... minesweeper.rb:7:in `start' bin/minesweeper:7:in `<main>' Intro • Interface • State • Flow • Tradeoffs
  190. module MineSweeper class Board ... def update_status status.text = if

    cells.any? { |cell| cell.mine? && cell.clicked? } cells.each(&:disable) 'You lose!' elsif cells.all? { |cell| !cell.mine? || (cell.mine? && cell.guessed?) } puts caller cells.each(&:disable) 'You win!' else count = mines - cells.count(&:guessed?) "#{count} mines left" end end end end Intro • Interface • State • Flow • Tradeoffs
  191. module MineSweeper class Board ... def update_status status.text = if

    cells.any? { |cell| cell.mine? && cell.clicked? } cells.each(&:disable) 'You lose!' elsif cells.all? { |cell| !cell.mine? || (cell.mine? && cell.guessed?) } puts caller.grep(Regexp.new(Regexp.quote(__dir__))) cells.each(&:disable) 'You win!' else count = mines - cells.count(&:guessed?) "#{count} mines left" end end end end Intro • Interface • State • Flow • Tradeoffs
  192. module MineSweeper class Board ... def update_status status.text = if

    cells.any? { |cell| cell.mine? && cell.clicked? } cells.each(&:disable) 'You lose!' elsif cells.all? { |cell| !cell.mine? || (cell.mine? && cell.guessed?) } puts caller.grep(Regexp.new(Regexp.quote(__dir__))) cells.each(&:disable) 'You win!' else count = mines - cells.count(&:guessed?) "#{count} mines left" end end end end Intro • Interface • State • Flow • Tradeoffs
  193. module MineSweeper class Board ... def update_status status.text = if

    cells.any? { |cell| cell.mine? && cell.clicked? } cells.each(&:disable) 'You lose!' elsif cells.all? { |cell| !cell.mine? || (cell.mine? && cell.guessed?) } puts caller.grep(Regexp.new(Regexp.quote(__dir__))) cells.each(&:disable) 'You win!' else count = mines - cells.count(&:guessed?) "#{count} mines left" end end end end Intro • Interface • State • Flow • Tradeoffs
  194. ~/debugging $ bin/minesweeper ... cell.rb:54:in `update' ... cell.rb:39:in `toggle_mine' ...

    cell.rb:18:in `block in initialize' ... board.rb:19:in `start' ... minesweeper.rb:7:in `start' Intro • Interface • State • Flow • Tradeoffs
  195. Where does the interpreter go from here? Intro • Interface

    • State • Flow • Tradeoffs
  196. require 'tk' require 'minesweeper/board' require 'minesweeper/cell' module MineSweeper DEFAULTS =

    { width: 20, height: 10, mines: 10 } def self.start(args = {}) Board.new(DEFAULTS.merge(args)).start end end Intro • Interface • State • Flow • Tradeoffs
  197. require 'tk' require 'minesweeper/board' require 'minesweeper/cell' module MineSweeper DEFAULTS =

    { width: 20, height: 10, mines: 10 } def self.start(args = {}) TracePoint.new(:call) do |tp| puts "#{tp.path}:#{tp.lineno}" end.enable Board.new(DEFAULTS.merge(args)).start end end Intro • Interface • State • Flow • Tradeoffs
  198. require 'tk' require 'minesweeper/board' require 'minesweeper/cell' module MineSweeper DEFAULTS =

    { width: 20, height: 10, mines: 10 } def self.start(args = {}) TracePoint.new(:call) do |tp| puts "#{tp.path}:#{tp.lineno}" end.enable Board.new(DEFAULTS.merge(args)).start end end Intro • Interface • State • Flow • Tradeoffs
  199. require 'tk' require 'minesweeper/board' require 'minesweeper/cell' module MineSweeper DEFAULTS =

    { width: 20, height: 10, mines: 10 } def self.start(args = {}) TracePoint.new(:call) do |tp| puts "#{tp.path}:#{tp.lineno}" end.enable Board.new(DEFAULTS.merge(args)).start end end Intro • Interface • State • Flow • Tradeoffs
  200. require 'tk' require 'minesweeper/board' require 'minesweeper/cell' module MineSweeper DEFAULTS =

    { width: 20, height: 10, mines: 10 } def self.start(args = {}) TracePoint.new(:call) do |tp| puts "#{tp.path}:#{tp.lineno}" end.enable Board.new(DEFAULTS.merge(args)).start end end Intro • Interface • State • Flow • Tradeoffs
  201. require 'tk' require 'minesweeper/board' require 'minesweeper/cell' module MineSweeper DEFAULTS =

    { width: 20, height: 10, mines: 10 } def self.start(args = {}) TracePoint.new(:call) do |tp| puts "#{tp.path}:#{tp.lineno}" end.enable Board.new(DEFAULTS.merge(args)).start end end Intro • Interface • State • Flow • Tradeoffs
  202. ~/debugging $ bin/minesweeper ... board.rb:5 ... board.rb:15 ... ruby/2.4.0/rubygems/core_ext/kernel_require.rb:39 ...

    ruby/2.4.0/monitor.rb:185 ... ruby/2.4.0/rubygems.rb:1240 ... ruby/2.4.0/rubygems.rb:1003 ... ruby/2.4.0/rubygems/specification.rb:1299 ... ruby/2.4.0/monitor.rb:197 ... ruby/2.4.0/monitor.rb:247 ... ruby/2.4.0/rubygems/core_ext/kernel_require.rb:39 ... ruby/2.4.0/monitor.rb:185 ... ruby/2.4.0/rubygems.rb:1240 ... ruby/2.4.0/rubygems.rb:1003 ... ruby/2.4.0/rubygems/specification.rb:1299 ... Intro • Interface • State • Flow • Tradeoffs
  203. ~/debugging $ bin/minesweeper ... board.rb:5 ... board.rb:15 ... ruby/2.4.0/rubygems/core_ext/kernel_require.rb:39 ...

    ruby/2.4.0/monitor.rb:185 ... ruby/2.4.0/rubygems.rb:1240 ... ruby/2.4.0/rubygems.rb:1003 ... ruby/2.4.0/rubygems/specification.rb:1299 ... ruby/2.4.0/monitor.rb:197 ... ruby/2.4.0/monitor.rb:247 ... ruby/2.4.0/rubygems/core_ext/kernel_require.rb:39 ... ruby/2.4.0/monitor.rb:185 ... ruby/2.4.0/rubygems.rb:1240 ... ruby/2.4.0/rubygems.rb:1003 ... ruby/2.4.0/rubygems/specification.rb:1299 ... Intro • Interface • State • Flow • Tradeoffs
  204. require 'tk' require 'minesweeper/board' require 'minesweeper/cell' module MineSweeper DEFAULTS =

    { width: 20, height: 10, mines: 10 } def self.start(args = {}) TracePoint.new(:call) do |tp| puts "#{tp.path}:#{tp.lineno}" end.enable Board.new(DEFAULTS.merge(args)).start end end Intro • Interface • State • Flow • Tradeoffs
  205. require 'tk' require 'minesweeper/board' require 'minesweeper/cell' module MineSweeper DEFAULTS =

    { width: 20, height: 10, mines: 10 } def self.start(args = {}) TracePoint.new(:call) do |tp| puts "#{tp.path}:#{tp.lineno}" if tp.path.include?(__dir__) end.enable Board.new(DEFAULTS.merge(args)).start end end Intro • Interface • State • Flow • Tradeoffs
  206. ~/debugging $ bin/minesweeper ... board.rb:5 ... board.rb:15 ... board.rb:40 ...

    board.rb:86 ... board.rb:73 ... board.rb:86 ... board.rb:86 ... board.rb:86 ... board.rb:86 ... board.rb:86 ... board.rb:86 ... board.rb:86 ... board.rb:86 ... cell.rb:10 ... board.rb:86 ... board.rb:73 ... Intro • Interface • State • Flow • Tradeoffs
  207. How did this object get here? Intro • Interface •

    State • Flow • Tradeoffs
  208. module MineSweeper class Cell ... def initialize(args = {}) @button

    = args[:button] @mine_count = args[:mine_count] @mine = args[:mine] @neighbors = args[:neighbors] board = args[:board] @button.command(-> { click(board) }) @button.bind('ButtonRelease-2', -> { toggle_mine(board) }) end end end Intro • Interface • State • Flow • Tradeoffs
  209. module MineSweeper class Cell ... def initialize(args = {}) @button

    = args[:button] @mine_count = args[:mine_count] @mine = args[:mine] @neighbors = args[:neighbors] board = args[:board] @button.command(-> { click(board) }) @button.bind('ButtonRelease-2', -> { toggle_mine(board) }) sourcefile = ObjectSpace.allocation_sourcefile(@button) sourceline = ObjectSpace.allocation_sourceline(@button) puts "#{sourcefile}:#{sourceline}" end end end require 'objspace' ObjectSpace.trace_object_allocations_start Intro • Interface • State • Flow • Tradeoffs
  210. module MineSweeper class Cell ... def initialize(args = {}) @button

    = args[:button] @mine_count = args[:mine_count] @mine = args[:mine] @neighbors = args[:neighbors] board = args[:board] @button.command(-> { click(board) }) @button.bind('ButtonRelease-2', -> { toggle_mine(board) }) sourcefile = ObjectSpace.allocation_sourcefile(@button) sourceline = ObjectSpace.allocation_sourceline(@button) puts "#{sourcefile}:#{sourceline}" end end end require 'objspace' ObjectSpace.trace_object_allocations_start Intro • Interface • State • Flow • Tradeoffs
  211. module MineSweeper class Cell ... def initialize(args = {}) @button

    = args[:button] @mine_count = args[:mine_count] @mine = args[:mine] @neighbors = args[:neighbors] board = args[:board] @button.command(-> { click(board) }) @button.bind('ButtonRelease-2', -> { toggle_mine(board) }) sourcefile = ObjectSpace.allocation_sourcefile(@button) sourceline = ObjectSpace.allocation_sourceline(@button) puts "#{sourcefile}:#{sourceline}" end end end require 'objspace' ObjectSpace.trace_object_allocations_start Intro • Interface • State • Flow • Tradeoffs
  212. module MineSweeper class Cell ... def initialize(args = {}) @button

    = args[:button] @mine_count = args[:mine_count] @mine = args[:mine] @neighbors = args[:neighbors] board = args[:board] @button.command(-> { click(board) }) @button.bind('ButtonRelease-2', -> { toggle_mine(board) }) sourcefile = ObjectSpace.allocation_sourcefile(@button) sourceline = ObjectSpace.allocation_sourceline(@button) puts "#{sourcefile}:#{sourceline}" end end end require 'objspace' ObjectSpace.trace_object_allocations_start Intro • Interface • State • Flow • Tradeoffs
  213. ~/debugging $ bin/minesweeper ... board.rb:48 ... board.rb:48 ... board.rb:48 ...

    board.rb:48 ... board.rb:48 ... board.rb:48 ... Intro • Interface • State • Flow • Tradeoffs
  214. module MineSweeper class Board def build_cells(tk_root) ... height.times.flat_map do |ycoord|

    width.times.map do |xcoord| index = index_for(ycoord, xcoord) neighbors = neighbors_for(ycoord, xcoord) button = TkButton.new(tk_root) do grid(column: xcoord, row: ycoord + 1) end ... end end end end end Intro • Interface • State • Flow • Tradeoffs
  215. module MineSweeper class Board def build_cells(tk_root) ... height.times.flat_map do |ycoord|

    width.times.map do |xcoord| index = index_for(ycoord, xcoord) neighbors = neighbors_for(ycoord, xcoord) button = TkButton.new(tk_root) do grid(column: xcoord, row: ycoord + 1) end ... end end end end end Intro • Interface • State • Flow • Tradeoffs
  216. Where is this object being mutated? Intro • Interface •

    State • Flow • Tradeoffs
  217. Intro • Interface • State • Flow • Tradeoffs

  218. module MineSweeper class Board attr_reader :cells def click(index) cells[index].click(self) end

    def start tk_root = TkRoot.new { title 'MineSweeper' } @cells = build_cells(tk_root) @status = build_status_label(tk_root) Tk.mainloop end ... end end Intro • Interface • State • Flow • Tradeoffs
  219. module MineSweeper class Board attr_reader :cells def click(index) cells[index].click(self) end

    def start tk_root = TkRoot.new { title 'MineSweeper' } @cells = build_cells(tk_root) @status = build_status_label(tk_root) Tk.mainloop end ... end end Intro • Interface • State • Flow • Tradeoffs
  220. module MineSweeper class Board attr_reader :cells def click(index) cells[index].click(self) end

    def start tk_root = TkRoot.new { title 'MineSweeper' } @cells = build_cells(tk_root) @status = build_status_label(tk_root) Tk.mainloop end ... end end Intro • Interface • State • Flow • Tradeoffs
  221. module MineSweeper class Board attr_reader :cells def click(index) cells[index].click(self) end

    def start tk_root = TkRoot.new { title 'MineSweeper' } @cells = build_cells(tk_root) @cells.freeze @status = build_status_label(tk_root) Tk.mainloop end ... end end Intro • Interface • State • Flow • Tradeoffs
  222. Intro • Interface • State • Flow • Tradeoffs

  223. module MineSweeper class Board attr_reader :cells ... def update_status status.text

    = if cells.any? { |cell| cell.mine? && cell.clicked? } cells.each(&:disable) 'You lose!' elsif cells.all? { |cell| !cell.mine? || (cell.mine? && cell.guessed?) } cells.each(&:disable) 'You win!' else cells.select!(&:guessed?) count = mines - cells.count "#{count} mines left" end end end end Intro • Interface • State • Flow • Tradeoffs
  224. module MineSweeper class Board attr_reader :cells ... def update_status status.text

    = if cells.any? { |cell| cell.mine? && cell.clicked? } cells.each(&:disable) 'You lose!' elsif cells.all? { |cell| !cell.mine? || (cell.mine? && cell.guessed?) } cells.each(&:disable) 'You win!' else cells.select!(&:guessed?) count = mines - cells.count "#{count} mines left" end end end end Intro • Interface • State • Flow • Tradeoffs
  225. module MineSweeper class Board attr_reader :cells ... def update_status status.text

    = if cells.any? { |cell| cell.mine? && cell.clicked? } cells.each(&:disable) 'You lose!' elsif cells.all? { |cell| !cell.mine? || (cell.mine? && cell.guessed?) } cells.each(&:disable) 'You win!' else count = mines - cells.count(&:guessed?) "#{count} mines left" end end end end Intro • Interface • State • Flow • Tradeoffs
  226. module MineSweeper class Board attr_reader :cells ... def update_status status.text

    = if cells.any? { |cell| cell.mine? && cell.clicked? } cells.each(&:disable) 'You lose!' elsif cells.all? { |cell| !cell.mine? || (cell.mine? && cell.guessed?) } cells.each(&:disable) 'You win!' else count = mines - cells.count(&:guessed?) "#{count} mines left" end end end end Intro • Interface • State • Flow • Tradeoffs
  227. When does this instance variable get set? Intro • Interface

    • State • Flow • Tradeoffs
  228. ~/debugging $ bin/minesweeper ... board.rb:62:in `build_status_label': undefined method `count' for

    nil:NilClass (NoMethodError) from ... board.rb:17:in `start' from ... minesweeper.rb:7:in `start' from bin/minesweeper:7:in `<main>' Intro • Interface • State • Flow • Tradeoffs
  229. module MineSweeper class Board attr_reader :cells def build_status_label(tk_root) require 'irb';

    binding.irb initial_status = "#{cells.count(&:mines?)} mines left" column_span = width TkLabel.new(tk_root) do text initial_status grid(column: 0, row: 0, columnspan: column_span) end end end end Intro • Interface • State • Flow • Tradeoffs
  230. module MineSweeper class Board attr_reader :cells def build_status_label(tk_root) initial_status =

    "#{cells.count(&:mines?)} mines left" column_span = width TkLabel.new(tk_root) do text initial_status grid(column: 0, row: 0, columnspan: column_span) end end end end Intro • Interface • State • Flow • Tradeoffs
  231. module MineSweeper class Board attr_reader :cells def build_status_label(tk_root) @cells =

    [] initial_status = "#{cells.count(&:mines?)} mines left" column_span = width TkLabel.new(tk_root) do text initial_status grid(column: 0, row: 0, columnspan: column_span) end end end end Intro • Interface • State • Flow • Tradeoffs
  232. Intro • Interface • State • Flow • Tradeoffs

  233. module MineSweeper class Board attr_reader :cells def build_status_label(tk_root) @cells =

    [] initial_status = "#{cells.count(&:mines?)} mines left" column_span = width TkLabel.new(tk_root) do text initial_status grid(column: 0, row: 0, columnspan: column_span) end end end end Intro • Interface • State • Flow • Tradeoffs
  234. module MineSweeper class Board attr_reader :cells def build_status_label(tk_root) cells =

    @cells = [] TracePoint.new(:line) do |tp| if (tp.path == __FILE__ && tp.binding.eval('self.class') == self.class && tp.binding.eval('@cells') != cells) puts "#{tp.path}:#{tp.lineno - 1}" exit end end.enable initial_status = "#{cells.count(&:mines?)} mines left” ... end end end Intro • Interface • State • Flow • Tradeoffs
  235. module MineSweeper class Board attr_reader :cells def build_status_label(tk_root) cells =

    @cells = [] TracePoint.new(:line) do |tp| if (tp.path == __FILE__ && tp.binding.eval('self.class') == self.class && tp.binding.eval('@cells') != cells) puts "#{tp.path}:#{tp.lineno - 1}" exit end end.enable initial_status = "#{cells.count(&:mines?)} mines left” ... end end end Intro • Interface • State • Flow • Tradeoffs
  236. module MineSweeper class Board attr_reader :cells def build_status_label(tk_root) cells =

    @cells = [] TracePoint.new(:line) do |tp| if (tp.path == __FILE__ && tp.binding.eval('self.class') == self.class && tp.binding.eval('@cells') != cells) puts "#{tp.path}:#{tp.lineno - 1}" exit end end.enable initial_status = "#{cells.count(&:mines?)} mines left” ... end end end Intro • Interface • State • Flow • Tradeoffs
  237. module MineSweeper class Board attr_reader :cells def build_status_label(tk_root) cells =

    @cells = [] TracePoint.new(:line) do |tp| if (tp.path == __FILE__ && tp.binding.eval('self.class') == self.class && tp.binding.eval('@cells') != cells) puts "#{tp.path}:#{tp.lineno - 1}" exit end end.enable initial_status = "#{cells.count(&:mines?)} mines left” ... end end end Intro • Interface • State • Flow • Tradeoffs
  238. module MineSweeper class Board attr_reader :cells def build_status_label(tk_root) cells =

    @cells = [] TracePoint.new(:line) do |tp| if (tp.path == __FILE__ && tp.binding.eval('self.class') == self.class && tp.binding.eval('@cells') != cells) puts "#{tp.path}:#{tp.lineno - 1}" exit end end.enable initial_status = "#{cells.count(&:mines?)} mines left” ... end end end Intro • Interface • State • Flow • Tradeoffs
  239. module MineSweeper class Board attr_reader :cells def build_status_label(tk_root) cells =

    @cells = [] TracePoint.new(:line) do |tp| if (tp.path == __FILE__ && tp.binding.eval('self.class') == self.class && tp.binding.eval('@cells') != cells) puts "#{tp.path}:#{tp.lineno - 1}" exit end end.enable initial_status = "#{cells.count(&:mines?)} mines left” ... end end end Intro • Interface • State • Flow • Tradeoffs
  240. ~/debugging $ bin/minesweeper ... board.rb:18 ~/debugging $ Intro • Interface

    • State • Flow • Tradeoffs
  241. module MineSweeper class Board attr_reader :cells, :status ... def start

    tk_root = TkRoot.new { title 'MineSweeper' } @status = build_status_label(tk_root) @cells = build_cells(tk_root) Tk.mainloop end end end Intro • Interface • State • Flow • Tradeoffs
  242. module MineSweeper class Board attr_reader :cells, :status ... def start

    tk_root = TkRoot.new { title 'MineSweeper' } @status = build_status_label(tk_root) @cells = build_cells(tk_root) Tk.mainloop end end end Intro • Interface • State • Flow • Tradeoffs
  243. module MineSweeper class Board attr_reader :cells, :status ... def start

    tk_root = TkRoot.new { title 'MineSweeper' } @status = build_status_label(tk_root) @cells = build_cells(tk_root) Tk.mainloop end end end Intro • Interface • State • Flow • Tradeoffs
  244. module MineSweeper class Board attr_reader :cells, :status ... def start

    tk_root = TkRoot.new { title 'MineSweeper' } @cells = build_cells(tk_root) @status = build_status_label(tk_root) Tk.mainloop end end end Intro • Interface • State • Flow • Tradeoffs
  245. Lessons learned Intro • Interface • State • Flow •

    Tradeoffs
  246. For large projects, ruby requires effort to follow the flow.

    Intro • Interface • State • Flow • Tradeoffs
  247. For new projects, stack traces are easy to generate and

    follow. Intro • Interface • State • Flow • Tradeoffs
  248. Ruby's tradeoffs Intro • Interface • State • Flow •

    Tradeoffs
  249. Compiler niceties for introspection Intro • Interface • State •

    Flow • Tradeoffs
  250. Compiler niceties for introspection Out of the box speed for

    flexibility Intro • Interface • State • Flow • Tradeoffs
  251. Compiler niceties for introspection Out of the box speed for

    flexibility Maintenance cost for start-up cost Intro • Interface • State • Flow • Tradeoffs
  252. Interface Out of the box speed for flexibility Maintenance cost

    for start-up cost Intro • Interface • State • Flow • Tradeoffs
  253. Interface State Maintenance cost for start-up cost Intro • Interface

    • State • Flow • Tradeoffs
  254. Interface State Flow Intro • Interface • State • Flow

    • Tradeoffs
  255. Practical Debugging github.com/kddeisz/debugging Kevin Deisz @kddeisz

  256. A Comprehensive Guide to Debugging Rails
 https://www.jackkinsella.ie/articles/a-comprehensive-guide-to-debugging-rails Debugging memory leaks

    in Ruby
 https://samsaffron.com/archive/2015/03/31/debugging-memory-leaks-in-ruby Debugging Rails Applications
 http://guides.rubyonrails.org/debugging_rails_applications.html Debugging Rails Applications in Development
 http://nofail.de/2013/10/debugging-rails-applications-in-development/ I am a puts debuggerer
 https://tenderlovemaking.com/2016/02/05/i-am-a-puts-debuggerer.html Ruby Debugging Magic Cheat Sheet
 http://www.schneems.com/2016/01/25/ruby-debugging-magic-cheat-sheet.html There’s More to Ruby Debugging Than puts()
 https://engineering.shopify.com/17489080-theres-more-to-ruby-debugging- than-puts References