Manejo de Excepciones

Introducción

En esta sección aprenderemos a manejar errores y excepciones en Ruby. Veremos cómo podemos rescatar excepciones y cómo podemos crear nuestras propias excepciones personalizadas. También veremos cómo podemos realizar pruebas unitarias en Ruby y cómo podemos utilizar herramientas de debugging para encontrar errores en nuestro código.

Rescate de Excepciones

En Ruby, las excepciones son objetos que representan un error en la ejecución de un programa. Cuando se produce un error, Ruby lanza una excepción y detiene la ejecución del programa. Para evitar que el programa se detenga, podemos rescatar excepciones utilizando la palabra clave rescue.

begin
    # Código que puede lanzar una excepción
    file = File.open("data.txt")
    contents = file.read
    puts contents
rescue Errno::ENOENT
    # Código que se ejecuta si el archivo no existe
    puts "El archivo no existe"
rescue Errno::EACCES
    # Código que se ejecuta si no se tiene acceso al archivo
    puts "No se tiene acceso al archivo"
rescue => e

    # Código que se ejecuta para cualquier otra excepción
    puts "Se ha producido un error: #{e.message}"
ensure

    # Código que se ejecuta siempre, sin importar si se lanzó una excepción o no
    file.close if file
end

En el ejemplo anterior, el bloque begin se utiliza para envolver el código que puede lanzar una excepción. Si se produce una excepción, el bloque rescue se ejecuta y se captura la excepción. Podemos utilizar varios bloques rescue para capturar diferentes excepciones. También podemos utilizar un bloque rescue sin argumentos para capturar cualquier excepción. El bloque ensure se ejecuta siempre, sin importar si se lanzó una excepción o no.

Creación de Excepciones Personalizadas

En Ruby, podemos crear nuestras propias excepciones personalizadas heredando de la clase Exception.

class MyError < Exception
end

begin
    raise MyError, "Se ha producido un error"
rescue MyError => e
    puts "Se ha producido un error personalizado: #{e.message}"
end

En el ejemplo anterior, creamos una excepción personalizada llamada MyError que hereda de la clase Exception. Luego, lanzamos la excepción MyError utilizando la palabra clave raise y capturamos la excepción utilizando un bloque rescue.

Ejemplo práctico: Crear una excepción personalizada llamada NegativeNumberError que se lanza cuando se intenta calcular la raíz cuadrada de un número negativo.

class NegativeNumberError < Exception
end

def square_root(x)
    raise NegativeNumberError, "No se puede calcular la raíz cuadrada de un número negativo" if x < 0
    Math.sqrt(x)
end

begin
    puts square_root(-1)
rescue NegativeNumberError => e
    puts "Se ha producido un error: #{e.message}"
end

Pruebas y Debugging

En Ruby, podemos realizar pruebas unitarias utilizando la biblioteca MiniTest o RSpec. También podemos utilizar las gemas pry y byebug para realizar debugging en nuestro código.

require 'minitest/autorun'

class NegativeNumberError < StandardError; end

def square_root(number)
  raise NegativeNumberError if number.negative?

  Math.sqrt(number)
end

class TestSquareRoot < Minitest::Test
  def test_square_root
    assert_equal 2, square_root(4)
    assert_equal 0, square_root(0)
    assert_raises NegativeNumberError do
      square_root(-1)
    end
  end
end

En el ejemplo anterior, creamos una prueba unitaria utilizando la biblioteca MiniTest para la función square_root. La prueba verifica que se lanza una excepción NegativeNumberError cuando se intenta calcular la raíz cuadrada de un número negativo.

Ejemplo práctico: Crear una prueba unitaria para la función square_root que verifica que se lanza una excepción NegativeNumberError cuando se intenta calcular la raíz cuadrada de un número negativo.

require 'minitest/autorun'

class NegativeNumberError < StandardError
end

def square_root(number)
  raise NegativeNumberError if number < 0

  Math.sqrt(number)
