Fundamentos de programación/Manejo de errores

Lección 12
Manejo de errores

Al igual que todas las cosas creadas por los seres humanos, los programas para computadores y los datos que procesan pueden contener errores.[1] Los errores son condiciones que no forman parte de la operación normal de un programa[1] y pueden ser de sintaxis, semánticos o de ejecución.[2] Los errores de sintaxis son problemas con el código escrito por el programador y los compiladores e intérpretes los reportan durante el procesamiento del código fuente. Los errores semánticos son problemas con los algoritmos que impiden que regresen el resultado esperado y para detectarlos es necesario comparar la salida esperada del programa con la salida que produce al ejecutarse. Los errores de ejecución son condiciones que afectan la operación normal del programa y pueden originarse por múltiples circunstancias, entre ellas el uso inadecuado del programa por parte del usuario, datos incorrectos o con un formato diferente al esperado, interacciones inesperadas con otros programas o un funcionamiento incorrecto del computador.[2]

Tanto los errores de sintaxis como los errores semánticos se pueden detectar y corregir durante la creación del programa, pero eso no es posible con los errores de ejecución.[2] Para manejar estos últimos los programadores deben agregar código en diversas secciones del programa para verificar si se ha producido un error y tomar las acciones necesarias para recuperarse del problema, reportarlo al usuario de forma adecuada[1] o terminar la ejecución de forma controlada.[3]

Los errores de ejecución los puede detectar automáticamente al implementación del lenguaje o los puede reportar explícitamente el programa al validar condiciones especiales antes o después de realizar una operación.[4] Los tipos más frecuentes incluyen datos de entrada inesperados o inválidos (como a presencia de signos de puntuación o letras cuando se esperan dígitos), divisiones por cero, uso de punteros nulos o que referencian posiciones de memoria inexistentes, intento de acceso a arreglos o cadenas en posiciones mayores a su tamaño, intentos de escribir a archivos de solo lectura y muchos otros.[4]

Las funciones o procedimientos de los programas son los encargados de detectar los errores de ejecución, pero normalmente no saben que hacer con ellos y deben notificar al programa o función que los ha llamado sobre el problema, para que este pueda tomar las medidas apropiadas.[5] Existen cuatro mecanismos principales para lograrlo: valores de retorno especiales, indicadores explícitos de resultado, uso de funciones para el manejo de errores y excepciones.[4]

Valores de retorno especiales

editar

El uso de valores de retorno especiales es una de las técnicas más sencillas disponibles para el manejo de errores. Al crear una función es necesario definir el rango de valores válidos que puede regresar como resultado. Una vez que ese rango está definido, se desarrolla la función para que regrese un valor fuera de ese rango para indicar un problema durante la ejecución.[4] Por ejemplo, si una función debe regresar la cantidad de clientes que han comprado en una tienda en un día específico, sabemos que ese número nunca será negativo. El programador de la función puede aprovechar ese hecho y regresar un valor negativo para indicar algún tipo de problema.

En el siguiente ejemplo el programa que invoca a la subrutina incorpora código para verificar el valor regresado por la función y reportar un error si no corresponde al conjunto de valores válidos.

// Sección inicial del programa.
// ...

entero resultado

resultado = cantidad_de_clientes (día_del_año)

si resultado < 0 entonces

  // Código para corregir el problema o reportarle el error
  // al usuario.

sino

  // Código para procesar el resultado de la función
  // si el valor regresado es válido (igual o mayor a
  // cero).

fin_si

// Resto del programa.
// ...

Es necesario ser cuidadosos al implementar o usar funciones que usan esta técnica ya que si el código que las invoca no realiza la validación requerida, el valor inválido se puede propagar a otras partes del programa y causar errores cuya causa puede ser difícil de identificar.

Indicadores explícitos de resultado

editar

Cuando no es posible seleccionar un valor específico en el dominio del tipo de datos regresado como resultado por la función, ya que todos los valores posibles son resultados válidos, es común recurrir al uso de variables globales o parámetros especiales para indicar el resultado de la operación.[4] Una de las formas más comunes de implementar esta técnica consiste en definir una variable global que la función invocada actualiza con valores definidos previamente. Si el valor de la variable global después de la invocación corresponde al código indicando una ejecución exitosa, el programa puede proceder con normalidad y usar el resultado de la función. Si el valor corresponde a uno de los códigos de error, el programa sabe que no puede usar el resultado y debe tomar las acciones necesarias para corregir el problema o reportarlo al usuario.[5]

