tutoriales.com

¡Maestría en Gestión de Errores! Explorando las Excepciones en Ruby para un Código Robusto

Este tutorial te guiará a través del fascinante mundo de la gestión de errores en Ruby. Aprenderás a implementar mecanismos robustos usando `begin`, `rescue`, `ensure` y `raise`, y cómo construir tus propias excepciones personalizadas para un control total del flujo de tu programa.

Intermedio15 min de lectura13 views
Reportar error

📖 Introducción a la Gestión de Errores en Ruby

En el desarrollo de software, los errores son una realidad inevitable. Un programa bien diseñado no es solo aquel que funciona correctamente bajo condiciones ideales, sino también aquel que maneja con gracia y eficacia las situaciones inesperadas. En Ruby, las excepciones son el mecanismo principal para gestionar estos errores, permitiéndonos interceptar problemas, tomar acciones correctivas y mantener la estabilidad de nuestra aplicación.

Ignorar la gestión de errores puede llevar a caídas inesperadas de programas, pérdida de datos o una experiencia de usuario frustrante. Dominar cómo "atrapar" y "manejar" excepciones es una habilidad fundamental para cualquier desarrollador Ruby.

En este tutorial, exploraremos los fundamentos de las excepciones en Ruby, desde los bloques básicos begin/rescue/ensure hasta la creación de nuestras propias jerarquías de excepciones personalizadas. Prepárate para escribir código más robusto, predecible y fácil de depurar.


🚨 ¿Qué son las Excepciones y por qué son Importantes?

Una excepción es un evento que ocurre durante la ejecución de un programa y que interrumpe el flujo normal de las instrucciones. En lugar de un fallo abrupto, Ruby "lanza" una excepción, que luego puede ser "capturada" y "manejada" por otra parte del código.

🔥 Importante: Las excepciones no son "errores de sintaxis". Son eventos de tiempo de ejecución que indican que algo inesperado o anómalo ha sucedido y que el programa no puede continuar de forma normal sin intervención.

Tipos Comunes de Excepciones en Ruby

Ruby tiene una jerarquía rica de clases de excepciones. Todas las clases de excepción descienden de Exception. Las excepciones que normalmente manejamos descienden de StandardError, que a su vez desciende de Exception.

Aquí tienes algunos ejemplos comunes:

  • NoMethodError: Se invoca un método que no existe en un objeto.
  • ArgumentError: Se pasan argumentos incorrectos a un método.
  • NameError: Se hace referencia a una variable o método no definido.
  • TypeError: Una operación se intenta en un objeto de tipo incorrecto.
  • ZeroDivisionError: Se intenta dividir por cero.
  • IOError: Problemas relacionados con operaciones de entrada/salida.
  • Errno::ENOENT: Archivo o directorio no encontrado.
¿Por qué `StandardError` y no `Exception` directamente?Por convención, los bloques `rescue` sin un tipo de excepción especificado capturan solo `StandardError` y sus subclases. Capturar `Exception` directamente puede atrapar señales del sistema como `SystemExit` o `Interrupt`, que normalmente no queremos manejar, ya que impiden que el programa termine correctamente o responda a interrupciones. Es una buena práctica casi siempre rescatar `StandardError` o sus subclases más específicas.

El Valor de una Buena Gestión de Errores

  • Estabilidad del Programa: Previene caídas inesperadas, manteniendo la aplicación en funcionamiento.
  • Experiencia de Usuario: Proporciona mensajes de error amigables en lugar de crípticos "stack traces".
  • Recuperación: Permite que el programa intente recuperarse de un problema (por ejemplo, reintentar una operación, usar un valor por defecto).
  • Depuración: Ayuda a identificar y localizar problemas rápidamente al registrar información relevante.
  • Seguridad: Evita exponer detalles internos sensibles del sistema a usuarios malintencionados a través de mensajes de error.

