Errores personalizados

Categories:

¿Cómo podemos hacer que los errores que lanzamos sean más descriptivos para quién está utilizando nuestro código?

Por ejemplo tenemos una clase donde definimos un método para realizar una operación aritmética (+, -, * /). Este método utiliza tres valores como parámetros. El primero es la operación que se quiere realizar y los otros dos parámetros son valores – numéricos – con los cuales se va a realizar la operación.

/*
NOTA:
El uso de eval en este ejemplo es con fines lúdicos y didácticos.
*/

function Operacion () {
  this.nombre = "Operación";
  this.evaluar = function (operador, a, b) {
      console.log(eval(parseFloat(a) + operador + parseFloat(b)));
  }
}

var operacion = new Operacion();    

operacion.evaluar("+", 10, 2);
//=> 12

Al parecer funciona bien pero ¿qué pasa cuando los parámetros no son los esperados?

operacion.evaluar("+"); 
//=> NaN

operacion.evaluar("+", "a", "b");
//=> NaN

operacion.evaluar("?", 10, 2);
//=> Unexpected end of input

operacion.evaluar(5, 3, 1);
//=> 9

Al no recibir los valores esperados JavaScript indica que hubo errores al momento de interpretar el código – a excepción del último ejemplo donde si realiza la operación pero es incorrecta porque está utilizando los tres parámetros para realizar la operación y únicamente debería tomar los últimos dos -. Los mensajes que se muestran son descriptivos con respecto al error generado durante la evaluación del código, pero no son descriptivos con respecto a la implementación código.

Lanzar excepciones

Podemos lanzar excepciones – mostrar errores – desde JavaScript para indicar que el código no está funcionando cómo se esperaba.
Para lanzar excepciones es necesario utilizar throw seguido de la excepción que queremos enviar. Podemos utilizar cualquier tipo de dato como excepción. Algunos tipos de datos nos pueden servir más que otros. Por ejemplo las cadenas de texto u objetos pueden resultar más útiles en comparación con números o valores de verdad.

/* 
 Lanzar una cadena de texto como error
*/
throw "Una cadena de texto como error";
//=> Uncaught Una cadena de texto como error

/*
Lanzar un número como error
*/
throw 500;
//=> Uncaught 500

/*
Lanzar un objeto como error 
*/
throw {name: "foo", message: "el error fu"};
//=> Uncaught Object {name: "foo", message: "el error fu"}

Al mostrar Uncaught JavaScript indica que no hubo código para “atrapar” la excepción lanzada.
Para que se puedan atrapar las excepciones se tiene que seguir el flujo try (→ throw) → catch (→ finally).

Crear errores personalizados

Ya que vimos – de manera general – cómo lanzar excepciones vamos a personalizar el error a enviar.

Una manera para crear errores personalizados es crear una clase en la cuál se definen dos atributos – name, message -. Estos atributos hacen referencia al nombre del error – name – y al mensaje de error que se va a visualizar – message – cuando exista una excepción en el código.

El valor de los atributos puede ser codificado de manera dura – hardcoded: establecer el valor utilizando cadenas de texto con un valor definido – pero en este caso vamos a asignar parte del contenido de estos valores como parámetros para tener un poco más de flexibilidad.

function ErrorPersonalizado(nombre, mensaje) {
    this.name = "Error " + nombre;
    this.message = "¡"+ mensaje +"!";
}

Si queremos visualizar el error como una cadena de texto podemos agregar la función toString al prototipo del error que estamos creando:

ErrorPersonalizado.prototype.toString = function () {
  return this.name + ': "' + this.message + '"';
}

Utilizar los errores personalizados

Para utilizar los errores personalizados vamos a tomar como referencia la clase que realiza la operación aritmética.

/*
Definir el error personalizado
*/

function ErrorPersonalizado(nombre, mensaje) {
  this.name = "Error " + nombre;
  this.message = "¡"+ mensaje +"!";
}