En el siguiente ejemplo, la función abrir_archivo regresa un número como identificador del archivo abierto. Sin embargo, existen muchas condiciones en las cuales la apertura del archivo puede fallar, por ejemplo puede ser posible que el usuario que ejecuta el programa no tenga los permisos necesarios para leer ese archivo. En esos casos la función reporta un error actualizando un valor en una variable global llamada errno que contiene el código de error de la última operación ejecutada. Si el código es igual a cero, la función fue exitosa, si fue diferente a cero, entonces ocurrió un error.

// El ambiente de desarrollo define una variable llamada errno
// donde se almacena el código de error de la última operación
// ejecutada.

// Abriendo el archivo
archivo_abierto := abrir_archivo (nombre_del_archivo, "leer")

si errno = 0 entonces

  // Código para procesar el resultado de la función
  // si el valor regresado es válido (igual o mayor a
  // cero).

sino

  // Código para corregir el problema o reportarle el error
  // al usuario.

fin_si

Al igual que en el caso de los valores de retorno especiales, es necesario ser cuidadosos al usar esta técnica de manejo de errores. Es frecuente que los programadores de las funciones olviden actualizar el estado de la variable global para indicar si el resultado de la ejecución fue exitoso. Esta técnica tampoco funciona bien en la presencia de programas que ejecutan múltiples actividades de forma concurrente, ya que múltiples hilos de ejecución pueden modificar el valor de la variable al mismo tiempo y no es posible saber cual de todas las funciones fue la que en realidad falló.[5]

Manejadores de errores

editar

Algunos lenguajes permiten pasar referencias a funciones como parámetros. En estos lenguajes se puede pasar una función dedicada a manejar errores que será invocada si la rutina encuentra un problema durante la ejecución.[4] El uso de esta estrategia impide que los programadores ignoren u olviden verificar el resultado de las funciones que llaman si estas usan alguna de las dos estrategias anteriores, ya que estas no obligan a verificar si hubieron errores en al ejecución.[6] La función debe identificar el problema y tratar de resolverlo. Si no lo puede hacer, debe terminar el programa, regresar con alguna indicación de que se ha producido un problema, indicar el error en alguna variable global o lanzar una excepción.[5]

Excepciones

editar

«Excepciones» es el nombre que reciben los errores de ejecución cuando un lenguaje de programación tiene mecanismos de manejo de excepciones[2] y consisten en una notificación indicando que algo interrumpió la ejecución normal del programa.[7] El manejo de excepciones fue introducido inicialmente en el lenguaje PL/1[4] y le permite a los programas detectar y reaccionar ante eventos inesperados.[7] También permite eliminar el código para el manejo de errores del flujo principal de ejecución del programa, lo que mejora su claridad, mantenibilidad y rendimiento.[3]

El propósito principal de este mecanismo es ayudar a transferir información del punto donde se genera el error al punto donde el error se puede manejar.[5] Cuando surge una excepción, la subrutina donde se generó puede «manejar» la excepción y continuar con la ejecución del programa.[6] Pero si no lo hace, la excepción interrumpe la ejecución y se propaga a la sección del programa donde se invocó la subrutina.[2] Este proceso se repite a través de toda la cadena de invocaciones de subrutinas hasta que encuentra una sección de código especializada para manejar la excepción o hasta que llega al programa principal. Si el programa principal tampoco atrapa la excepción, este termina de la forma predeterminada por el ambiente de ejecución.[2]

Un mecanismo sencillo para el manejo de excepciones sería similar al siguiente:

intentar
Código que podría lanzar una excepción se sabemos como manejar.
atrapar Tipo de excepción 1:
[Instrucciones a ejecutar el código lanza una excepción del tipo especificado..]
atrapar Tipo de excepción 2:
[Instrucciones a ejecutar el código lanza una excepción del tipo especificado..]
fin_intentar

Con este tipo de mecanismo, el programador coloca las instrucciones o funciones que pueden lanzar una excepción dentro de un bloque de código intentar. Luego del código deseado, el programador especifica una o más excepciones que puede manejar en las secciones atrapar e incluye el código necesario para recuperarse del error. Cada entorno de programación define una gran cantidad de excepciones para reportar todos los tipos posibles de errores, usualmente organizadas en una jerarquía,[7][3] y algunos incluso le permiten a los programadores definir sus tipos de excepciones personalizadas.[3]

En el siguiente fragmento de código, el programador coloca las instrucciones necesarias para realizar una división en un bloque intentar. Si la división falla, lanzando una excepción de tipo DivisiónPorCero, el programador imprime el un mensaje de error en la consola y le asigna el valor 0 a la variable que contiene el resultado.

cargar ("es_texto.bib")

entero dividendo := 150
entero divisor := 0
entero resultado

intentar
  resultado := dividendo / divisor
atrapar DivisiónPorCero:
  resultado := 0
  desplegar_en_pantalla ("Se intentó realizar una división por cero.")
fin_intentar

// Código adicional del programa.
...

Programación segura contra fallos