🛠️ begin, rescue, else, ensure y retry: Los Pilares de la Gestión de Errores

Ruby proporciona una estructura elegante para manejar excepciones utilizando estas palabras clave. Vamos a explorarlas en detalle.

begin y rescue (Intentar y Rescatar)

El bloque begin marca la sección de código donde pueden ocurrir excepciones. El bloque rescue define el código que se ejecutará si se lanza una excepción dentro del bloque begin.

def dividir(a, b)
  begin
    resultado = a / b
    puts "El resultado de la división es: #{resultado}"
  rescue ZeroDivisionError => e
    puts "⚠️ ¡Error! No se puede dividir por cero. Mensaje: #{e.message}"
  rescue TypeError => e
    puts "⚠️ ¡Error! Tipos de datos incorrectos para la división. Mensaje: #{e.message}"
  rescue StandardError => e # Captura cualquier otra excepción estándar
    puts "⚠️ ¡Ocurrió un error inesperado! Tipo: #{e.class}, Mensaje: #{e.message}"
  end
end

dividir(10, 2)  # Salida: El resultado de la división es: 5
dividir(10, 0)  # Salida: ⚠️ ¡Error! No se puede dividir por cero.
dividir('a', 2) # Salida: ⚠️ ¡Error! Tipos de datos incorrectos para la división.
dividir(nil, 2) # Salida: ⚠️ ¡Ocurrió un error inesperado! Tipo: NoMethodError
💡 Consejo: Puedes omitir el `begin` si el bloque `rescue` se aplica a todo un método o a un bloque directamente. El `begin` es necesario cuando quieres especificar un sub-bloque particular para el rescate.
def leer_archivo(nombre_archivo)
  archivo = File.open(nombre_archivo, 'r')
  contenido = archivo.read
  puts "Contenido del archivo: #{contenido}"
rescue Errno::ENOENT => e
  puts "⚠️ ¡Error! El archivo '#{nombre_archivo}' no se encontró. Mensaje: #{e.message}"
rescue IOError => e
  puts "⚠️ ¡Error de I/O al leer el archivo. Mensaje: #{e.message}"
ensure
  archivo.close if archivo # Asegura que el archivo se cierre siempre
end

leer_archivo('archivo_existente.txt') # Asumiendo que existe
leer_archivo('archivo_inexistente.txt')

Accediendo a la Excepción (=> e)

El => e después del tipo de excepción es opcional, pero muy útil. Asigna el objeto de la excepción a la variable e, lo que te permite acceder a sus propiedades, como e.message (el mensaje de error) y e.backtrace (la traza de la pila, útil para depuración).

def procesar_dato(dato)
  raise ArgumentError, 'El dato no puede ser nulo' if dato.nil?
  # ... procesamiento
rescue ArgumentError => mi_error
  puts "Error en el procesamiento: #{mi_error.message}"
  puts "Detalles (backtrace):\n#{mi_error.backtrace.join("\n")}"
end

procesar_dato(nil)

else (Cuando no hay excepción)

El bloque else se ejecuta solo si no se lanzó ninguna excepción dentro del bloque begin. Es útil para separar el código que solo debe ejecutarse si todo salió bien.

def realizar_operacion_segura
  begin
    # Código que podría lanzar una excepción
    puts "Intentando una operación segura..."
    # 1 / 0 # Descomenta para ver la excepción
    puts "Operación completada sin excepciones."
  rescue ZeroDivisionError => e
    puts "Error: #{e.message}"
  else
    puts "✅ ¡Todo salió bien! El bloque 'else' se ejecutó."
  ensure
    puts "El bloque 'ensure' siempre se ejecuta."
  end
end

realizar_operacion_segura()
puts "---"
# 1 / 0 # Esto causaría una ZeroDivisionError si no estuviera comentado
realizar_operacion_segura()

ensure (Siempre)

