Inicio / Ruby / Ruby: Lenguaje Elegante y Expresivo / Manejo de Errores y Excepciones

Manejo de Errores y Excepciones

begin/rescue/ensure, raise, excepciones personalizadas, retry y Result Object.

Intermedio
🔒 Solo lectura
📖

Estás en modo lectura

Puedes leer toda la lección, pero para marcar progreso, hacer ejercicios y ganar XP necesitas una cuenta Pro.

Desbloquear por $9/mes

Manejo de Errores y Excepciones

Ruby tiene un sistema robusto de excepciones que permite manejar errores de forma elegante.


Jerarquía de excepciones

Exception
├── NoMemoryError
├── ScriptError
│   ├── LoadError
│   └── SyntaxError
├── SignalException
│   └── Interrupt
└── StandardError          ← rescue captura estas por defecto
    ├── ArgumentError
    ├── IOError
    ├── NameError
    │   └── NoMethodError
    ├── RangeError
    ├── RuntimeError       ← raise sin clase usa esta
    ├── TypeError
    ├── ZeroDivisionError
    └── StopIteration

begin / rescue / ensure

begin
  resultado = 10 / 0
rescue ZeroDivisionError => e
  puts "Error: #{e.message}"       # "Error: divided by 0"
  puts "Clase: #{e.class}"         # "Clase: ZeroDivisionError"
  puts "Backtrace: #{e.backtrace.first}"
ensure
  puts "Esto siempre se ejecuta"   # como finally
end

# Múltiples tipos de error
begin
  # código peligroso
rescue ArgumentError => e
  puts "Argumento inválido: #{e.message}"
rescue TypeError => e
  puts "Tipo incorrecto: #{e.message}"
rescue StandardError => e
  puts "Error general: #{e.message}"
end

# rescue en una línea
valor = Integer("abc") rescue nil   # nil (en vez de error)

retry

intentos = 0

begin
  intentos += 1
  puts "Intento #{intentos}..."
  raise "Error temporal" if intentos < 3
  puts "¡Éxito!"
rescue RuntimeError => e
  retry if intentos < 3
  puts "Falló después de #{intentos} intentos"
end

raise — lanzar excepciones

# Formas de raise
raise "Algo salió mal"                          # RuntimeError
raise ArgumentError, "Parámetro inválido"       # tipo específico
raise ArgumentError.new("Parámetro inválido")   # equivalente

# En métodos
def dividir(a, b)
  raise ArgumentError, "b no puede ser cero" if b == 0
  a.to_f / b
end

begin
  dividir(10, 0)
rescue ArgumentError => e
  puts e.message   # "b no puede ser cero"
end

Excepciones personalizadas

# Convención: heredar de StandardError
class AppError < StandardError; end
class NotFoundError < AppError; end
class ValidationError < AppError
  attr_reader :field, :code

  def initialize(message, field: nil, code: nil)
    super(message)
    @field = field
    @code = code
  end
end

class AuthenticationError < AppError; end
class AuthorizationError < AppError; end

# Uso
def buscar_usuario(id)
  raise NotFoundError, "Usuario #{id} no encontrado" unless id > 0
  { id: id, nombre: "Ana" }
end

def validar_email(email)
  unless email.match?(/\A[\w.]+@[\w.]+\z/)
    raise ValidationError.new(
      "Email inválido",
      field: :email,
      code: :format
    )
  end
end

begin
  validar_email("invalido")
rescue ValidationError => e
  puts "#{e.message} (campo: #{e.field}, código: #{e.code})"
  # "Email inválido (campo: email, código: format)"
end

rescue en métodos (sin begin)

# Ruby permite rescue directo en métodos
def leer_archivo(path)
  File.read(path)
rescue Errno::ENOENT
  "Archivo no encontrado: #{path}"
rescue Errno::EACCES
  "Sin permisos para: #{path}"
end

puts leer_archivo("/no/existe")   # "Archivo no encontrado: ..."

throw / catch (control de flujo)

# throw/catch NO es para excepciones, es para saltar niveles
resultado = catch(:encontrado) do
  [1, 2, 3].each do |x|
    [4, 5, 6].each do |y|
      if x + y == 7
        throw :encontrado, [x, y]   # sale de ambos loops
      end
    end
  end
  nil   # si no se encuentra
end

puts resultado.inspect   # [1, 6] o [2, 5] o [3, 4]

Patrones de manejo de errores

Result Object

class Result
  attr_reader :value, :error

  def initialize(value: nil, error: nil)
    @value = value
    @error = error
  end

  def success? = @error.nil?
  def failure? = !success?

  def self.success(value) = new(value: value)
  def self.failure(error) = new(error: error)
end

def dividir_seguro(a, b)
  return Result.failure("División por cero") if b == 0
  Result.success(a.to_f / b)
end

resultado = dividir_seguro(10, 3)
if resultado.success?
  puts "Resultado: #{resultado.value}"
else
  puts "Error: #{resultado.error}"
end

Logging de errores

def with_error_logging
  yield
rescue StandardError => e
  $stderr.puts "[ERROR] #{e.class}: #{e.message}"
  $stderr.puts e.backtrace.first(5).join("\n")
  raise   # re-lanza la excepción
end

with_error_logging do
  # código que puede fallar
end

Resumen

Concepto Uso
begin/rescue/ensure Manejar excepciones
raise Lanzar excepciones
retry Reintentar el bloque begin
rescue => Capturar y nombrar la excepción
ensure Código que siempre ejecuta
StandardError Base para excepciones custom
throw/catch Control de flujo (no excepciones)
rescue inline valor rescue default
🔒

Ejercicio práctico disponible

Excepciones custom y Result pattern

Desbloquear ejercicios
// Excepciones custom y Result pattern
// Desbloquea Pro para acceder a este ejercicio
// y ganar +50 XP al completarlo

function ejemplo() {
    // Tu código aquí...
}

¿Te gustó esta lección?

Con Pro puedes marcar progreso, hacer ejercicios, tomar quizzes, ganar XP y obtener tu constancia.

Ver planes desde $9/mes