Qué es una excepción y cuándo usarla
Una excepción es un objeto que representa una situación anómala que interrumpe el flujo normal del programa. Ruby las usa para señalar fallos (por ejemplo, archivo inexistente) o para comunicar reglas de negocio incumplidas (por ejemplo, “no hay stock”). Manejar excepciones no significa “ocultar” errores, sino controlar qué hacer cuando ocurre algo que puede pasar en producción.
Errores de programación vs. errores esperables
Una distinción clave para escribir código robusto es separar:
- Errores de programación (bugs): nil inesperado, método mal escrito, índice fuera de rango por lógica incorrecta. Normalmente no se rescatan; se corrigen en el código. Ejemplos típicos:
NoMethodError,NameError,ArgumentErrorpor uso incorrecto de una API interna. - Errores esperables (operativos/entrada/sistema): el usuario escribe “abc” donde se esperaba un número, un archivo no existe, un servicio externo falla. Estos sí se manejan con rutas alternativas o mensajes claros. Ejemplos:
Errno::ENOENT,JSON::ParserError,Timeout::Error,SocketError.
Regla práctica: rescata lo que puedas resolver o comunicar; no rescates lo que indica un bug que debe arreglarse.
begin / rescue / ensure: estructura básica
Ruby permite capturar excepciones con begin y rescue. El bloque ensure se ejecuta siempre, haya o no error, y es ideal para liberar recursos (cerrar archivos, conexiones, etc.).
begin # 1) Código que puede fallar contenido = File.read("datos.txt") puts contenidorescue Errno::ENOENT => e # 2) Qué hacer si el archivo no existe warn "No se encontró el archivo: #{e.message}"rescue => e # 3) Rescate genérico (úsalo con cuidado) warn "Error inesperado: #{e.class} - #{e.message}"ensure # 4) Se ejecuta siempre puts "Intento de lectura finalizado"endBuenas prácticas al rescatar
- Rescata excepciones específicas antes que genéricas. Evita
rescue => esi no tienes un plan claro. - No uses excepciones para control de flujo normal (por ejemplo, para decidir si un valor está vacío). Para eso, valida.
- Incluye contexto en el manejo: qué operación falló, con qué parámetros, y qué alternativa se tomó.
raise: lanzar excepciones de forma intencional
raise se usa para señalar que una condición no se cumple. Es útil cuando detectas un estado inválido que no debería continuar.
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
def aplicar_descuento(precio, porcentaje) raise ArgumentError, "porcentaje debe estar entre 0 y 100" unless (0..100).include?(porcentaje) precio - (precio * porcentaje / 100.0)endTambién puedes relanzar la excepción original (por ejemplo, para agregar contexto) usando raise sin argumentos dentro de un rescue.
def cargar_config(path) JSON.parse(File.read(path))rescue JSON::ParserError => e raise "Config inválida en #{path}: #{e.message}"endMensajes de error útiles: qué deben incluir
Un mensaje de error útil reduce el tiempo de diagnóstico. Procura incluir:
- Qué falló (operación).
- Dónde (recurso/identificador: archivo, id, clave).
- Por qué (regla o causa).
- Qué hacer (si aplica: “verifica que el archivo exista”, “ingresa un número”).
| Malo | Mejor |
|---|---|
"Error" | "No se pudo leer 'clientes.csv': archivo inexistente. Verifica la ruta." |
"Dato inválido" | "Cantidad inválida: 'abc'. Ingresa un entero mayor o igual a 0." |
Validaciones y rutas alternativas sin duplicar lógica
Para evitar duplicación, separa en capas: parseo/validación (entrada), lógica de dominio (reglas), y adaptadores (archivos/red). Así puedes manejar errores en el borde sin repetir la lógica central.
Patrón 1: método “parse_” que valida y falla con mensaje claro
def parse_entero!(texto, campo:) Integer(texto)rescue ArgumentError raise ArgumentError, "#{campo} debe ser un entero. Recibido: #{texto.inspect}"endLuego reutilizas ese método en distintos puntos sin reescribir el rescate.
Patrón 2: ruta alternativa con valor por defecto (cuando tenga sentido)
Para errores esperables, a veces conviene una alternativa segura.
def leer_texto_o_vacio(path) File.read(path)rescue Errno::ENOENT ""endÚsalo solo si el valor por defecto no oculta un problema importante. Si el archivo es crítico, mejor lanzar una excepción con contexto.
Patrón 3: envolver excepciones externas en excepciones del dominio
Si tu mini-proyecto tiene reglas propias, conviene exponer errores del dominio en lugar de errores técnicos. Eso hace el código más claro para quien lo usa.
class PersistenciaError < StandardError; enddef guardar_pedido(path, data) File.write(path, data)rescue SystemCallError => e # Agrupa errores de sistema (permisos, disco, etc.) raise PersistenciaError, "No se pudo guardar el pedido en #{path}: #{e.message}"endEjercicio 1: capturar errores de conversión numérica
Objetivo: pedir una cantidad, convertirla a entero y manejar entradas inválidas sin que el programa explote.
Paso a paso
- Intenta convertir con
Integer()(más estricto queto_i). - Rescata
ArgumentErrory muestra un mensaje accionable. - Reintenta o devuelve un valor controlado.
def pedir_entero(mensaje, min: nil) loop do print mensaje input = STDIN.gets&.chomp begin n = Integer(input) if min && n < min puts "Debe ser >= #{min}." next end return n rescue ArgumentError puts "Entrada inválida: #{input.inspect}. Ingresa un número entero." end endendcantidad = pedir_entero("Cantidad a comprar: ", min: 1)puts "Comprarás #{cantidad} unidades"Nota: aquí el error es esperable (entrada del usuario), por eso se maneja con reintento.
Ejercicio 2: manejar archivos inexistentes
Objetivo: leer un archivo de configuración o datos. Si no existe, ofrecer una alternativa (crear uno, usar valores por defecto o abortar con mensaje claro).
Opción A: usar valores por defecto si falta
require "json"def cargar_config_o_default(path) JSON.parse(File.read(path))rescue Errno::ENOENT { "modo" => "demo" }rescue JSON::ParserError => e raise "Config corrupta en #{path}: #{e.message}"endconfig = cargar_config_o_default("config.json")p configOpción B: crear el archivo si no existe
require "json"def asegurar_config(path) File.read(path)rescue Errno::ENOENT default = { "modo" => "demo" } File.write(path, JSON.pretty_generate(default)) JSON.pretty_generate(default)endputs asegurar_config("config.json")Elige A o B según el caso: si el archivo es opcional, A; si es parte del setup, B puede mejorar la experiencia.
Ejercicio 3: excepciones personalizadas para reglas del dominio (StockInsuficienteError)
Objetivo: modelar una regla de negocio: no se puede vender más de lo disponible. En lugar de devolver false o nil (que obliga a revisar en todos lados), lanza una excepción del dominio con datos útiles.
Paso 1: definir la excepción
class StockInsuficienteError < StandardError attr_reader :sku, :disponible, :solicitado def initialize(sku:, disponible:, solicitado:) @sku = sku @disponible = disponible @solicitado = solicitado super("Stock insuficiente para #{sku}: disponible=#{disponible}, solicitado=#{solicitado}") endendPaso 2: usarla en una operación de dominio
def descontar_stock!(inventario, sku, cantidad) disponible = inventario.fetch(sku, 0) if cantidad > disponible raise StockInsuficienteError.new( sku: sku, disponible: disponible, solicitado: cantidad ) end inventario[sku] = disponible - cantidadendinventario = { "A1" => 3 }descontar_stock!(inventario, "A1", 2) # OKdescontar_stock!(inventario, "A1", 5) # Lanza excepciónPaso 3: capturarla en el borde (interfaz/entrada) y ofrecer alternativa
begin descontar_stock!(inventario, "A1", 5) puts "Compra realizada"rescue StockInsuficienteError => e puts e.message puts "Sugerencia: reduce la cantidad o elige otro producto."endObserva la separación: la función de dominio descontar_stock! no imprime ni pregunta nada; solo aplica reglas. La interacción con el usuario ocurre al capturar la excepción.
Checklist rápido para código robusto con excepciones
- Rescata solo lo que puedas manejar; deja que los bugs exploten en desarrollo.
- Prefiere excepciones específicas y mensajes con contexto.
- Usa
ensurepara limpieza garantizada. - Valida entrada antes de ejecutar lógica crítica; usa métodos de parseo reutilizables.
- Crea excepciones del dominio para reglas de negocio y captura en los bordes del sistema.