El bloque ensure es crucial. El código dentro de ensure siempre se ejecuta, independientemente de si se lanzó una excepción o no, o si se manejó. Es perfecto para tareas de limpieza, como cerrar archivos, conexiones de base de datos o liberar recursos.

def operar_con_recurso
  recurso = nil
  begin
    recurso = open('mi_archivo.txt', 'w') # Simula abrir un recurso
    recurso.puts "Escribiendo datos..."
    # raise 'Simulando un error inesperado'
  rescue StandardError => e
    puts "Se capturó un error: #{e.message}"
  ensure
    if recurso
      recurso.close
      puts "Recurso cerrado en el bloque ensure."
    end
  end
end

op_with_resource()
📌 Nota: Incluso si un `return` se ejecuta dentro del bloque `begin` o `rescue`, el bloque `ensure` se ejecutará antes de que el método retorne.

retry (Reintentar)

La palabra clave retry se utiliza dentro de un bloque rescue para volver a ejecutar el bloque begin desde el principio. Es útil para operaciones que pueden fallar temporalmente, como conexiones de red o acceso a recursos bloqueados.

⚠️ Advertencia: Usa retry con precaución. Un bucle retry sin una condición de salida puede llevar a un bucle infinito si la causa de la excepción no se resuelve.

intentos = 0
MAX_INTENTOS = 3

def conectar_a_servicio
  begin
    intentos += 1
    puts "Intentando conectar al servicio (Intento #{intentos})..."
    raise "Fallo de conexión" if intentos < MAX_INTENTOS
    puts "Conexión exitosa al servicio!"
  rescue => e
    puts "Error: #{e.message}"
    if intentos < MAX_INTENTOS
      puts "Reintentando en 1 segundo..."
      sleep 1
      retry # Vuelve a ejecutar el 'begin' 
    else
      puts "Fallo persistente después de #{MAX_INTENTOS} intentos. Abortando."
      raise # Re-lanza la última excepción si falla definitivamente
    end
  end
end

conectar_a_servicio()

💥 Lanzando Excepciones con raise

raise es la herramienta que usamos para generar explícitamente una excepción. Puedes lanzar una instancia de una clase de excepción o, de forma más sencilla, pasar un mensaje.

Sintaxis de raise

  1. raise (sin argumentos): Re-lanza la excepción actualmente activa (solo dentro de un bloque rescue).
  2. raise "Mensaje de error": Lanza una RuntimeError con el mensaje especificado.
  3. raise ClaseDeExcepcion: Lanza una nueva instancia de la clase de excepción.
  4. raise ClaseDeExcepcion, "Mensaje de error": Lanza una instancia de la clase con el mensaje.
  5. raise instancia_de_excepcion: Lanza una instancia de excepción ya creada.
def validar_edad(edad)
  raise ArgumentError, "La edad no puede ser negativa" if edad < 0
  raise ArgumentError, "La edad debe ser un número entero" unless edad.is_a? Integer
  puts "Edad validada: #{edad}"
end

begin
  validar_edad(30)
  validar_edad(-5)
rescue ArgumentError => e
  puts "Error de validación: #{e.message}"
end

begin
  validar_edad(25.5)
rescue ArgumentError => e
  puts "Error de validación: #{e.message}"
end

begin
  # Re-lanzar una excepción dentro de rescue
  raise "Primer error"
rescue => e
  puts "Capturado: #{e.message}"
  puts "Re-lanzando..."
  raise # Re-lanza la excepción actual
end

¿Cuándo usar raise?

  • Validación de Argumentos: Cuando un método recibe datos no válidos que impiden su funcionamiento.
  • Condiciones Anormales: Cuando el programa llega a un estado que no debería ser posible.
  • Delegación de Errores: Cuando una capa de tu aplicación detecta un error pero no sabe cómo manejarlo, puede lanzar una excepción para que una capa superior la intercepte.

✨ Creando Excepciones Personalizadas

