Source-Diving for Fun and Profit

Source-Diving for Fun and Profit

Ever spent hours pouring over a gem's documentation trying to figure out how to make it work? Dug through dozens of blog posts trying to understand why a library's not working? Well what if I promised you an end to all that?!

Well, ok, I'd be lying. But maybe I can save you some hair-pulling some of the time! Let me introduce you to the joys of Reading the Code. Maybe it seems obvious to you, but one of the biggest leaps I made as a ruby dev was really getting comfortable jumping into a gem's source as a debugging technique.

In an effort to get you over that hump earlier than I did, let's talk tips and tricks for getting in and out of a library's codebase as efficiently as possible. It won't solve every problem, but sometimes 5 minutes on GitHub will save you hours on StackOverflow.

B5c79b428fca86c70fd59d1c27a980d8?s=128

Kevin Kuchta

November 18, 2019
Tweet

Transcript

  1. 15.
  2. 16.
  3. 17.
  4. 18.
  5. 19.
  6. 20.
  7. 21.
  8. 22.
  9. 23.
  10. 24.
  11. 25.
  12. 26.
  13. 28.
  14. 30.
  15. 39.

    @kkuchta Find the biggest $ find lib -type f |

    xargs wc -l | sort -r | head Find in ./lib Sort by line count Get the line count for each Just show the top 10
  16. 41.

    @kkuchta Take me to your leader • Entry points •

    Files with lots of content • Biggest files
  17. 47.

    @kkuchta Ag is cool $ ag 'something' $ ag -i

    'something' $ ag 'some.*thing'
  18. 48.

    @kkuchta Ag is cool $ ag 'something' $ ag -i

    'something' $ ag 'some.*thing' $ ag --ruby 'something'
  19. 63.

    @kkuchta • def stream • PaginatedListing • def _stream •

    dead end • Subreddit • def listing • define_method(:comments) Iterative Searching session.subreddit('all').comments.stream
  20. 66.
  21. 67.

    @kkuchta Iterative Searching Gotcha: metaprogramming def add_one(x) x + 1

    end define_method("add_one") do |x| x + 1 end method_name = "add" + "_" + "one" define_method(method_name) do |x| x + 1 end
  22. 76.

    @kkuchta Searching: Referral • Search by full name, exact name,

    partial name • Filter by type (constant, instance_method, local var, etc) • Regexes everywhere • Sort by anything • Customize output • Filter by scope • github.com/testdouble/referral
  23. 80.

    @kkuchta 3. Trace It puts "x = #{x}, y =

    #{y}" puts "some_gem = #{some_gem.inspect}" result = some_gem.do_thing(x, y, true) puts "result = #{result}"
  24. 85.

    @kkuchta 3. Trace It /Users/kevin/.rvm/gems/ruby-2.0.0-p648 actionmailer-4.0.4 commander-4.1.3 jquery-rails-3.1.1 actionpack-4.0.4 diff-lcs-1.2.5

    json-1.8.1 activemodel-4.0.4 directory_watcher-1.4.1 kgio-2.9.2 activerecord-4.0.4 erubis-2.7.0 kramdown-0.14.2 activerecord-deprecated_finders-1.0.3 execjs-2.2.1 liquid-2.5.0 activesupport-4.0.4 fast-stemmer-1.0.2 listen-1.0.3 arel-4.0.2 ffi-1.8.1 lumberjack-1.0.3 builder-3.1.4 formatador-0.2.4 mail-2.5.4 byebug-9.0.6 guard-1.8.0 maruku-0.6.1 classifier-1.3.3 guard-haml-0.5 method_source-0.8.1 coderay-1.0.9 haml-3.1.4 method_source-0.9.2 coderay-1.1.2 highline-1.6.18 mime-types-1.25.1 coffee-rails-4.0.1 hike-1.2.3 minitest-4.7.5 coffee-script-2.3.0 i18n-0.6.11 multi_json-1.10.1 coffee-script-source-1.7.1 jbuilder-1.5.3 pg-0.17.1 $ echo $GEM_HOME $ ls $GEM_HOME/gems/
  25. 92.

    @kkuchta 3. Trace It /Users/kevin/.rvm/gems/ruby-2.6.0/gems/net-sftp-2.1.2/lib/net/sftp/operations/ file.rb:85:in `gets': wrong number of

    arguments (given 2, expected 0..1) stream = sftp.file.open(filepath) csv = CSV.new(stream).to_a
  26. 94.
  27. 95.

    @kkuchta 3. Trace It 2. /Users/kevin/.rvm/gems/ruby-2.6.0/gems/net-sftp-2.1.2/lib/net/sftp/operations/ file.rb:85:in `gets': wrong number

    of arguments (given 2, expected 0..1) stream = sftp.file.open(filepath) csv = CSV.new(stream).to_a 1. from /Users/kevin/.rvm/rubies/ruby-2.6.0/lib/ruby/2.6.0/csv/parser.rb:392:in `resolve_row_separator'
  28. 96.

    @kkuchta 3. Trace It stream = sftp.file.open(filepath) csv = CSV.new(stream).to_a

    from /Users/kevin/.rvm/rubies/ruby-2.6.0/lib/ruby/2.6.0/csv/parser.rb:392:in `resolve_row_separator' 392
  29. 98.

    @kkuchta 3. Trace It stream = sftp.file.open(filepath) csv = CSV.new(stream).to_a

    from /Users/kevin/.rvm/rubies/ruby-2.6.0/lib/ruby/2.6.0/csv/parser.rb:392:in `resolve_row_separator'
  30. 99.

    @kkuchta 3. Trace It stream = sftp.file.open(filepath) csv = CSV.new(stream).to_a

    /Users/kevin/.rvm/gems/ruby-2.6.0/gems/net-sftp-2.1.2/lib/net/sftp/operations/ file.rb:85:in `gets': wrong number of arguments (given 2, expected 0..1) from /Users/kevin/.rvm/rubies/ruby-2.6.0/lib/ruby/2.6.0/csv/parser.rb:392:in `resolve_row_separator'
  31. 100.

    @kkuchta 3. Trace It stream = sftp.file.open(filepath) csv = CSV.new(stream).to_a

    /Users/kevin/.rvm/gems/ruby-2.6.0/gems/net-sftp-2.1.2/lib/net/sftp/operations/ file.rb:85:in `gets': wrong number of arguments (given 2, expected 0..1)
  32. 104.

    @kkuchta 3. Trace It • Print debugging • Quick, easy

    • Rerun to make changes • Debugger debugging • Steeper learning curve • Exploratory tracing
  33. 105.

    @kkuchta 3. Trace It def first(x) x = x +

    3 if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end first(7)
  34. 106.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It
  35. 107.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It
  36. 108.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It
  37. 109.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It pry(main)>
  38. 110.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It pry(main)> step
  39. 111.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It pry(main)> step
  40. 112.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It pry(main)> step
  41. 113.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It pry(main)> step pry(main)>
  42. 114.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It pry(main)> step pry(main)> x
  43. 115.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It pry(main)> step pry(main)> x => 7 pry(main)>
  44. 116.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It pry(main)> step pry(main)> x => 7 pry(main)> next pry(main)>
  45. 117.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It pry(main)> step pry(main)> x => 7 pry(main)> next pry(main)> x > 5 => true pry(main)>
  46. 118.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It pry(main)> step pry(main)> x => 7 pry(main)> next pry(main)> x > 5 => true pry(main)> next pry(main)>
  47. 119.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It pry(main)> step pry(main)> x => 7 pry(main)> next pry(main)> x > 5 => true pry(main)> next pry(main)> step pry(main)>
  48. 120.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It pry(main)> step pry(main)> x => 7 pry(main)> next pry(main)> x > 5 => true pry(main)> next pry(main)> step pry(main)> y => 10 pry(main)>
  49. 121.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It pry(main)> step pry(main)> x => 7 pry(main)> next pry(main)> x > 5 => true pry(main)> next pry(main)> step pry(main)> y => 10 pry(main)> finish
  50. 122.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It pry(main)> step pry(main)> x => 7 pry(main)> next pry(main)> x > 5 => true pry(main)> next pry(main)> step pry(main)> y => 10 pry(main)> finish
  51. 123.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It
  52. 124.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It next => Go to the next line
  53. 125.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It next => Go to the next line step => step into this function
  54. 126.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It next => Go to the next line step => step into this function finish => finish this function
  55. 127.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It next => Go to the next line step => step into this function finish => finish this function
  56. 128.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It next => Go to the next line step => step into this function finish => finish this function continue => stop debugging + move on
  57. 129.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It next => Go to the next line step => step into this function finish => finish this function whereami => print the code around you continue => stop debugging + move on
  58. 130.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It next => Go to the next line step => step into this function finish => finish this function whereami => print the code around you backtrace => print the current stack trace continue => stop debugging + move on
  59. 131.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It next => Go to the next line step => step into this function finish => finish this function whereami => print the code around you backtrace => print the current stack trace !!! => exit the whole program continue => stop debugging + move on
  60. 132.

    @kkuchta require 'pry-byebug' def first(x) x = x + 3

    if x > 5 x = second(x) end x end def second(y) y = y + 4 while y > 1 y = y / 2.0 end y end binding.pry first(7) 3. Trace It next => Go to the next line step => step into this function finish => finish this function whereami => print the code around you backtrace => print the current stack trace !!! => exit the whole program continue => stop debugging + move on help => exit the whole program
  61. 136.

    @kkuchta Debuggers are great • Debugger: pry-byebug • Rebugger: Kevin

    Kuchta • Start in your code and step into gem code
  62. 149.

    @kkuchta Tracing: Forwards Starting from: result = first(c) def first(x)

    second(x - 3) end def second(y) third(y * 2) end
  63. 150.

    @kkuchta Tracing: Forwards Starting from: result = first(c) def first(x)

    second(x - 3) end def second(y) third(y * 2) end def third(z) z / z + 1 end
  64. 151.

    @kkuchta Tracing: Forwards • Debuggers • Step into each new

    level • Print debugging • See how far the code is getting in each level • Good when you have a function call that's not doing what you'd expect
  65. 158.

    @kkuchta • Start at your point of interest • Good

    for exceptions Tracing: Backwards
  66. 159.

    @kkuchta • Start at your point of interest • Good

    for exceptions • Text search / referral Tracing: Backwards
  67. 164.

    @kkuchta • Searching for references • Check current file •

    Check includes / extends • Metaprogramming Tracing
  68. 165.

    @kkuchta • Searching for references • Check current file •

    Check includes / extends • Metaprogramming • Use ag to search the whole codebase Tracing
  69. 166.

    @kkuchta • Searching for references • Check current file •

    Check includes / extends • Metaprogramming • Use ag to search the whole codebase • Check gem files Tracing
  70. 177.
  71. 178.

    @kkuchta The Kuchta 3ish-step Process©™® 1. Take me to your

    leader • Entry points • Big files 2. Text search • Ag • Repeated searching
  72. 179.

    @kkuchta The Kuchta 3ish-step Process©™® 1. Take me to your

    leader • Entry points • Big files 2. Text search • Ag • Repeated searching 3. Trace • Puts statements • Debugger