editar

El uso de técnicas para el manejo de errores como las explicadas anteriormente le permite a los desarrolladores crear programas seguros contra fallos. Esto es, programas que se ejecutan razonablemente por cualquier persona que los use.[8] La seguridad, o tolerancia, a las fallas es especialmente importante en programas críticos donde un error mal manejado puede provocar pérdidas económicas o de vidas humanas como en los programas que se ejecutan en las computadoras de los aviones o en los sistemas bancarios.[9]

Para lograr esto, los programadores deben comprobar que los datos de entrada sean válidos para la operación que están por realizar, que los parámetros que reciben sus funciones tengan los tipos de valores esperados y mostrar mensajes de error significativos a los usuarios cuando encuentran una condición inesperada o que no pueden manejar apropiadamente.[8] También se pueden aplicar técnicas de programación defensiva que consisten en agregar verificaciones adicionales y recursos de recuperación de errores para garantizar que el programa puede identificar problemas potenciales y reaccionar adecuadamente a los mismos.[9]

Resumen de la lección

editar
  • Los errores son condiciones que no forman parte de la operación normal de un programa.
  • Los errores pueden ser de sintaxis, semánticos o de ejecución.
  • Los errores de sintaxis consisten en código escrito incorrectamente.
  • Los errores semánticos son problemas con los algoritmos que impiden que regresen el resultado esperado.
  • Los errores de ejecución son condiciones que afectan la operación normal del programa.
  • Los mecanismos principales para el manejo de errores de ejecución son el uso de valores de retorno especiales, de indicadores explícitos de resultado, de funciones para el manejo de errores y las excepciones.
  • La programación segura contra fallos permite crear programas que se ejecutan razonablemente por cualquier persona que los use.

Términos clave

editar

Lecturas adicionales

editar

Bibliografía

editar
  1. 1,0 1,1 1,2 D Language Foundation. «D Programming Language - Errors» [Lenguaje de programación D - Errores]. D Lang (en inglés). Washington, Estados Unidos. Consultado el 8 de octubre de 2016. 
  2. 2,0 2,1 2,2 2,3 2,4 2,5 Wachenchauzer, Rosita; Manterola, Margarita; Curia, Maximiliano; Medrano, Marcos; Paez, Nicolás (2011). «Algoritmos de Programación con Python - Manejo de errores y excepciones». LibrosWeb.es. España. Consultado el 10 de octubre de 2016. 
  3. 3,0 3,1 3,2 3,3 Deitel, Paul J.; Deitel, Harvey M. (2009). JAVA for programmers. Deitel Developer Series (en inglés) (1.ª edición). Nueva Jersey, Estados Unidos: Prentice Hall. p. 1155. ISBN 978-0-13-700129-3. 
  4. 4,0 4,1 4,2 4,3 4,4 4,5 4,6 Scott, Michael L. (2000). Programming Language Pragmatics (en inglés) (1.ª edición). California, Estados Unidos: Morgan Kaufmann. p. 858. ISBN 1-55860-578-9. 
  5. 5,0 5,1 5,2 5,3 5,4 Stroustrup, Bjarne (2013). The C++ Programming Language, Fourth Edition [El lenguaje de programación C++, cuarta edición] (en inglés) (4.ª edición). Nueva Jersey, Estados Unidos: Addison-Wesley Professional. p. 1368. ISBN 978-0-321-56384-2. 
  6. 6,0 6,1 Cline, Marshall; Lomow, Greg; Girou, Mike (1998). C++ FAQs, Second Edition [C++, Preguntas frecuentes, segunda edición] (en inglés) (2.ª edición). Nueva Jersey, Estados Unidos: Addison Wesley Longman, Inc. p. 624. ISBN 978-0-201-30983-6. 
  7. 7,0 7,1 7,2 Nakov, Svetlin; Kolev, Veselin (2013). Fundamentals of Computer Programming with C# [Fundamentos de programación de computadores con C#] (en inglés) (1.ª edición). Bulgaria: Faber Publishing. p. 1122. ISBN 978-954-400-773-7. Consultado el 9 de octubre de 2016. 
  8. 8,0 8,1 Joyanes Aguilar, Luis; Zahonero Martinez, Ignacio (1998). Estructura de datos. Algoritmos, abstracción y objetos. (1.ª edición). Madrid, España: McGraw Hill. p. 857. ISBN 84-481-2042-6. 
  9. 9,0 9,1 Sommerville, Ian (2002). Software Engineering [Ingeniería de software] (6.ª edición). México: Pearson Educación. p. 692. ISBN 970-26-0206-8. 


Proyecto: Fundamentos de programación
Anterior: Evaluación de la lección 11 — Manejo de errores — Siguiente: Evaluación de la lección 12