A menudo, las clases de excepción estándar de Ruby no son lo suficientemente descriptivas para los errores específicos de tu aplicación. Crear tus propias clases de excepción te permite categorizar y manejar errores de una manera más granular y significativa.

Por qué usar Excepciones Personalizadas

  • Claridad del Código: Los nombres de las excepciones pueden comunicar mejor la naturaleza del problema.
  • Manejo Granular: Permite que diferentes bloques rescue capturen tipos específicos de errores de tu dominio.
  • Organización: Ayuda a estructurar la lógica de manejo de errores en aplicaciones grandes.

Pasos para Crear una Excepción Personalizada

  1. Heredar de StandardError (o una de sus subclases): Es la mejor práctica para la mayoría de los casos.
  2. Definir un constructor initialize (opcional): Para agregar propiedades personalizadas al objeto de excepción.
# Definición de una excepción base para nuestra aplicación
class MiAplicacionError < StandardError
end

# Excepción específica para cuando un usuario no es encontrado
class UsuarioNoEncontradoError < MiAplicacionError
  attr_reader :id_usuario

  def initialize(message = "Usuario no encontrado", id_usuario = nil)
    super(message)
    @id_usuario = id_usuario
  end
end

# Excepción específica para cuando una operación no está permitida
class OperacionNoPermitidaError < MiAplicacionError
  attr_reader :operacion, :usuario

  def initialize(message = "Operación no permitida", operacion = nil, usuario = nil)
    super(message)
    @operacion = operacion
    @usuario = usuario
  end
end


def buscar_usuario(id)
  raise UsuarioNoEncontradoError.new("El usuario con ID #{id} no existe.", id) if id == 99
  "Usuario con ID #{id} encontrado."
end

def realizar_accion(usuario, accion)
  raise OperacionNoPermitidaError.new("El usuario '#{usuario}' no tiene permiso para '#{accion}'.", accion, usuario) if usuario == "invitado" && accion == "editar_perfil"
  "#{usuario} realizó la acción '#{accion}'."
end


# Manejo de las excepciones personalizadas
begin
  puts buscar_usuario(123)
  puts buscar_usuario(99)
rescue UsuarioNoEncontradoError => e
  puts "❌ Error de usuario: #{e.message} (ID Buscado: #{e.id_usuario})"
rescue MiAplicacionError => e # Captura cualquier otra excepción de nuestra aplicación
  puts "❌ Error general de la aplicación: #{e.message}"
end

puts "---"

begin
  puts realizar_accion("admin", "eliminar_datos")
  puts realizar_accion("invitado", "editar_perfil")
rescue OperacionNoPermitidaError => e
  puts "❌ Error de permiso: #{e.message} (Operación: #{e.operacion}, Usuario: #{e.usuario})"
end
💡 Consejo: Considera crear una clase base para todas las excepciones de tu aplicación (por ejemplo, `MiAplicacionError < StandardError`). Esto te permite atrapar todos los errores de tu dominio con un solo `rescue MiAplicacionError`.

📈 Buenas Prácticas y Estrategias Avanzadas

La gestión de errores no se trata solo de escribir begin/rescue, sino de hacerlo de manera efectiva y sostenible.

🎯 Principios Clave

  • No suprimir errores en silencio: Evita bloques rescue vacíos que ocultan problemas importantes. Si rescatas un error, haz algo con él (registra, notifica, recupera, etc.).
  • Rescata solo lo que puedes manejar: No uses rescue StandardError indiscriminadamente si solo esperas un ZeroDivisionError. Sé lo más específico posible.
  • Registra los errores: Utiliza un sistema de logging (Logger de Ruby, Rails.logger en Rails) para registrar detalles de las excepciones, incluyendo message y backtrace.
  • Diseña con la "Ley de Deméter" en mente: Un objeto debe hablar solo con sus amigos directos. Evita cadenas de llamadas que puedan fallar en múltiples puntos y sean difíciles de depurar (a.b.c.d.e). Ruby tiene el operador safe navigation (&.) para esto.