ErrorPersonalizado.prototype.toString = function () {
  return this.name + ': "' + this.message + '"';
}


/*
Definir la clase Operación. 
Aquí lanzamos diferentes instancias del error personalizado con base en los parámetros que recibe la instancia que utiliza la función evaluar.
*/

function Operacion () {
  this.nombre = "Operación";
  this.evaluar = function (operador, a, b) {
    if (operador === undefined) {
     throw new ErrorPersonalizado(this.nombre, "El operador no está definido. Asigna un operador +, -, *, /");
    } else if (operador !== "+" && operador !== "-" && operador !== "*" && operador !== "/") {
      throw new ErrorPersonalizado(this.nombre, "Asegúrate que el operador únicamente sea  +, -, *, /");      
    } 
  
    if (a === undefined || b === undefined) {
      throw new ErrorPersonalizado(this.nombre, "Es necesario asignar dos valores numéricos como parámetros después del operador");
    } else if (isNaN(a) || isNaN(b)) {
      throw new ErrorPersonalizado(this.nombre, "Los valores tienen que ser números");
    } else {
      console.log(eval(parseFloat(a) + operador + parseFloat(b)));
    }
  }
}

/*
Uso de la clase Operacion en la cuál creamos una instancia que utiliza el método evaluar. 

En este caso, si hubiera algún error al evaluar los parámetros se va a mostrar en la consola el error como cadena de texto porque 
atrapamos la excepción.
*/

function realizar(operador, valor1, valor2) {
  try {
    var operacion = new Operacion();    
    operacion.evaluar(operador, valor1, valor2);
  } catch (e){
      console.log(e.toString());
  }  
}

En este caso extendí un poco el código para evaluar los parámetros que recibe el método evaluar. Cuando los parámetros no son los que se esperan se lanza una instancia de la excepción. En cada instancia establecemos un mensaje donde se describe por qué estamos enviando este error en particular.

También hice una función – realizar – que se encarga de atrapar los errores para mostrarlos como texto. Vamos a utilizar esta función con los ejemplos que tienen los parámetros incorrectos. En este caso los mensajes que se nos muestran son más descriptivos.

realizar("+");
//=> Error Operación: "¡Es necesario asignar dos valores numéricos como parámetros después del operador!"

realizar("+", "a", "b");
//=> Error Operación: "¡Los valores tienen que ser números!"

realizar(5, 3, 1);
//=> Error Operación: "¡Asegúrate que el operador únicamente sea  +, -, *, /!"

realizar("?", 10, 2);
//=> Error Operación: "¡Asegúrate que el operador únicamente sea  +, -, *, /!"

Si utilizamos directamente los parámetros incorrectos en el objeto operación, se visualiza el mismo mensaje pero antes se muestra “Uncaught” por que no atrapamos la excepción.

var operacion = new Operacion();    

operacion.evaluar("+");
//=> Uncaught 
//=> ErrorPersonalizado {name: "Error Operación", message: "¡Es necesario asignar dos valores numéricos como parámetros después del operador!", toString: function}


operacion.evaluar("+", "a", "b");
//=> Uncaught 
//=> ErrorPersonalizado {name: "Error Operación", message: "¡Los valores tienen que ser números!", toString: function}

operacion.evaluar(5, 3, 1);
//=> Uncaught 
//=> ErrorPersonalizado {name: "Error Operación", message: "¡Asegúrate que el operador únicamente sea  +, -, *, /!", toString: function}

operacion.evaluar("?", 10, 2);
//=> Uncaught 
//=> ErrorPersonalizado {name: "Error Operación", message: "¡Asegúrate que el operador únicamente sea  +, -, *, /!", toString: function}

Al crear nuestros tipos de errores podemos ser más descriptivos en los mensajes de error que enviamos para quien utilice nuestro código incluso para nosotros mismos.

De esta manera puede ser resultar más fácil la integración o el uso previsto del código y, si hubiera errores, sería más fácil ofrecer alternativas al atrapar los errores.