How often does your program do exactly what you want the first time around?
Many times our programs dont’t work like we expect, so we have to use the art of debugging ruby to help us finding out why.
You may be familiar with the following error message:
undefined method 'some_method' for nil:NilClass
This means that a nil value managed to find it’s way into our code.
Using the techniques discussed in this article you will learn how to deal with this issue and similar problems!
Understanding Errors & Stack Traces
When you are getting an error from the Ruby interpreter or your program is not doing what it should be doing then it’s time to put on your debugging hat.
If the problem is that your program is crashing, it is important to pay attention to the error message, which usually will contain clues of what’s going wrong.
Here is an example:
def method1 method2 end def method2 puts invalid_variable end method1
Running this code will give you the following error:
/tmp/stack.rb:6:in 'method2': undefined local variable or method 'invalid_variable' for main:Object (NameError) from /tmp/stack.rb:2:in 'method1' from /tmp/stack.rb:9:in ''
This is what is known as a stack trace.
Let’s analyze it together!
We start with the line on the top.
This is where the actual error occurred, but it doesn’t mean the error condition originated here.
However, it is a good point to start our investigation.
Here’s the deal:
Text | Description |
---|---|
/tmp/stack.rb:6 | File and line number |
in `method2‘ | Method name |
undefined local variable or method ‘invalid_variable‘ | Error message |
main:Object | Class name |
(NameError) | Exception name |
As you can see the error is not that intimidating when broken down in this way.
By the way, you can find a list of exceptions here.
Now:
Every line in the stack trace below the first one tells you how the code got here.
It’s basically a method chain, if you keep going down you should eventually find the main method of your app.
Here is a general algorithm for dealing with a stack trace:
- Read the top line of the stack trace
- If the file is part of your project: open the faulting file on the indicated line number. If it isn’t, keep going down the stack trace until you find the first reference to a file you recognize
- See if anything obvious jumps out to you and fix it (look for things mentioned on the error message)
- If that doesn’t help then you will need to find more information, like the values of the affected variables.
Debugging Ruby
The most basic (which doesn’t necessarily mean bad) debugging technique that you are probably familiar with is just dumping the values of the suspected variables.
In Ruby you can do that using puts or p.
Using p is equivalent to saying puts variable.inspect, and it’s useful for looking at objects.
Example:
Book = Struct.new(:title) def find_book(title) books = [] books << Book.new('Eloquent Ruby') books.find { |b| b.title == title } end book = find_book('Eloquent Ruby') p book # This will print our book object book = find_book('POODR') p book # This will print nil book.name # Guess what happens next!
Digging Deeper with Pry
When you have many variables to check, adding puts
everywhere might not be very practical.
In that case you should try pry.
Using pry you can make your code stop at a specific line of code (also known as a breakpoint) and it will drop you into an irb-like environment, where you can evaluate ruby code in the context of your project, or execute one of the many useful pry commands.
Using pry is really easy:
All you have to do is drop binding.pry
where you would like to install a pry breakpoint.
You will also need to require pry into your project (require 'pry').
If you just want to do it temporarily then you can call your ruby script like this:
ruby -rpry app.rb
That won't be very helpful for a rails app, so you may want to add pry to your Gemfile.
What I like to do is to have a macro/snippet on my editor that already includes the require in the same line than the breakpoint, so when I delete it I will be deleting both things.
This is what you will see when you are dropped on a pry session:
If you want to completely quit a pry session you can type exit!, if you do a regular exit it runs your program until the next breakpoint.
The power of pry doesn't end here. For example, you can use the ls
command to see what methods and instance variables an object has access to.
Don't forget to run the help command to get a listing of all the goodies!
Another Ruby Debugger: Byebug
Byebug can act as a pry replacement or as a gdb-like debugger for Ruby.
If you want to use it for the former then you just drop byebug instead of binding.pry
where you want your code to stop. One of the cons of using Byebug over pry is that it doesn't provide syntax highlighting.
Let's see how you can set breakpoints and debug you code inside byebug!
Usually you would call the help command, but in this case it is lacking a bit on information:
So you will have to consult the documentation.
You can see how using the command break and a line number you can set your breakpoints.
To get a list of breakpoints you can use info breakpoint.
Once your breakpoints are set, you can move through the program execution using the following commands:
- step (advance one instruction, stepping into method calls)
- next (advance one instruction, doesn't get inside methods)
- continue (run until the end or next breakpoint)
If you type enter without any command it with just repeat the last one, this is very useful when walking through your code.
When All Else Fails
Make sure to take a break when you have put a good amount of time in and can't see the solution, when you come back with fresh eyes you will realize the solution was in front of you. You can also try explaining the problem to someone else.
Some times you are not sure where the problem is, when this happens you still have plenty of options.
For example, you may want to comment blocks of code to try and isolate the issue.
If the issue disappears then you can uncomment a portion of the code that you just commented.
This is a very low-tech solution, but it might be exactly what you need.
If you got this far and nothing seems to help:
It's time to pull out the big guns.
Here are some system tools that you will often find to be helpful.
One of these tools is Wireshark, which will let you inspect network traffic.
If you are dealing with SSL-encrypted traffic a mitm (Man in the middle) proxy like mitmproxy might be able to help you.
You can also try curl to initiate HTTP connections from your terminal, which may help you debug invalid server responses.
Another tool that is useful to be familiar with is strace (linux only).
Strace will show you all the system calls that your app is doing.
You can filter for specific system calls using the -e option. A more modern alternative to strace is sysdig.
Warning! You may want to avoid using strace in production since it severely degrades the performance of the system under test.
Finally, if you are dealing with an issue that looks like it's coming from some external gem, an obvious step is to inspect the gem's source code.
You can use the gem open
Conclusion
Even if debugging isn't the most fun activity ever there are plenty of tools and techniques that can make it easier for you, use them to help you.
Please share this post if you enjoyed it so more ppl can learn! 🙂
Thank you.
Great post!
I don’t know if you are familiar with the tv serie “Mr. Robot”, but I would like to share some thoughts about debugging:
I think Eliot has a point in there, and the things you wrote about here can help us understand why we make errors when coding and how to undo them.
Thank you for your comment! I have never seen “Mr. Robot” but I have heard about it. I really like that quote you posted, so I guesss I will have to watch the show now 🙂
good post! debugging in Ruby is an often overlooked area.
Good post, thanks for introducing me to sysdig.
One note on byebug:
You can use David RodrÃguez’s pry-byebug gem (https://github.com/deivid-rodriguez/pry-byebug) to use the byebug commands within the pry repl. Now you will have syntax highlighting + byebug’s powerful debugging capabilities.
Thanks for your comment! I agree, pry-byebug is great 🙂
Using p(obj) instead of puts(obj.inspect) is not preferable just because of brevity. If need be, #inspect can be overridden. The idea is to dump the object itself, e.g. String#inspect wraps an object in quotes (like so – “self”). It can be very useful on instances of your own classes if you get stuck.
Excellent choice of the subject! Loved the tip for “require pry” snippet.
Knowing about curl is good thing. But using it for everyday debugging is cumbersome. There are plenty of great tools like postman(https://www.postman.com/downloads/) for debugging http requests/responses. Although browser-based solutions can’t modify headers blocked by browser like Origin
thanks for the article, for those that want to read more on the topic i’d like to link to one of my articles about debugging rails applications in development: http://nofail.de/2013/10/debugging-rails-applications-in-development/