# Sin safe navigation, podría lanzar NoMethodError si usuario es nil o no tiene perfil
# nombre_ciudad = usuario.perfil.direccion.ciudad

# Con safe navigation
nombre_ciudad = usuario&.perfil&.direccion&.ciudad
puts "Ciudad: #{nombre_ciudad || 'Desconocida'}"
Operación ¿Ocurre un error esperado? Rescatar y manejar (específico) No ¿Ocurre un error inesperado? Rescatar y registrar (general) No Operación exitosa Continuar ejecución (si es posible) o Re-lanzar Fin

Estrategias Comunes

  • Centralización del Manejo de Errores: En aplicaciones grandes, puedes tener un módulo o clase dedicada a manejar errores, especialmente para casos comunes (e.g., errores de API).
  • Patrón Null Object: En lugar de devolver nil o lanzar una excepción cuando un objeto no se encuentra, devolver un "objeto nulo" que responde a los mismos métodos pero no hace nada o devuelve valores por defecto. Esto evita NoMethodError en cascada.
  • Circuit Breaker (Cortocircuito): Para servicios externos, un circuit breaker puede detener temporalmente las llamadas a un servicio que está fallando repetidamente, evitando sobrecargarlo aún más y dando tiempo para que se recupere.
  • Idempotencia con retry: Asegúrate de que las operaciones que reintentas con retry sean idempotentes (ejecutar la operación múltiples veces produce el mismo resultado que ejecutarla una vez) o que tu lógica de reintento maneje las ejecuciones duplicadas.
Ejemplo de uso de `Logger` ```ruby require 'logger'

LOG = Logger.new(STDOUT) LOG.level = Logger::INFO

def procesar_datos_criticos(data) begin # Simular una operación que falla raise "Datos corruptos detectados" if data == :corruptos LOG.info "Datos procesados exitosamente." rescue => e LOG.error "Fallo al procesar datos: #{e.message}" LOG.error e.backtrace.join("\n") # Opcional: Notificar a un servicio de monitoreo # send_notification_to_bugsnag(e) end end

procesar_datos_criticos(:buenos) procesar_datos_criticos(:corruptos)

</details>

### Tabla Comparativa: `rescue` vs `if` para manejo de errores

| Característica             | `rescue` (Excepciones)                                 | `if` (Verificación de Valores de Retorno)             |
| :------------------------- | :----------------------------------------------------- | :---------------------------------------------------- |
| --- | --- | --- |
| **Propósito Principal**    | Manejar eventos *excepcionales* e inesperados.        | Manejar condiciones *esperadas* y flujos alternativos. |
| **Flujo de Control**       | Interrumpe el flujo normal; salta al bloque `rescue`.  | Sigue un flujo condicional normal.                   |
| --- | --- | --- |
| **Legibilidad**            | Más claro para errores, separa el "camino feliz" del error. | Puede llevar a `if anidados` o código disperso para errores. |
| **Rendimiento**            | Tiene un *overhead* mayor, ya que involucra la pila de llamadas. | Bajo *overhead*, es solo una evaluación condicional. |
| --- | --- | --- |
| **Jerarquía de Errores**   | Permite definir y atrapar tipos específicos de errores. | No aplica; se basa en valores de retorno.            |
| **Recomendado para**       | Condiciones de error irrecuperables, eventos inesperados. | Validaciones de entrada, valores de retorno esperados. |

<div class="callout warning">⚠️ <strong>Advertencia:</strong> No uses excepciones como un mecanismo de control de flujo estándar. Si puedes prever una condición y manejarla con una simple verificación `if`, es generalmente más eficiente y claro hacerlo así. Las excepciones están diseñadas para eventos *excepcionales*.</div>

---

## 📝 Ejemplos Prácticos Completos

Para consolidar lo aprendido, veamos un par de ejemplos más elaborados.

### <span class="badge green">Ejemplo 1: Procesamiento de un archivo CSV</span>

```ruby
require 'csv'

