Skip to content

Debugging Your Code

Last updated on July 28th, 2024 at 07:16 pm

Table of Contents

As with any programming language, there will eventually be situations where the program you write doesn’t behave as you expect and you will need to debug your code.

Printing to the Console

The first and easiest debugging mechanism in almost any programming language is writing strings to the console. Ruby has several commands which achieve this goal:

puts

The puts statement will output the provided values to the console, terminating the output with a newline.

p

The p statement is the same as the puts statement, except that it returns the value that was printed, whereas puts always returns nil.

pp

The pp statement is a nice way to get an object to “Pretty Print”, separating elements of the object on their own line so that it is more readable, similar to the JavaScript function JSON.stringify(object, null, 2). If you print an array or hash, however, it will print on one line.

print

The print statement is the same as the puts statement, except that it does not add a newline at the end of the output.

Interactive Debugging

While a programmer can can solve a lot of simple problems with printing to the console, eventually, there is a need for more in depth debugging tools, which are commonly referred to as Interactive Debugging Tools. The most commonly used tools for this purpose are pry and byebug.

Pry and Byebug are both popular Ruby debugging tools, but they serve slightly different purposes and offer distinct features. A basic comparison of the two looks like:

  1. Interactive REPL Environment:
    • Pry: Offers an interactive Read-Eval-Print Loop (REPL) environment, allowing you to experiment with code, inspect objects, and evaluate expressions on the fly. This makes it more versatile for exploration and debugging.
    • Byebug: Primarily focuses on traditional debugging tasks, such as setting breakpoints, stepping through code, and inspecting variables.
  2. Code Navigation and Editing:
    • Pry: Provides advanced code navigation features, such as the ability to list methods, show method source code, and navigate directly to method definitions. It also allows in-session editing of methods and the ability to reload code without restarting the session.
    • Byebug: Does not offer built-in code navigation or editing capabilities. It focuses on runtime debugging and requires a different tool or method for code editing.
  3. Custom Commands and Plugins:
    • Pry: Supports the creation of custom commands and plugins, enabling users to extend its functionality. This allows for a highly customizable debugging experience tailored to specific needs or workflows.
    • Byebug: While it has a powerful set of built-in commands for debugging, it does not support the same level of extensibility with custom commands and plugins.
  4. Syntax Highlighting:
    • Pry: Includes syntax highlighting for better readability of code and outputs, making it easier to distinguish different elements within the code.
    • Byebug: Does not provide syntax highlighting, making it less visually appealing and harder to read, especially with complex code.
  5. Pry Hooks:
    • Pry: Allows the use of hooks to customize the behavior of the REPL, such as executing specific code before or after the REPL starts. This adds another layer of customization and automation.
    • Byebug: Does not have an equivalent feature for hooks, limiting the ability to automate and customize the debugging session.

In summary, Pry offers a more interactive and customizable debugging experience with its REPL environment, code navigation and editing features, custom commands and plugins, syntax highlighting, and hooks. Byebug, on the other hand, is more focused on traditional debugging tasks and does not provide the same level of interactivity and customization as Pry.

The tems have been combined into the gem pry-byebug.

Tapping

Chaining methods is one of the very powerful tools that Ruby provides. However, there are times that you need to see the values that are passed in a method chain. In this case, the tap command is exactly what you need.

Determining Your Call Stack (looking back)

Sometimes when debugging code, it is necessary to determine the call chain. The method caller can be used for this purpose. An example of using caller would be:

Which would generate the output

Determining Your Call Path (looking forward)

While caller can be used to determine where your method was called from, sometimes method names are duplicated in an application and you need to know which of the methods you are accessing when you call it. For this. you would use the method source_location. It returns to you the name of the file and the line number in that file where the method begins.

TracePoint

Ruby has a very interesting class called TracePoint which can be used to trace code execution. To use TracePoint you create a TracePoint object, specifying a filter which is to be used in the trace, and then enable the trace. Once it is enabled, each time the TracePoint is reached, it will execute the block defined in TracePoint creation.

An example taken from the ruby docs follows:

At the time of the writing this document, the available filters are:

Filter NameFilter Function
:lineexecute an expression or statement on a new line
:classstart a class or module definition
:endfinish a class or module definition
:callcall a Ruby method
:returnreturn from a Ruby method
:c_callcall a C-language routine
:c_returnreturn from a C-language routine
:railsraise an exception
:b_callevent hook at block entry
:b_returnevent hook at block ending
:a_callevent hook at all calls (call, b_call, and c_call)
:a_returnevent hook at all returns (return, b_return, and c_return)
:thread_beginevent hook at thread beginning
:thread_endevent hook at thread ending
:fiber_switchevent hook at fiber switch
:script_compilednew Ruby code compiled (with eval, load or require)
List of Available Filters for TracePoint Class