end

class TestSquareRoot < Minitest::Test
  def test_square_root
    assert_equal 2, square_root(4)
    assert_equal 0, square_root(0)
    assert_raises NegativeNumberError do
      square_root(-1)
    end
  end
end

En el ejemplo anterior, creamos una prueba unitaria utilizando la biblioteca MiniTest para la función square_root. La prueba verifica que se lanza una excepción NegativeNumberError cuando se intenta calcular la raíz cuadrada de un número negativo.

Ejercicios Prácticos

  1. Crear una excepción personalizada llamada InvalidEmailError que se lanza cuando se intenta crear un objeto Email con una dirección de correo electrónico inválida.
Ver respuesta
class InvalidEmailError < Exception
end

En el ejercicio anterior, creamos una excepción personalizada llamada InvalidEmailError que hereda de la clase Exception.

  1. Crear una clase Email que tiene un atributo address que representa la dirección de correo electrónico. La clase debe tener un constructor que recibe la dirección de correo electrónico y lanza una excepción InvalidEmailError si la dirección de correo electrónico es inválida.
Ver respuesta
class Email
  attr_reader 

  def initialize(address)
    raise InvalidEmailError, "Dirección de correo electrónico inválida" unless address.match?(/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i)

    @address = address
  end
end

begin
  email = Email.new("
    puts email.address
rescue InvalidEmailError => e
    puts "Se ha producido un #{e.message}"
    end

En el ejercicio anterior, creamos una clase Email que tiene un atributo address que representa la dirección de correo electrónico. El constructor de la clase lanza una excepción InvalidEmailError si la dirección de correo electrónico es inválida.

  1. Crear una prueba unitaria para la clase Email que verifica que se lanza una excepción InvalidEmailError cuando se intenta crear un objeto Email con una dirección de correo electrónico inválida.
Ver respuesta
require 'minitest/autorun'

class InvalidEmailError < StandardError; end

class Email
  attr_reader 

  def initialize(address)
    raise InvalidEmailError, "Dirección de correo electrónico inválida" unless address.match?(/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i)

    @address = address
  end
end

class TestEmail < Minitest::Test
  def test_email
    assert_raises InvalidEmailError do
      Email.new("invalid_email")
    end
  end
end

En el ejercicio anterior, creamos una prueba unitaria utilizando la biblioteca MiniTest para la clase Email. La prueba verifica que se lanza una excepción InvalidEmailError cuando se intenta crear un objeto Email con una dirección de correo electrónico inválida.

  1. Crear una excepción personalizada llamada InvalidPasswordError que se lanza cuando se intenta crear un objeto Password con una contraseña inválida.
Ver respuesta
class InvalidPasswordError < Exception
end

En el ejercicio anterior, creamos una excepción personalizada llamada InvalidPasswordError que hereda de la clase Exception.

  1. Crear una clase Password que tiene un atributo value que representa la contraseña. La clase debe tener un constructor que recibe la contraseña y lanza una excepción InvalidPasswordError si la contraseña es inválida.
Ver respuesta
class Password
  attr_reader 

  def initialize(value)
    raise InvalidPasswordError, "Contraseña inválida" unless value.match?(/\A(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}\z/)

    @value = value
  end
end

begin
  password = Password.new("password")
  puts password.value
rescue InvalidPasswordError => e
    puts "Se ha producido un error: #{e.message}"
    end

En el ejercicio anterior, creamos una clase Password que tiene un atributo value que representa la contraseña. El constructor de la clase lanza una excepción InvalidPasswordError si la contraseña es inválida.

Conclusiones

En esta sección aprendimos a manejar errores y excepciones en Ruby. Vimos cómo podemos rescatar excepciones y cómo podemos crear nuestras propias excepciones personalizadas. También aprendimos a realizar pruebas unitarias en Ruby y a utilizar herramientas de debugging para encontrar errores en nuestro código.