class ErrorProcesamientoCSV < StandardError
end

class ArchivoVacioError < ErrorProcesamientoCSV
  def initialize(msg = "El archivo CSV está vacío.")
    super
  end
end

class FormatoInvalidoError < ErrorProcesamientoCSV
  attr_reader :linea, :dato_problema
  def initialize(msg = "Formato de línea CSV inválido", linea = nil, dato_problema = nil)
    super(msg)
    @linea = linea
    @dato_problema = dato_problema
  end
end

def procesar_csv(ruta_archivo)
  datos_procesados = []
  begin
    raise Errno::ENOENT, "Archivo no encontrado: #{ruta_archivo}" unless File.exist?(ruta_archivo)

    csv_data = CSV.read(ruta_archivo, headers: true)
    raise ArchivoVacioError if csv_data.empty?

    csv_data.each_with_index do |row, index|
      begin
        nombre = row['Nombre']
        edad = Integer(row['Edad']) # Intentar convertir a entero, puede lanzar ArgumentError
        ciudad = row['Ciudad']

        raise FormatoInvalidoError.new("Edad no es un número", index + 2, row['Edad']) unless edad.is_a? Integer

        datos_procesados << { nombre: nombre, edad: edad, ciudad: ciudad }
      rescue ArgumentError => e # Captura error de Integer() conversion
        raise FormatoInvalidoError.new("Error de conversión en línea #{index + 2}: #{e.message}", index + 2, row['Edad'])
      rescue => e # Captura cualquier otro error en la fila
        raise FormatoInvalidoError.new("Error inesperado en línea #{index + 2}: #{e.message}", index + 2, row.to_s)
      end
    end
    puts "✅ CSV procesado exitosamente: #{datos_procesados.size} registros."
    return datos_procesados

  rescue Errno::ENOENT => e
    puts "❌ Error: #{e.message}"
    []
  rescue ArchivoVacioError => e
    puts "❌ Advertencia: #{e.message}"
    []
  rescue FormatoInvalidoError => e
    puts "❌ Error de formato en CSV: #{e.message}"
    puts "  Línea: #{e.linea}, Dato problemático: '#{e.dato_problema}'"
    []
  rescue ErrorProcesamientoCSV => e # Captura cualquier otra excepción personalizada
    puts "❌ Error general de procesamiento CSV: #{e.message}"
    []
  rescue StandardError => e # Captura cualquier otra excepción inesperada
    puts "❌ Error inesperado al procesar CSV: #{e.class} - #{e.message}"
    puts e.backtrace.join("\n")
    []
  ensure
    puts "Fin del intento de procesamiento de archivo."
  end
end

# Simular archivos
File.write('datos_validos.csv', "Nombre,Edad,Ciudad\nAlice,30,NY\nBob,24,LA")
File.write('datos_invalidos.csv', "Nombre,Edad,Ciudad\nCharlie,veinte,Chicago\nDavid,40,Houston")
File.write('archivo_vacio.csv', "Nombre,Edad,Ciudad")

puts "\n--- Procesando datos_validos.csv ---"
procesar_csv('datos_validos.csv')

puts "\n--- Procesando datos_invalidos.csv ---"
procesar_csv('datos_invalidos.csv')

puts "\n--- Procesando archivo_vacio.csv ---"
procesar_csv('archivo_vacio.csv')

puts "\n--- Procesando archivo_no_existe.csv ---"
procesar_csv('archivo_no_existe.csv')

File.delete('datos_validos.csv') if File.exist?('datos_validos.csv')
File.delete('datos_invalidos.csv') if File.exist?('datos_invalidos.csv')
File.delete('archivo_vacio.csv') if File.exist?('archivo_vacio.csv')

