Manejo de Errores
## Objetivos de Aprendizaje
- Manejar excepciones con begin-rescue
- Usar ensure para limpieza
- Lanzar excepciones personalizadas
- Crear clases de excepciones personalizadas
## Jerarquia de Excepciones
```text
Exception
├── StandardError
│ ├── ArgumentError
│ ├── NameError
│ ├── NoMethodError
│ ├── TypeError
│ ├── RuntimeError
│ └── ...
└── SystemExit
```
## Manejo Basico de Excepciones
### begin-rescue
```ruby
begin
resultado = 10 / 0
rescue
puts "Ocurrio un error"
end
```
### Con Variable de Error
```ruby
begin
resultado = 10 / 0
rescue => e
puts "Error: #{e.class} - #{e.message}"
end
```
### Multiples Clausulas rescue
```ruby
begin
# Codigo que podria fallar
resultado = operacion_riesgosa
rescue ArgumentError => e
puts "Argumento invalido: #{e.message}"
rescue NoMethodError => e
puts "Metodo no encontrado: #{e.message}"
rescue => e
puts "Error inesperado: #{e.message}"
end
```
### rescue como Modificador
```ruby
resultado = operacion_riesgosa rescue valor_por_defecto
```
## Ensure
`ensure` se ejecuta ocurra o no una excepcion:
```ruby
begin
archivo = File.open("ejemplo.txt", "r")
contenido = archivo.read
rescue Errno::ENOENT => e
puts "Archivo no encontrado: #{e.message}"
ensure
archivo.close if archivo
end
```
## Retry
`retry` re-ejecuta el bloque begin:
```ruby
intentos = 0
begin
intentos += 1
conectar_servicio
rescue ErrorDeConexion
if intentos < 3
puts "Reintentando... (intento #{intentos})"
retry
else
puts "Fallo despues de #{intentos} intentos"
end
end
```
## Raise
### Lanzar Errores Estandar
```ruby
def dividir(a, b)
raise ArgumentError, "El divisor no puede ser cero" if b == 0
a / b
end
```
### Lanzar Excepciones Personalizadas
```ruby
def retirar(monto)
raise "Fondos insuficientes" if monto > @saldo
@saldo -= monto
end
# Mejor: usar clase de excepcion especifica
class ErrorFondosInsuficientes < StandardError
def initialize(saldo, monto)
super("No se puede retirar #{monto}, el saldo es #{saldo}")
end
end
def retirar(monto)
raise ErrorFondosInsuficientes.new(@saldo, monto) if monto > @saldo
@saldo -= monto
end
```
## Clases de Excepciones Personalizadas
```ruby
class ErrorValidacion < StandardError
attr_reader :campo
def initialize(mensaje, campo = nil)
super(mensaje)
@campo = campo
end
end
def validar_edad(edad)
unless edad.is_a?(Integer)
raise ErrorValidacion.new("La edad debe ser un entero", :edad)
end
unless edad >= 0
raise ErrorValidacion.new("La edad no puede ser negativa", :edad)
end
end
```
## Catch y Throw
No son excepciones - se usan para control de flujo:
```ruby
resultado = catch(:encontrado) do
[1, 2, 3, 4, 5].each do |n|
throw(:encontrado, n * 2) if n == 3
end
nil
end
puts resultado # 6
```
## Metodos de Excepciones
```ruby
begin
10 / 0
rescue => e
puts e.class # ZeroDivisionError
puts e.message # divided by 0
puts e.backtrace # Array de cadenas de ubicacion
puts e.backtrace.first # Primera linea del trace
end
```
## Mejores Practicas
### No Suprimir Excepciones
```ruby
# Mal
begin
hacer_algo
rescue
# No hacer nada - ignorar silenciosamente
end
# Bien
begin
hacer_algo
rescue SomeError => e
manejar_error(e)
end
```
### Lanzar Temprano, Capturar Tarde
```ruby
# Lanzar excepciones temprano (fallar rapido)
def procesar_usuario(datos)
raise ArgumentError, "Datos de usuario requeridos" if datos.nil?
# ... resto del procesamiento
end
# Capturar excepciones al nivel apropiado
def importar_usuarios
usuarios.each do |datos_usuario|
begin
procesar_usuario(datos_usuario)
rescue ErrorValidacion => e
registrar_advertencia("Saltando usuario invalido: #{e.message}")
end
end
end
```
### Usar Excepciones Especificas
```ruby
# Mal - demasiado generico
begin
File.open(archivo_datos)
rescue
# Manejar error
end
# Bien - excepcion especifica
begin
File.open(archivo_datos)
rescue Errno::ENOENT
puts "Archivo de datos no encontrado"
rescue Errno::EACCES
puts "Permiso denegado"
end
```
## Patrones de Manejo de Excepciones
### Patron nil
```ruby
def encontrar_usuario(id)
begin
Usuario.find(id)
rescue ActiveRecord::RecordNotFound
nil
end
end
# O con modificador rescue
def encontrar_usuario(id)
Usuario.find(id) rescue nil
end
```
### Patron Valor por Defecto
```ruby
config = begin
YAML.load_file("config.yml")
rescue Errno::ENOENT
{}
end
```
### Patron de Registro
```ruby
require 'logger'
logger = Logger.new(STDOUT)
begin
operacion_riesgosa
rescue => e
logger.error("#{e.class}: #{e.message}")
logger.debug(e.backtrace.join("\n"))
raise # Relanzar despues de registrar
end
```
## Resumen
- `begin...rescue...ensure` maneja excepciones
- `rescue ErrorType => e` captura errores especificos
- `raise` lanza una excepcion
- `ensure` ejecuta codigo de limpieza siempre
- `retry` re-ejecuta el bloque begin
- `catch/throw` para control de flujo no exception
- Crear excepciones personalizadas subclaseando `StandardError`
- Siempre manejar excepciones especificas, no todas las excepciones
- Usar `ensure` para limpieza (cerrar archivos, etc.)
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →