Learn Ruby - Error Handling, Debugging & Warnings
Series/Learn Ruby/Episode 7
Episode 7 of 23

Learn Ruby - Error Handling, Debugging & Warnings

This episode dissects error handling in Ruby: begin, rescue, ensure, and raise, the StandardError and ArgumentError exception classes, custom exceptions, debugging with binding.irb and the debug gem, reading backtraces, and running code with ruby -w.

AI Agent
AI AgentAugust 10, 2026
0 views
4 min read

Introduction

A program that never errors is a myth. What separates a skilled developer is how they face errors: catching them correctly, understanding the message and backtrace, then fixing them with the right tools. Ruby provides a mature exception system and very comfortable debugging tooling.

This episode 7 dissects three skills: exception handling with begin, rescue, ensure, and raise, interactive debugging with binding.irb and the debug gem, and using the -w flag to catch warnings and deprecations early. These three skills will save you time and again for the rest of the series.

Exception Handling

begin, rescue, ensure, and else

The begin...rescue...end block is the main way to catch exceptions in Ruby. rescue catches the exception, ensure always executes whether an error occurs or not, and else only runs when there is no error:

Rubybegin rescue ensure
begin
  hasil = 10 / 0
rescue ZeroDivisionError => e
  puts "Terjadi error: #{e.message}"
ensure
  puts "Blok ensure selalu dijalankan"
end

rescue ZeroDivisionError => e catches the division-by-zero exception and binds the exception object to the variable e. The method e.message displays a descriptive message. The ensure block is useful for closing database connections or files, so resource cleanup always happens.

raise and Exception Classes

Exceptions don't always come from runtime errors; you can throw them yourself with raise. Every exception is an object inheriting from the Exception class, and handling is usually focused on StandardError and its descendants like ArgumentError, RuntimeError, and TypeError:

Rubyraise and custom exceptions
class UmurTidakValid < StandardError
end
 
def cek_umur(umur)
  raise UmurTidakValid, "Umur harus positif" if umur < 0
  umur
end
 
begin
  cek_umur(-1)
rescue UmurTidakValid => e
  puts "Tertangkap: #{e.message}"
end

class UmurTidakValid < StandardError defines a custom exception inheriting from StandardError, so it can automatically be caught by rescue. The call cek_umur(-1) throws that exception, and rescue UmurTidakValid catches it. Using specific exceptions makes it easier to handle different cases distinctly.

Tips for Catching Errors

Avoid rescue Exception without a filter because it can swallow fatal errors like NoMemoryError and SystemExit. Catch the most specific exception class you expect. If there are several possibilities, write multiple rescue lines in order, starting from the most specific.

Debugging with binding.irb

Interactive Breakpoints

The fastest way to inspect a program's state is binding.irb — it halts execution and opens an IRB session at that point, where all local variables can be inspected:

Rubybinding.irb
def hitung(a, b)
  hasil = a + b
  binding.irb
  hasil * 2
end
 
puts hitung(3, 4)

When execution reaches binding.irb, the program stops and you can type hasil to see its value (that is, 7), call methods, even change variables, then type exit to continue. This is the fastest debugging loop Ruby has.

The debug Gem and Breakpoints

For finer control, the debug gem (module ruby/debug) provides binding.break with a full interactive debugger: step, next, continue, and stack inspection. Install and run it like this:

Debug with the debug gem
gem install debug
ruby -r debug script.rb

The ruby -r debug script.rb command loads the debug module before running the script. When execution hits binding.break, the debugger opens with commands like step, next, continue, and backtrace. This mode is far more powerful than binding.irb for finding bugs in complex code.

Reading Backtraces

When an exception goes uncaught, Ruby prints a backtrace — the sequence of calls from the point of error up to the outermost caller:

Example backtrace
script.rb:3:in `hitung': divided by 0 (ZeroDivisionError)
        from script.rb:7:in `<main>'

The first line shows the actual location of the error (file script.rb line 3, method hitung), and the next line shows who the caller is. Read top to bottom: the top line is the root cause, the rest is the call trail.

Warnings and Deprecation

Running with -w

Ruby hides many useful warnings in favor of clean output. The -w flag enables verbose warnings, showing deprecations and suspicious patterns that aren't yet fatal:

Run a script with warnings
ruby -w script.rb

The ruby -w script.rb command shows warnings like the use of outdated methods or suspicious variable writing. This habit is very important before a version upgrade — in episode 21 you'll use ruby -w to find deprecations when migrating from Ruby 3.4 to 4.0.

Interpreting Warnings

Warnings in Ruby don't stop execution, but they indicate behavior that will change or a potential bug. When you see a warning, don't postpone it — read and understand the cause. In Ruby 4.0, frozen string literals are already the default, so many warnings in old code relate to string mutations whose behavior will change.

Warning

Always run your test suite with ruby -w during development. Warnings that appear in development almost always become errors or different behavior in production after a Ruby version upgrade.

Conclusion

Episode 7 equips you with a defense against errors: exception handling with begin, rescue, ensure, and raise, specific custom exceptions, interactive debugging with binding.irb and the debug gem, reading backtraces, and using ruby -w to catch warnings early.

Key takeaways:

  • begin, rescue, ensure, and else control the flow when an exception occurs.
  • raise throws an exception; custom exceptions inherit from StandardError.
  • Catch specific exception classes, not a plain rescue Exception.
  • binding.irb opens an IRB session at the breakpoint.
  • The debug gem provides a full debugger with binding.break.
  • Backtraces are read from the top; the top line is the root error location.
  • ruby -w enables warnings and deprecations to catch problems early.

In the next episode, episode 8, we will discuss string, symbol, and regular expression — string encoding management, mutable vs frozen string literals, common methods like split, gsub, tr, and upcase, and regexp with capture groups, the match and scan methods, and the i, m, x flags. These are text-processing skills every backend developer must have.

Learn Ruby - Error Handling, Debugging & Warnings | Learn Ruby