Este ejemplo muestra cómo combinar múltiples bloques rescue, excepciones personalizadas y la verificación de condiciones para crear un robusto procesador de CSV.

Ejemplo 2: Un sistema de autenticación simplificado

class AuthError < StandardError
end

class CredencialesInvalidasError < AuthError
  def initialize(msg = "Credenciales de usuario o contraseña incorrectas.")
    super
  end
end

class UsuarioBloqueadoError < AuthError
  attr_reader :usuario_intento
  def initialize(msg = "El usuario está bloqueado debido a múltiples intentos fallidos.", usuario = nil)
    super(msg)
    @usuario_intento = usuario
  end
end

class SistemaAutenticacion
  USUARIOS = {
    "admin" => { password: "pass123", bloqueado: false },
    "user" => { password: "secure", bloqueado: false },
    "locked_user" => { password: "locked", bloqueado: true }
  }
  MAX_INTENTOS_FALLIDOS = 3
  @@intentos_fallidos = Hash.new(0)

  def self.autenticar(nombre_usuario, password)
    begin
      usuario_data = USUARIOS[nombre_usuario]

      raise CredencialesInvalidasError unless usuario_data
      raise UsuarioBloqueadoError.new("Usuario '#{nombre_usuario}' bloqueado.", nombre_usuario) if usuario_data[:bloqueado]

      if usuario_data[:password] == password
        @@intentos_fallidos[nombre_usuario] = 0 # Resetear intentos al éxito
        puts "Bienvenido, #{nombre_usuario}!"
        true
      else
        @@intentos_fallidos[nombre_usuario] += 1
        if @@intentos_fallidos[nombre_usuario] >= MAX_INTENTOS_FALLIDOS
          USUARIOS[nombre_usuario][:bloqueado] = true
          raise UsuarioBloqueadoError.new("Usuario '#{nombre_usuario}' bloqueado tras #{MAX_INTENTOS_FALLIDOS} intentos fallidos.", nombre_usuario)
        else
          raise CredencialesInvalidasError
        end
      end
    rescue CredencialesInvalidasError => e
      puts "❌ Error de autenticación: #{e.message}"
      false
    rescue UsuarioBloqueadoError => e
      puts "❌ Error de seguridad: #{e.message}"
      false
    rescue AuthError => e # Captura cualquier otra excepción personalizada de autenticación
      puts "❌ Error inesperado de autenticación: #{e.message}"
      false
    rescue StandardError => e
      puts "❌ Error crítico en el sistema de autenticación: #{e.class} - #{e.message}"
      false
    end
  end
end

puts "\n--- Intentos de autenticación ---"
SistemaAutenticacion.autenticar("admin", "pass123") # Éxito
SistemaAutenticacion.autenticar("user", "wrong") # Falla 1
SistemaAutenticacion.autenticar("user", "wrong") # Falla 2
SistemaAutenticacion.autenticar("user", "wrong") # Falla 3, bloqueado
SistemaAutenticacion.autenticar("user", "secure") # Bloqueado
SistemaAutenticacion.autenticar("inexistente", "123") # Usuario no existe
SistemaAutenticacion.autenticar("locked_user", "locked") # Usuario pre-bloqueado

Este ejemplo demuestra cómo usar excepciones personalizadas para modelar diferentes estados y errores en un proceso de autenticación, mejorando la claridad y el control del flujo.


🔮 Conclusión

La gestión de errores es una parte integral del desarrollo de software de calidad. Al dominar el uso de begin/rescue/ensure/else/retry, raise, y la creación de excepciones personalizadas, puedes construir aplicaciones Ruby más robustas, confiables y fáciles de mantener. Recuerda siempre priorizar la claridad, la especificidad en el manejo de errores y la importancia de no suprimir problemas en silencio.

¡Felicidades, ahora estás equipado para escribir código Ruby que no solo funciona, sino que también maneja los imprevistos con gracia!

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!