JS: Introducción a la POO
Teoría: Excepciones
Errores y excepciones en la programación
Una de las áreas más importantes en la programación es el manejo de errores. Hasta ahora hemos logrado evitarlos, pero en el mundo real, donde las aplicaciones contienen miles, decenas de miles e incluso millones de líneas de código, el manejo de errores afecta muchas cosas: la facilidad de modificación y extensión, el comportamiento adecuado del programa para el usuario en diferentes situaciones.
En esta lección, vamos a examinar el mecanismo de excepciones. Pero antes de estudiar las nuevas construcciones, hablemos sobre los errores en general.
En JavaScript, las cadenas de texto tienen un método llamado text.indexOf(str). Busca una subcadena str dentro del texto text y devuelve el índice de inicio de esa subcadena en el texto. ¿Qué sucede si la subcadena no se encuentra? ¿Es esto un error? No, este es el comportamiento normal de la función. No pasa nada grave si la subcadena no se encuentra. Imagina cualquier editor de texto y su mecanismo de búsqueda. La situación en la que no se encuentra nada ocurre constantemente y no afecta el funcionamiento del programa.
Por cierto, echa un vistazo a la documentación de esta función para ver cómo indica que la subcadena no se encontró.
Otra situación. En los mismos editores de texto, hay una función llamada "abrir archivo". Imagina que algo salió mal durante la apertura del archivo, por ejemplo, si se eliminó. ¿Es esto un error o no? Sí, en esta situación se produce un error, pero no es un error de programación. Este tipo de error puede ocurrir siempre, independientemente de la voluntad del programador. No puede evitar que ocurra. Lo único que puede hacer es implementar correctamente su manejo.
Otra pregunta interesante es: ¿qué tan crítico es este error? ¿Debe detener toda la aplicación o no? En aplicaciones mal escritas, donde el manejo de errores está implementado incorrectamente, esta situación llevará al colapso de toda la aplicación y se cerrará. En una aplicación bien escrita, no sucederá nada grave. El usuario verá una advertencia de que el archivo no se puede leer y podrá elegir acciones posteriores, como intentar leerlo nuevamente o realizar otra acción.
Lo dicho anteriormente tiene consecuencias muy serias. La misma situación puede ser considerada un error o una situación normal en diferentes niveles. Por ejemplo, si la tarea de una función es leer un archivo y no pudo hacerlo, desde el punto de vista de esa función, se produjo un error. ¿Debe esto llevar a la detención de toda la aplicación? Como descubrimos anteriormente, no debería. La aplicación que utiliza esta función puede tomar la decisión de qué tan crítica es esta situación, pero no la función en sí misma.
Códigos de retorno
En los lenguajes de programación que aparecieron antes de 1990 (aproximadamente), el manejo de errores se realizaba a través de un mecanismo de retorno de un valor especial por parte de la función. Por ejemplo, en C, si una función no puede realizar su tarea, debe devolver un valor especial, ya sea NULL o un número negativo. El valor de este número indica qué error ocurrió. Por ejemplo:
Observa las construcciones condicionales y la asignación constante de la variable ret. Básicamente, cada operación potencialmente peligrosa debe ser verificada para asegurarse de que se haya realizado correctamente. Si algo sale mal, la función devuelve un código de error especial.
Aquí es donde comienzan los problemas. Como la vida nos ha enseñado, en la mayoría de los casos, el error no se maneja en el lugar donde ocurrió, ni siquiera en el nivel superior. Supongamos que hay una función A que llama a un código que potencialmente puede generar un error, y A debe manejarlo correctamente y notificar al usuario sobre el problema. Sin embargo, el error en sí ocurre dentro de la función E, que se llama dentro de A no directamente, sino a través de una cadena de funciones: A => B => C => D => E. ¿Qué crees que sucede en este caso? Todas las funciones en esta cadena, a pesar de que no manejan el error, deben conocerlo, capturarlo y devolver el código de error al exterior. Como resultado, el código que se ocupa de los errores se vuelve tan grande que se pierde el código que realiza la tarea original.
Cabe mencionar que existen esquemas de manejo de errores que no tienen estas desventajas, pero funcionan según el principio de retorno. Por ejemplo, el monad Either.
Excepciones
Es en este contexto que surge el mecanismo de excepciones. Su objetivo principal es transmitir el error desde el lugar donde ocurrió hasta el lugar donde se puede manejar, evitando todos los niveles intermedios. En otras palabras, el mecanismo de excepciones desenrolla automáticamente la pila de llamadas.
Hay dos cosas que debes recordar sobre las excepciones: el código en el que se produce el error lanza la excepción y el código en el que se maneja el error la captura.
Las excepciones en sí son objetos Error. Estos objetos contienen un mensaje pasado al constructor, una traza de la pila y otros datos útiles.
Las excepciones se lanzan con la palabra clave throw:
También puedes lanzar cualquier expresión:
throw interrumpe la ejecución del código. En este sentido, es similar a return, pero a diferencia de este último, no solo interrumpe la ejecución de la función actual, sino también de todo el código, hasta el bloque catch más cercano en la pila de llamadas.
La construcción try catch es una instrucción especial con dos bloques que permite capturar todas las excepciones y manejarlas.
El primer bloque se coloca después de try:
Cualquier excepción lanzada por el código dentro de este bloque será capturada y pasada al segundo bloque. Si no hay errores, este bloque se omite.
Dentro de este bloque, el error estará disponible en la variable e. Puedes elegir cualquier nombre para la variable:
Dentro del bloque catch, puedes ejecutar cualquier código, incluso lanzar nuevas excepciones que pueden ser capturadas por un bloque try catch en un nivel superior:
El bloque try/catch generalmente se coloca en el nivel superior del programa, pero no es obligatorio. Es muy probable que haya varios bloques intermedios que puedan capturar errores y lanzarlos nuevamente. Este tema es bastante complejo y requiere cierta experiencia en el trabajo.