Debugging is an ability every developer should have learned. It can be quite tricky to understand certain edge cases and sometime you have to think outside of the box provider…
Especially with Ruby it can be quite confusing which method you actually call since there is inheritance, monkey patching, dynamic method definition, the evil but omnipresent method_missing
and (possibly also dynamically) included modules.
The rails console which is a rather simple REPL called IRB will help you a lot when it comes to play around with your code to reproduce, locate and understand bugs. And if you like IRB you will love Pry! Just check the Wiki or this brief overview. Or just install it, run it and type help
. You might also want to read A collection of development gems.
Since we're all using Pry now let's get to the tips!
So what do we have here…?
First we need accessors to all the methods and variables an object has. These should be the most interesting ones:
# Returns array of instance methods for the given class.
User.instance_methods # => [:nil?, :===, :=~, :!~, :eql?, …]
@user.class.instance_methods
# If you only want the methods of this particular class
# (without inherited methods) pass false:
User.instance_methods(false)
# To get a list of defined instance variables
@user.instance_variables # => [:@name]
# To get the actual value of the variable (if there is no getter method)
@user.instance_variable_get(:@name) # => "Horst"
# With Pry you could also "cd" into the object:
pry(main)> cd @user
pry(#<User>):1> @name
=> "Horst"
Since you can define methods on individual instances the object.class.instance_methods
might not yield correct results. Fortunately you can ask an object for a list of it's #methods
:
foo = {}
def foo.bar?
"sure it's bar, what else should it be?"
end
Hash.instance_methods.grep /^bar/ # => []
foo.methods.grep /^bar/ # => [:bar?]
Don't forget about grep!
You've seen it in the last tip, the Enumerable#grep method can be very handy when dealing with large arrays. Here are some examples for method lists:
# Show all methods ending with a question mark (they can only occur at the end)
User.instance_methods.grep /\?/
# Same goes for bang methods
User.instance_methods.grep /!/
# Show the common to_ casting methods
User.instance_methods.grep /^to_/
Which method do I really call?
Due to the nature of Ruby to be very flexible you sometimes can't really tell which method you will actually call. But fortunately Ruby knows it:
@user.method(:name) # => #<Method: User#name>
@user.method(:puts) # => #<Method: User(Kernel)#puts>
It also keeps track of where the methods came from:
@user.method(:name).source_location # => ["test.rb", 4]
# Doesn't work with methods implemented in C
@user.method(:puts).source_location # => nil
Note that the filename will sometimes be relative to the current directory of your ruby process (usually from where you've called the script) but in the case of Rails you propably will get the full path.
With Pry at your hand you can even show the method definition, in this case it will reveal that this method was dynamically defined through attr_accessor
.
[2] pry(main)> show-method @user.name
From: test.rb @ line 4:
Owner: User
Visibility: public
Number of lines: 1
attr_accessor :name
If you've defined your favorite text editor you can directly jump to the file and line by using the edit command:
edit @user.name
Use tests
When you can, add one or better multiple failing tests. This not only helps you finding the error it also helps you to prevent this error from happening again in future code updates.
OS X only: Let your code talk
This is a rather odd one. Sometimes I don't really want breakpoints, I just want to know in which order callbacks are called or how the code flows. On Mac OS X there is a build-in terminal command say
which reads text you pass as an argument.
So when I'm sitting there late at night, trying to figuring something out and my eyes need a short break I tend to add some of these lines instead of using text ouput (which is hard to spot in the rails log):
`say Processing item #{item.id}`
`say Found #{matches.count} matches`
before_filter { `say You should go to bed now` }
Generally…
…for those who are struggling with debugging in general:
Reproduce the problem. In the best case you would have a consistently failing test. But bugs can be more nasty than that. If you're lucky you can sporadically trigger the error but sometimes it just flashes up randomly on your bug tracker and you have no clue whatsoever. You need to narrow it down and inspect every little detail in the suspected part of your code. Even if it appears totally plausible, double check it. Don't forget the "out of the box" thing. Let others take a look, maybe they will spot it at a glance.
Understand the problem. If you can reproduce the problem it is usually easy to imagine possible reasons for it. You can also try to find the error by revisiting and rethinking the code but this can get complicated.
Locate the problem and fix it. Sometimes you will need a workaround to fix things especially if the problem originates from outside your control. Hopefully the tips in this article will help you with this step.
Do you have special tricks or techniques when dealing with bugs? Let us know!