Elixir offers robust tools engineers can use to analyze code and resolve bugs. Let’s look at some of the most effective tips and tricks for debugging in Elixir.
Have you ever encountered a bug and had no idea what to do, so you described the issue to others, even to those outside of the tech world? This might lead you to a solution, but often that solution only comes after a lengthy explanation.
Although it can sometimes be useful, this “debugging technique” can waste your precious time. Thankfully, Elixir, like many other languages, provides tools that help you get to the “I see it now” moment much faster and easier.
Debugging in Elixir: life-saving techniques
The simplest tip: IO module
IO module’s functions, IO.puts/2 and IO.inspect/2, help engineers see what’s going on with their code.
Puts/2 prints specific items. More precisely: IO.puts/2 takes a string, prints it, and returns the atom :ok.
iex(1)> IO.puts(“Hi there”)
Hi there:ok
However, IO.puts isn’t very useful if you want to print information about a complex data structure, such as a map or a tuple.
That’s where IO.inspect comes in handy. One of the main differences between the two functions is that IO.inspect can print all data types! Another distinction is that IO.inspect returns the exact data you gave it rather than always returning :ok atom. This is useful if you have a chain of functions and want to quickly review the data at the current pipeline step.
For example:
(1..10)
|> IO.inspect
|> Enum.map(fn x -> x * 2 end)
|> IO.inspect
|> Enum.sum
|> IO.inspect
Prints:
1..10
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
110
Important reminder: Use IO.puts solely with strings to ensure your program doesn’t crash! On the other hand, you can use IO.inspect for all data types (including lists, maps, structures, and functions).
Advanced approach: IEx functions
IEx refers to Elixir’s interactive shell. Typically, you can use it to evaluate expressions interactively.
But, for debugging purposes, IEx allows engineers to examine the code (IEx.pry) and set breakpoints without changing the code (break!).
Let’s take a closer look at these IEx functions.
IEx.pry
You’ve probably heard of the “pry” name if you’re familiar with Ruby. So, similarly to Ruby, IEx.pry for debugging in Elixir allows you to pry into a specific line in your code.
Here’s what to insert at the line you want to debug:
require IEx; IEx.pry
Then, run the code inside an IEx session: iex -S mix or iex -S mix phx.server.
When the code execution reaches IEx.pry, an interactive shell opens, allowing you to interfere with the current code. Once you finish prying, just type respawn.
Although great, this approach has certain drawbacks:
- You must modify the code you want to debug.
- You can’t navigate across lines using commands that you may know from other languages, such as Step Over or Step Out.
Break!
Besides the pry function, IEx offers a break! function for setting breakpoints without modifying the code. This is crucial if you wish to establish breaks in code regardless of whether it’s built-in, from a library, or yours.
To use it, insert IEx.break! in your code.
The only limitation is that break! only works on compiled code, and you can only break it at the start of the function.
Bonus IEx tips and tricks
Here are some tips and tricks to help you get the most out of the IEx module.
The first tip refers to the enabling of shell history. There are two ways: you can enable each session by starting it with a flag or enable all sessions by setting the ERL_AFLAGS environment.
If you frequently use the IEx module, this advice is highly beneficial. Namely, Elixir allows you to create a file called .iex.exs from the directory where you access IEx.You can also build a global file inside your home directory. This way, the program will evaluate that file every time you open an IEx session.
Last but not least, if you make a typo when using IEx and the command fails, try the following trick. Start a new line with #iex:break: , and you’ll instantly terminate the command.
Erlang’s trick for debugging in Elixir: :debugger tool
Since Elixir is built on top of Erlang, it inherits Erlang’s many features, including a graphical debugger with a pretty straightforward name :debugger.
Let’s look at how it works:
defmodule Example do
def double_sum(x, y) do
hard_work(x, y)
end
defp hard_work(x, y) do
x = 2 * x
y = 2 * y
x + y
end
end
Now let’s run an IEx:
$ elixirc example.ex
$ iex
… and start the debugger:
iex> :debugger.start()
{:ok, #PID<0.87.0>}
iex> :int.ni(Example)
{:module, Example}
iex> :int.break(Example, 3)
:ok
iex> Example.double_sum(1, 2)
Once you start the debugger, a graphical user interface appears.
In it, call :int.ni(Example) and thereafter, on line 3, insert a breakpoint using :int.break (Example, 3).
And voilà! The debugger displays the process with a break status.
As you can see, a :debugger is another handy tool for debugging in Elixir, allowing you to examine your code and evaluate expressions swiftly.
Debugging starts with your mindset
Above, we’ve discussed three common strategies for debugging in Elixir, as well as some valuable tips and tricks to help you be even more productive.
However, at the very end, we want to offer you additional advice — and this one isn’t about a specific function or hidden trick. It’s about you, or more precisely, your ability to think critically and analytically.
The best way to master this mindset is to challenge yourself with new projects and tasks constantly. Practice will give you experience and confidence to develop your unique methods for debugging in Elixir. Undoubtedly, that’s the best set of tools you could have.
Therefore, enjoy trying various Elixir debugging techniques and combining them with your distinctive approach! Happy debugging!