Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

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.

Kevin Newton

April 25, 2017
Tweet

More Decks by Kevin Newton

Other Decks in Programming

Transcript

  1. ~/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
  2. 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
  3. The ruby standard library has every tool you need to

    debug effectively. Intro • Interface • State • Flow • Tradeoffs
  4. Interface problems occur when you don’t understand the dependent structure

    of methods or constants. Intro • Interface • State • Flow • Tradeoffs
  5. Why is this thing nil? Why can’t I call the

    method I want? Intro • Interface • State • Flow • Tradeoffs
  6. 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
  7. 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
  8. 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
  9. 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
  10. ~/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
  11. ~/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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. Why can’t I call the method I want? Intro •

    Interface • State • Flow • Tradeoffs
  24. ~/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
  25. ~/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
  26. ~/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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. ~/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
  38. 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
  39. 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
  40. 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
  41. 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
  42. ~/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
  43. ~/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
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. What can this object see and do? Intro • Interface

    • State • Flow • Tradeoffs
  50. 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
  51. 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
  52. 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
  53. 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
  54. 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
  55. 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
  56. 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
  57. ~/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
  58. ~/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
  59. ~/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
  60. ~/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
  61. ~/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
  62. ~/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
  63. ~/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
  64. ~/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
  65. Oh god oh god what is this gem even doing?

    Intro • Interface • State • Flow • Tradeoffs
  66. ~/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
  67. ~/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
  68. ~/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
  69. 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
  70. 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
  71. ~/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
  72. ~/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
  73. ~/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
  74. ~/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
  75. 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
  76. 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
  77. ~/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
  78. ~/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
  79. ~/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
  80. ~/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
  81. 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
  82. 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
  83. 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
  84. 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
  85. ~/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
  86. ~/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
  87. 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
  88. 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
  89. ~/debugging $ bundle exec gem pristine tk Restoring gems to

    pristine condition... Restored tk-0.1.2 ~/debugging $ Intro • Interface • State • Flow • Tradeoffs
  90. Take advantage of the fact that gems aren't bytecode Intro

    • Interface • State • Flow • Tradeoffs
  91. State problems occur when the assumptions you made about the

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

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

    been initialized at this point? Intro • Interface • State • Flow • Tradeoffs
  94. 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
  95. How does this value change at this point? Intro •

    Interface • State • Flow • Tradeoffs
  96. 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
  97. 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
  98. 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
  99. 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
  100. 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
  101. 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
  102. 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
  103. ~/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
  104. ~/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
  105. ~/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
  106. ~/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
  107. ~/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
  108. ~/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
  109. ~/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
  110. 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
  111. 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
  112. 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
  113. 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
  114. 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
  115. ~/debugging $ bin/minesweeper irb(#<...>):001:0> instance_variables => [:@width, :@height, :@mines] irb(#<...>):002:0>

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

    Interface • State • Flow • Tradeoffs
  117. 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
  118. Status = Struct.new(:text) board = MineSweeper::Board.new(mines: 10) board.instance_eval do @status

    = Status.new end Intro • Interface • State • Flow • Tradeoffs
  119. 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
  120. 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
  121. 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
  122. 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
  123. 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
  124. 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
  125. 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
  126. 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
  127. 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
  128. 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
  129. 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
  130. 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
  131. Take advantage of Ruby's quick feedback loops and flexibility. Intro

    • Interface • State • Flow • Tradeoffs
  132. 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
  133. 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
  134. How did the interpreter get here? Where does the interpreter

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

    go from here? How did this object get here? Intro • Interface • State • Flow • Tradeoffs
  136. 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
  137. 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
  138. 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
  139. 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
  140. 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
  141. 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
  142. ~/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
  143. ~/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
  144. 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
  145. 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
  146. 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
  147. 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
  148. ~/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
  149. 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
  150. 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
  151. 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
  152. 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
  153. 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
  154. 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
  155. ~/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
  156. ~/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
  157. 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
  158. 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
  159. ~/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
  160. 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
  161. 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
  162. 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
  163. 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
  164. 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
  165. ~/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
  166. 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
  167. 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
  168. 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
  169. 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
  170. 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
  171. 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
  172. 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
  173. 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
  174. 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
  175. 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
  176. ~/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
  177. 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
  178. 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
  179. 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
  180. 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
  181. 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
  182. 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
  183. 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
  184. 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
  185. 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
  186. 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
  187. 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
  188. 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
  189. 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
  190. 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
  191. For large projects, ruby requires effort to follow the flow.

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

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

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

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

    for start-up cost Intro • Interface • State • Flow • Tradeoffs
  196. 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