The Pitfalls of Overusing Rescue in Ruby (and How to Do It Right)

3 days ago 3

May 27, 2025

I’ve seen a lot of Ruby code over the years — some elegant, some messy, and some that made me pause and ask, “Why is this even working?”


🚀 Enhance Your Ruby App’s Error Handling!

Looking to improve error reporting and exception handling in your Ruby applications? Let’s connect and make your code cleaner, safer, and easier to maintain.

Get in Touch


One of those moments came when I saw something like this:

def safe_puts(value) puts value.inspect rescue NoMethodError => e puts "Failed to print: #{e.message} (possibly undefined method on #{value.class})" rescue TypeError => e puts "Type error while printing: #{e.message}" rescue StandardError => e puts "Unexpected error while printing: #{e.class}: #{e.message}" end

At first glance, it looks clever — a method that wraps puts and catches any errors so nothing ever crashes. But as I dug deeper, I realized how dangerous this pattern can be.

So today, let’s talk about why this usage is problematic, what you should do instead, and how to write better error handling that actually helps you — not hides from you.


🧨 What’s Wrong With This Method?

Let’s break down the issues one by one.

1. You’re Rescuing Way Too Broadly

This method uses rescue => e, which means it catches every single exception — including serious ones like NoMemoryError, SystemStackError, or even Interrupt (which happens when someone hits Ctrl+C).

That’s not just overkill — it’s hiding real problems behind a veil of false safety.

Better approach: Rescue only the exceptions you expect:

def safe_print(value) puts value rescue NoMethodError => e puts "Could not print: #{e.message}" end

Now, only relevant failures are caught. Everything else bubbles up, where it belongs.


2. You’re Stepping on Ruby’s Built-In Methods

By calling this method print, we’re overriding Ruby’s own Kernel#print. That might seem harmless at first, but it leads to confusion. Imagine debugging a script where print “hello” doesn’t behave the way you expect.

Better approach: Rename your method to avoid stepping on built-in behavior:

def safe_puts(value) puts value rescue StandardError => e puts "Error: #{e.message}" end

Now it’s clear what this does — and no existing code gets broken.


3. You’re Hiding Failures Instead of Fixing Them

Article contentYou’re Hiding Failures Instead of Fixing Them

Printing an error message may help during development, but in production, it’s just noise. Worse, it gives the illusion that everything is handled when, in fact, something has gone wrong.

Errors should be logged, monitored, and acted upon — not quietly dismissed.

Better approach: Use a logger instead of printing directly:

require 'logger' LOGGER = Logger.new(STDOUT) def debug_print(value) puts value.inspect rescue StandardError => e LOGGER.error "Failed to print value: #{e.class} - #{e.message}" end

Now, you’re documenting the issue in a way that helps future maintainers — not just hiding it from view.


🛠️ Real-World Example: Faraday API Calls

Let’s take a concrete example from the real world — using Faraday to fetch sunrise and sunset times from an external API.

Here’s a naive version:

begin conn = Faraday.new(url: "https://api.sunrisesunset.io ") response = conn.get('/json', lat: lat, lng: lng, date: date) rescue => e puts "Something went wrong: #{e.message}" end

It looks safe — but again, it hides more than it reveals.

Let’s refactor this into something more maintainable, testable, and flexible.

require 'faraday' class SunriseSunsetAPI def initialize(lat, lng, date = 'today') @lat = lat @lng = lng @date = date end def fetch response = connection.get('/json', lat: @lat, lng: @lng, date: @date) if response.success? return response.body else raise "API returned status #{response.status}: #{response.body}" end rescue Faraday::Error => e raise "Network error: #{e.message}" end private def connection @connection ||= Faraday.new(url: "https://api.sunrisesunset.io ") end end

Used like this:

begin data = SunriseSunsetAPI.new(lat: 40.71, lng: -74.01).fetch rescue => e puts "Failed to retrieve data: #{e.message}" end

Why This Works Better

  • Reusable : You can use this class anywhere.
  • Testable : Easy to mock responses and verify behavior.
  • Flexible : Callers decide how to respond to failure.
  • Explicit : Errors bubble up clearly, so you know what went wrong.

🧭 Final Thoughts: Rescue Should Reveal, Not Conceal

Exception handling isn’t about avoiding failure — it’s about responding to it with clarity and control.

When used well, rescue gives you the power to manage uncertainty gracefully. When used poorly, it turns your code into a minefield of silent bugs and hidden surprises.

So next time you reach for rescue, ask yourself:

  • Am I rescuing only what I expect?
  • Am I giving the caller enough context to understand what went wrong?
  • Is my code clearer because of this, or more confusing?

Because in the long run, the best code doesn’t just work — it tells a story. And the best error handling makes that story honest, helpful, and easy to follow.


Have thoughts on exception handling patterns or want to share your own rescue horror stories? Drop a comment below 👇

Article content

#Ruby #Programming #CodeQuality #BestPractices #CleanCode #SoftwareEngineering #Faraday #ErrorHandling #Rescue

Unknown's avatar

Published by ggerman

Software Developer | Ruby on Rails | Embedded Systems Enthusiast I’m a backend developer with 10+ years of experience building scalable systems and crafting robust APIs with Ruby on Rails, Python, and PHP. I thrive on delivering confident solutions, leveraging tools like AWS, Docker, and RSpec to ensure stability and performance. Beyond coding, I love blending hardware and software in IoT projects, creating innovative solutions with Arduino C++ and MicroPython. Always learning, always solving.

Published May 27, 2025May 27, 2025

Read Entire Article