En programación informática , existen varios mecanismos de lenguaje para el manejo de excepciones . El término excepción se utiliza normalmente para denotar una estructura de datos que almacena información sobre una condición excepcional. Un mecanismo para transferir el control, o generar una excepción, se conoce como throw ; se dice que la excepción es thrown . La ejecución se transfiere a un catch .
Uso
Los lenguajes de programación difieren sustancialmente en su noción de lo que es una excepción. Las excepciones se pueden utilizar para representar y manejar situaciones anormales, impredecibles y erróneas, pero también como estructuras de control de flujo para manejar situaciones normales. Por ejemplo, los iteradores de Python lanzan excepciones StopIteration para indicar que no hay más elementos producidos por el iterador. [1] Hay desacuerdo dentro de muchos lenguajes sobre lo que constituye un uso idiomático de las excepciones. Por ejemplo, Joshua Bloch afirma que las excepciones de Java solo se deben usar para situaciones excepcionales, [2] pero Kiniry observa que la función incorporada de Java FileNotFoundExceptionno es en absoluto un evento excepcional. [3] De manera similar, Bjarne Stroustrup, autor de C++, afirma que las excepciones de C++ solo se deben usar para el manejo de errores, ya que para eso fueron diseñadas, [4] pero Kiniry observa que muchos lenguajes modernos como Ada, C++, Modula-3, ML y OCaml, Python y Ruby usan excepciones para el control de flujo. Algunos lenguajes como Eiffel, C#, Common Lisp y Modula-2 han hecho un esfuerzo concertado para restringir el uso de excepciones, aunque esto se hace a un nivel social más que técnico. [3]
Historia
El manejo de excepciones de software se desarrolló en los años 1960 y 1970. LISP 1.5 (1958-1961) [5]ERROR permitió que la pseudofunción generara excepciones , de manera similar a los errores generados por el intérprete o el compilador. Las excepciones se capturaban mediante la ERRORSETpalabra clave , que regresaba NILen caso de un error, en lugar de terminar el programa o ingresar al depurador. [6]
PL/I introdujo su propia forma de manejo de excepciones alrededor de 1964, lo que permitía manejar interrupciones con unidades ON. [7]
MacLisp observó que ERRSETy ERRse usaban no solo para generar errores, sino también para el flujo de control no local, y por lo tanto agregó dos nuevas palabras clave, CATCHy THROW(junio de 1972). [8] El comportamiento de limpieza que ahora generalmente se denomina "finally" se introdujo en NIL (Nueva implementación de LISP) a mediados y fines de los años 1970 como UNWIND-PROTECT. [9] Esto luego fue adoptado por Common Lisp . Contemporáneo con esto fue dynamic-winden Scheme, que manejaba excepciones en cierres. Los primeros artículos sobre el manejo estructurado de excepciones fueron Goodenough (1975a) y Goodenough (1975b). [10] Posteriormente, el manejo de excepciones fue ampliamente adoptado por muchos lenguajes de programación a partir de la década de 1980.
Sintaxis
Muchos lenguajes informáticos tienen soporte sintáctico integrado para excepciones y manejo de excepciones. Esto incluye ActionScript , Ada , BlitzMax , C++ , C# , Clojure , COBOL , D , ECMAScript , Eiffel , Java , ML , Object Pascal (por ejemplo, Delphi , Free Pascal y similares), PowerBuilder , Objective-C , OCaml , Perl, [11] PHP (a partir de la versión 5), PL/I , PL/SQL , Prolog , Python , REALbasic , Ruby , Scala , Seed7 , Smalltalk , Tcl , Visual Prolog y la mayoría de los lenguajes .NET .
Excluyendo pequeñas diferencias sintácticas, solo hay un par de estilos de manejo de excepciones en uso. En el estilo más popular, una excepción se inicia mediante una declaración especial ( throwo raise) con un objeto de excepción (por ejemplo, con Java u Object Pascal) o un valor de un tipo enumerado extensible especial (por ejemplo, con Ada o SML). El alcance de los manejadores de excepciones comienza con una cláusula de marcador ( tryo el iniciador de bloque del lenguaje como begin) y termina en el inicio de la primera cláusula de manejador ( catch, except, rescue). Pueden seguir varias cláusulas de manejador, y cada una puede especificar qué tipos de excepción maneja y qué nombre usa para el objeto de excepción. Como una variación menor, algunos lenguajes usan una sola cláusula de manejador, que se ocupa de la clase de la excepción internamente.
También es común una cláusula relacionada ( finallyo ensure) que se ejecuta independientemente de si se produjo una excepción o no, normalmente para liberar recursos adquiridos dentro del cuerpo del bloque de manejo de excepciones. Cabe destacar que C++ no proporciona esta construcción, recomendando en su lugar la técnica de adquisición de recursos es inicialización (RAII) que libera recursos mediante destructores . [12] Según un artículo de 2008 de Westley Weimer y George Necula , la sintaxis de los bloques try... finallyen Java es un factor que contribuye a los defectos de software. Cuando un método necesita manejar la adquisición y liberación de 3 a 5 recursos, los programadores aparentemente no están dispuestos a anidar suficientes bloques debido a problemas de legibilidad, incluso cuando esta sería una solución correcta. Es posible utilizar un solo bloque try... finallyincluso cuando se trata de múltiples recursos, pero eso requiere un uso correcto de los valores centinela , que es otra fuente común de errores para este tipo de problema. [13] : 8:6–8:7
Python y Ruby también permiten una cláusula ( else) que se utiliza en caso de que no se produzca ninguna excepción antes de que se alcance el final del alcance del controlador.
En su totalidad, el código de manejo de excepciones podría verse así (en pseudocódigo similar a Java ):
prueba { linea = console.readLine ( ) ;
if ( line . length () == 0 ) { throw new EmptyLineException ( "¡La línea leída desde la consola estaba vacía!" ); }
console.printLine ( " ¡ Hola %s!" % line ); } catch ( EmptyLineException e ) { console.printLine ( " ¡ Hola!" ); } catch ( Exception e ) { console.printLine ( " Error: " + e.message ( ) ); } else { console.printLine ( " El programa se ejecutó exitosamente." ) ; } finally { console.printLine ( " El programa está finalizando." ) ; }
C no tiene un sistema de manejo de excepciones try-catch, pero utiliza códigos de retorno para la comprobación de errores. Las funciones de la biblioteca estándar setjmpylongjmp se pueden utilizar para implementar el manejo try-catch mediante macros. [14]
Perl 5 utiliza diefor throwy for try-catch. Tiene módulos CPAN que ofrecen semántica try-catch. [15]eval {} if ($@) {}
Semántica de terminación y reanudación
Cuando se lanza una excepción, el programa busca hacia atrás en la pila de llamadas de función hasta que encuentra un manejador de excepción. Algunos lenguajes requieren que se desenrolle la pila a medida que avanza esta búsqueda. Es decir, si la función f , que contiene un manejador H para la excepción E , llama a la función g , que a su vez llama a la función h , y ocurre una excepción E en h , entonces las funciones h y g pueden terminarse, y H en f manejará E . Esto se denomina semántica de terminación. Alternativamente, los mecanismos de manejo de excepciones pueden no desenrollar la pila en la entrada [nota 1] a un manejador de excepción, lo que le da al manejador de excepción la opción de reiniciar el cálculo, reanudarlo o desenrollarlo. Esto permite que el programa continúe el cálculo exactamente en el mismo lugar donde ocurrió el error (por ejemplo, cuando un archivo que faltaba anteriormente se ha vuelto disponible) o que implemente notificaciones, registros, consultas y variables fluidas sobre el mecanismo de manejo de excepciones (como se hace en Smalltalk). Permitir que el cálculo se reanude donde se dejó se denomina semántica de reanudación.
Existen argumentos teóricos y de diseño a favor de ambas decisiones. Las discusiones sobre la estandarización de C++ en 1989-1991 dieron como resultado una decisión definitiva de utilizar la semántica de terminación en C++. [16] Bjarne Stroustrup cita una presentación de Jim Mitchell como un dato clave:
Jim había utilizado el manejo de excepciones en media docena de lenguajes durante un período de 20 años y fue uno de los primeros defensores de la semántica de reanudación como uno de los principales diseñadores e implementadores del sistema Cedar/Mesa de Xerox . Su mensaje fue
- “Se prefiere la rescisión al reinicio; no es una cuestión de opinión, sino de años de experiencia. El reinicio es seductor, pero no válido.”
Respaldó esta afirmación con su experiencia en varios sistemas operativos. El ejemplo clave fue Cedar/Mesa: fue escrito por gente a la que le gustaba y utilizaba la reanudación, pero después de diez años de uso, sólo quedaba un uso de la reanudación en el sistema de medio millón de líneas, y era una consulta de contexto. Como la reanudación no era realmente necesaria para tal consulta de contexto, la eliminaron y encontraron un aumento significativo de la velocidad en esa parte del sistema. En todos y cada uno de los casos en los que se había utilizado la reanudación, se había convertido, a lo largo de los diez años, en un problema y un diseño más apropiado la había reemplazado. Básicamente, cada uso de la reanudación había representado un fracaso a la hora de mantener separados los niveles de abstracción. [10]
Los lenguajes de manejo de excepciones con reanudación incluyen Common Lisp con su sistema de condiciones, PL/I, Dylan, R , [17] y Smalltalk . Sin embargo, la mayoría de los lenguajes de programación más nuevos siguen a C++ y utilizan semántica de terminación.
Implementación del manejo de excepciones
La implementación del manejo de excepciones en lenguajes de programación generalmente implica una buena cantidad de soporte tanto de un generador de código como del sistema de ejecución que acompaña a un compilador. (Fue la adición del manejo de excepciones a C++ lo que terminó con la vida útil del compilador original de C++, Cfront . [18] ) Dos esquemas son los más comunes. El primero,El registro dinámico genera código que actualiza continuamente las estructuras sobre el estado del programa en términos de manejo de excepciones.[19] Normalmente, esto agrega un nuevo elemento aldiseño del marco de pilaque sabe qué controladores están disponibles para la función o el método asociado con ese marco; si se lanza una excepción, un puntero en el diseño dirige el tiempo de ejecución al código del controlador apropiado. Este enfoque es compacto en términos de espacio, pero agrega sobrecarga de ejecución en la entrada y salida del marco. Se usó comúnmente en muchas implementaciones de Ada, por ejemplo, donde ya se necesitaba soporte de generación y tiempo de ejecución complejos para muchas otras características del lenguaje.El Manejo de Excepciones Estructuradas(SEH) de 32 bits de Microsoft usa este enfoque con una pila de excepciones separada.[20]El registro dinámico, al ser bastante sencillo de definir, es susceptible ala prueba de corrección.[21]
El segundo esquema, y el implementado en muchos compiladores de C++ de calidad de producción y Microsoft SEH de 64 bits , es unenfoque basado en tablas . Esto crea tablas estáticas entiempo de compilaciónytiempo de enlaceque relacionan rangos delcontador del programacon el estado del programa con respecto al manejo de excepciones.[22] Luego, si se lanza una excepción, el sistema de tiempo de ejecución busca la ubicación de la instrucción actual en las tablas y determina qué controladores están en juego y qué se debe hacer. Este enfoque minimiza la sobrecarga ejecutiva para el caso en que no se lanza una excepción. Esto sucede a costa de algo de espacio, pero este espacio se puede asignar a secciones de datos de solo lectura y propósito especial que no se cargan ni se reubican hasta que realmente se lanza una excepción.[23] La ubicación (en memoria) del código para manejar una excepción no necesita estar ubicada dentro (o incluso cerca) de la región de memoria donde se almacena el resto del código de la función. Entonces, si se lanza una excepción, puede ocurrir un impacto en el rendimiento, aproximadamente comparable a una llamada de función[24], si el código de manejo de excepciones necesario necesita cargarse/almacenarse en caché. Sin embargo, este esquema tiene un costo de rendimiento mínimo si no se lanza ninguna excepción. Dado que se supone que las excepciones en C++ sonexcepcionales(es decir, poco comunes/raros), la frase "excepciones de costo cero"[nota 2]a veces se usa para describir el manejo de excepciones en C++. Al igual quela identificación de tipo en tiempo de ejecución(RTTI), las excepciones podrían no adherirse al principio de sobrecarga cero de C++ ya que la implementación del manejo de excepciones en tiempo de ejecución requiere una cantidad de memoria distinta de cero para la tabla de búsqueda.[25]Por esta razón, el manejo de excepciones (y RTTI) se puede deshabilitar en muchos compiladores de C++, lo que puede ser útil para sistemas con memoria muy limitada[25](comosistemas integrados). Este segundo enfoque también es superior en términos de lograrseguridad de subprocesos[ cita requerida ].
También se han propuesto otros esquemas de definición e implementación. Para los lenguajes que admiten metaprogramación , se han propuesto enfoques que no implican ningún tipo de sobrecarga (más allá del soporte ya existente para la reflexión ). [26]
Manejo de excepciones basado en el diseño por contrato
Una visión diferente de las excepciones se basa en los principios del diseño por contrato y se apoya en particular en el lenguaje Eiffel . La idea es proporcionar una base más rigurosa para el manejo de excepciones definiendo con precisión qué es un comportamiento "normal" y "anormal". En concreto, el enfoque se basa en dos conceptos:
- Fallo : incapacidad de una operación para cumplir su contrato. Por ejemplo, una suma puede producir un desbordamiento aritmético (no cumple su contrato de calcular una buena aproximación a la suma matemática); o una rutina puede no cumplir su poscondición.
- Excepción : evento anormal que ocurre durante la ejecución de una rutina (la rutina que es la " destinataria " de la excepción) durante su ejecución. Un evento anormal de este tipo resulta del fallo de una operación llamada por la rutina.
El "principio de manejo seguro de excepciones", introducido por Bertrand Meyer en Construcción de software orientado a objetos, sostiene que solo hay dos formas significativas en las que una rutina puede reaccionar cuando ocurre una excepción:
- Falla, o "pánico organizado": la rutina corrige el estado del objeto restableciendo la invariante (esta es la parte "organizada"), y luego falla (entra en pánico), desencadenando una excepción en su llamador (de modo que el evento anormal no se ignora).
- Reintentar: la rutina vuelve a intentar el algoritmo, generalmente después de cambiar algunos valores para que el próximo intento tenga más posibilidades de tener éxito.
En particular, no se permite simplemente ignorar una excepción; un bloque debe volver a intentarse y completarse exitosamente, o propagar la excepción a quien lo llama.
A continuación se muestra un ejemplo expresado en sintaxis Eiffel. Se supone que una rutina send_fastes normalmente la mejor manera de enviar un mensaje, pero puede fallar y generar una excepción; si es así, el algoritmo utiliza send_slow, que fallará con menos frecuencia. Si send_slowfalla, la rutina senden su conjunto debería fallar, lo que provocaría que el autor de la llamada obtenga una excepción.
send ( m : MESSAGE ) es -- Enviar m a través de un enlace rápido, si es posible, de lo contrario a través de un enlace lento. local tried_fast , tried_slow : BOOLEAN hacer si tried_fast entonces tried_slow := True send_slow ( m ) de lo contrario tried_fast := True send_fast ( m ) fin rescate si no tried_slow entonces reintentar fin fin
Las variables locales booleanas se inicializan en Falso al principio. Si send_fastfalla, el cuerpo ( docláusula) se ejecutará nuevamente, lo que provocará la ejecución de send_slow. Si esta ejecución de send_slowfalla, la rescuecláusula se ejecutará hasta el final sin ninguna cláusula retry(sin elsecláusula en el final if), lo que provocará que la ejecución de la rutina en su totalidad falle.
Este enfoque tiene el mérito de definir claramente qué son los casos "normales" y "anormales": un caso anormal, que causa una excepción, es aquel en el que la rutina es incapaz de cumplir su contrato. Define una distribución clara de roles: la docláusula (cuerpo normal) se encarga de lograr, o intentar lograr, el contrato de la rutina; la rescuecláusula se encarga de restablecer el contexto y reiniciar el proceso, si esto tiene una posibilidad de éxito, pero no de realizar ningún cálculo real.
Aunque las excepciones en Eiffel tienen una filosofía bastante clara, Kiniry (2006) critica su implementación porque "Las excepciones que son parte de la definición del lenguaje están representadas por valores INTEGER, las excepciones definidas por el desarrollador por valores STRING. [...] Además, debido a que son valores básicos y no objetos, no tienen una semántica inherente más allá de la que se expresa en una rutina de ayuda que necesariamente no puede ser infalible debido a la sobrecarga de representación en efecto (por ejemplo, no se pueden diferenciar dos números enteros del mismo valor)". [3]
Excepciones no detectadas
Las aplicaciones contemporáneas enfrentan muchos desafíos de diseño al considerar estrategias de manejo de excepciones. Particularmente en las aplicaciones empresariales modernas, las excepciones a menudo deben cruzar los límites de los procesos y de las máquinas. Parte del diseño de una estrategia sólida de manejo de excepciones consiste en reconocer cuándo un proceso ha fallado hasta el punto en que no puede ser manejado económicamente por la parte de software del proceso. [27]
Si se lanza una excepción y no se captura (operativamente, se lanza una excepción cuando no se especifica ningún controlador aplicable), la excepción no capturada es manejada por el entorno de ejecución; la rutina que hace esto se llamamanejador de excepciones no capturadas .[28][29]El comportamiento predeterminado más común es terminar el programa e imprimir un mensaje de error en la consola, que generalmente incluye información de depuración como una representación de cadena de la excepción y elseguimiento de la pila.[28][30][31]Esto a menudo se evita al tener un manejador de nivel superior (nivel de aplicación) (por ejemplo en unbucle de eventos) que captura las excepciones antes de que lleguen al tiempo de ejecución.[28][32]
Tenga en cuenta que si bien una excepción no detectada puede provocar que el programa finalice de forma anormal (el programa puede no ser correcto si no se detecta una excepción, en particular al no revertir transacciones parcialmente completadas o no liberar recursos), el proceso finaliza normalmente (suponiendo que el entorno de ejecución funciona correctamente), ya que el entorno de ejecución (que controla la ejecución del programa) puede garantizar el cierre ordenado del proceso.
En un programa multiproceso, una excepción no detectada en un subproceso puede provocar la finalización de ese subproceso únicamente, no de todo el proceso (las excepciones no detectadas en el controlador de nivel de subproceso son detectadas por el controlador de nivel superior). Esto es particularmente importante para los servidores, donde, por ejemplo, un servlet (que se ejecuta en su propio subproceso) puede finalizar sin que el servidor en su conjunto se vea afectado.
Este controlador de excepciones no detectadas predeterminado se puede anular, ya sea de forma global o por subproceso, por ejemplo, para proporcionar un registro alternativo o informes de usuario final de excepciones no detectadas, o para reiniciar subprocesos que finalizan debido a una excepción no detectada. Por ejemplo, en Java, esto se hace para un único subproceso a través de Thread.setUncaughtExceptionHandlery de forma global a través de Thread.setDefaultUncaughtExceptionHandler; en Python, esto se hace modificando sys.excepthook.
Excepciones comprobadas
Java introdujo la noción de excepciones comprobadas, [33] [34] que son clases especiales de excepciones. Las excepciones comprobadas que un método puede generar deben ser parte de la firma del método . Por ejemplo, si un método puede generar una excepción IOException, debe declarar este hecho explícitamente en su firma de método. Si no lo hace, se genera un error en tiempo de compilación. Según Hanspeter Mössenböck, las excepciones comprobadas son menos convenientes pero más robustas. [35] Las excepciones comprobadas pueden, en tiempo de compilación , reducir la incidencia de excepciones no controladas que surgen en tiempo de ejecución en una aplicación determinada.
Kiniry escribe que "Como cualquier programador Java sabe, el volumen de try catchcódigo en una aplicación Java típica es a veces mayor que el código comparable necesario para la comprobación explícita de parámetros formales y valores de retorno en otros lenguajes que no tienen excepciones comprobadas. De hecho, el consenso general entre los programadores Java en las trincheras es que lidiar con excepciones comprobadas es una tarea casi tan desagradable como escribir documentación. Por lo tanto, muchos programadores informan que "resienten" las excepciones comprobadas". [3] Martin Fowler ha escrito "... en general creo que las excepciones son buenas, pero las excepciones comprobadas de Java son más problemáticas de lo que valen". [36] A partir de 2006, ningún lenguaje de programación importante ha seguido a Java en la adición de excepciones comprobadas. [36] Por ejemplo, C# no requiere ni permite la declaración de ninguna especificación de excepción, con lo siguiente publicado por Eric Gunnerson: [37] [3] [36]
"El examen de programas pequeños lleva a la conclusión de que exigir especificaciones de excepciones podría mejorar la productividad del desarrollador y la calidad del código, pero la experiencia con proyectos de software grandes sugiere un resultado diferente: menor productividad y poco o ningún aumento en la calidad del código".
Anders Hejlsberg describe dos preocupaciones con las excepciones controladas: [38]
- Control de versiones: se puede declarar un método para que lance las excepciones X e Y. En una versión posterior del código, no se puede lanzar la excepción Z desde el método, porque haría que el nuevo código fuera incompatible con los usos anteriores. Las excepciones comprobadas requieren que los invocadores del método agreguen Z a su cláusula de lanzamiento o gestionen la excepción. Alternativamente, Z puede representarse erróneamente como X o Y.
- Escalabilidad: en un diseño jerárquico, cada sistema puede tener varios subsistemas. Cada subsistema puede generar varias excepciones. Cada sistema padre debe ocuparse de las excepciones de todos los subsistemas que se encuentran debajo de él, lo que da como resultado una cantidad exponencial de excepciones que deben abordarse. Las excepciones controladas requieren que todas estas excepciones se traten explícitamente.
Para evitar esto, Hejlsberg dice que los programadores recurren a eludir la característica mediante una declaración. Otra elusión es utilizar un controlador. [38] Esto se conoce como manejo de excepciones de tipo catch-all o manejo de excepciones de Pokémon por el eslogan del programa "¡Tienes que atraparlos a todos!". [39] Los tutoriales de Java desaconsejan el manejo de excepciones de tipo catch-all ya que puede atrapar excepciones "para las que el controlador no fue diseñado". [40] Otra elusión desaconsejada es hacer que todas las excepciones sean subclases . [41] Una solución recomendada es utilizar un controlador de tipo catch-all o una cláusula throws pero con una superclase específica de todas las excepciones potencialmente lanzadas en lugar de la superclase general . Otra solución recomendada es definir y declarar tipos de excepción que sean adecuados para el nivel de abstracción del método llamado [42] y asignar excepciones de nivel inferior a estos tipos mediante el uso de encadenamiento de excepciones .
throws Exceptiontry { ... } catch (Exception e) {}RuntimeExceptionException
Mecanismos similares
Las raíces de las excepciones comprobadas se remontan a la noción de especificación de excepciones del lenguaje de programación CLU . [43] Una función podría generar solo excepciones enumeradas en su tipo, pero cualquier excepción con fugas de las funciones llamadas se convertiría automáticamente en la única excepción en tiempo de ejecución, failureen lugar de resultar en un error en tiempo de compilación. [44] Más tarde, Modula-3 tenía una característica similar. [45] Estas características no incluyen la comprobación en tiempo de compilación que es central en el concepto de excepciones comprobadas. [43]
Las primeras versiones del lenguaje de programación C++ incluían un mecanismo opcional similar a las excepciones controladas, llamado especificaciones de excepción . De forma predeterminada, cualquier función podía lanzar cualquier excepción, pero esto podía limitarse mediante una throwcláusula añadida a la firma de la función, que especificaba qué excepciones podía lanzar la función. Las especificaciones de excepción no se aplicaban en tiempo de compilación. Las violaciones daban como resultado la llamada a la función global. [46] Se podía dar una especificación de excepción vacía, que indicaba que la función no lanzaría ninguna excepción. Esto no se convirtió en el valor predeterminado cuando se añadió el manejo de excepciones al lenguaje porque habría requerido demasiada modificación del código existente, habría impedido la interacción con el código escrito en otros lenguajes y habría tentado a los programadores a escribir demasiados controladores a nivel local. [46] Sin embargo, el uso explícito de especificaciones de excepción vacías podría permitir a los compiladores de C++ realizar optimizaciones significativas del código y del diseño de la pila que se impiden cuando el manejo de excepciones puede tener lugar en una función. [23] Algunos analistas consideraban que el uso adecuado de las especificaciones de excepción en C++ era difícil de lograr. [47] Este uso de especificaciones de excepción se incluyó en C++98 y C++03 , quedó obsoleto en el estándar de lenguaje C++ de 2012 ( C++11 ), [48] y se eliminó del lenguaje en C++17 . Ahora, una función que no lanzará ninguna excepción se puede indicar con la palabra clave.
std::unexpectednoexcept
Existe un analizador de excepciones no capturadas para el lenguaje de programación OCaml . [49] La herramienta informa el conjunto de excepciones generadas como una firma de tipo extendida. Pero, a diferencia de las excepciones comprobadas, la herramienta no requiere ninguna anotación sintáctica y es externa (es decir, es posible compilar y ejecutar un programa sin haber comprobado las excepciones).
Comprobación dinámica de excepciones
El objetivo de las rutinas de manejo de excepciones es garantizar que el código pueda manejar condiciones de error. Para establecer que las rutinas de manejo de excepciones sean lo suficientemente robustas, es necesario presentar al código un amplio espectro de entradas no válidas o inesperadas, como las que se pueden crear mediante la inyección de fallas de software y las pruebas de mutación (que a veces también se denominan pruebas de fuzz ). Uno de los tipos de software para los que es más difícil escribir rutinas de manejo de excepciones es el software de protocolo, ya que una implementación de protocolo robusta debe estar preparada para recibir entradas que no cumplan con las especificaciones pertinentes.
Para garantizar que se puedan realizar análisis de regresión significativos durante todo el proceso del ciclo de vida del desarrollo de software , todas las pruebas de manejo de excepciones deben estar altamente automatizadas y los casos de prueba deben generarse de manera científica y repetible. Existen varios sistemas disponibles comercialmente que realizan dichas pruebas.
En entornos de motores de ejecución como Java o .NET , existen herramientas que se conectan al motor de ejecución y, cada vez que se produce una excepción de interés, registran la información de depuración que existía en la memoria en el momento en que se generó la excepción ( valores de pila de llamadas y montón ). Estas herramientas se denominan herramientas de gestión de excepciones automatizadas o de interceptación de errores y proporcionan información sobre la "causa raíz" de las excepciones.
Excepciones asincrónicas
Las excepciones asincrónicas son eventos generados por un subproceso independiente o un proceso externo, como presionar Ctrl-C para interrumpir un programa, recibir una señal o enviar un mensaje disruptivo como "stop" o "suspend" desde otro subproceso de ejecución . [50] [51] Mientras que las excepciones sincrónicas ocurren en una throwdeclaración específica, las excepciones asincrónicas pueden generarse en cualquier momento. De ello se deduce que el compilador no puede optimizar el manejo de excepciones asincrónicas, ya que no puede probar la ausencia de excepciones asincrónicas. También es difícil programarlas correctamente, ya que las excepciones asincrónicas deben bloquearse durante las operaciones de limpieza para evitar fugas de recursos.
Los lenguajes de programación suelen evitar o restringir el manejo asincrónico de excepciones; por ejemplo, C++ prohíbe generar excepciones desde los manejadores de señales y Java ha dejado en desuso el uso de su excepción ThreadDeath que se usaba para permitir que un hilo detuviera a otro. [52] Otra característica es un mecanismo semiasincrónico que genera una excepción asincrónica solo durante ciertas operaciones del programa. Por ejemplo, Java solo afecta al hilo cuando el hilo llama a una operación que lanza . [53] La API POSIX similar tiene condiciones de carrera que hacen que sea imposible usarla de forma segura. [54]Thread.interrupt()InterruptedExceptionpthread_cancel
Sistemas de condición
Common Lisp , R , [55] Dylan y Smalltalk tienen un sistema de condiciones [56] (ver Common Lisp Condition System ) que engloba los sistemas de manejo de excepciones antes mencionados. En esos lenguajes o entornos, la aparición de una condición (una "generalización de un error" según Kent Pitman ) implica una llamada a una función, y solo en una fase avanzada del manejo de excepciones se puede tomar la decisión de desenrollar la pila.
Las condiciones son una generalización de las excepciones. Cuando surge una condición, se busca y selecciona un manejador de condiciones adecuado, en orden de pila, para manejar la condición. Las condiciones que no representan errores pueden quedar sin manejar en absoluto; su único propósito puede ser propagar sugerencias o advertencias hacia el usuario. [57]
Excepciones continuables
Esto está relacionado con el llamado modelo de reanudación del manejo de excepciones, en el que se dice que algunas excepciones son continuables : se permite volver a la expresión que señaló una excepción, después de haber tomado una acción correctiva en el controlador. El sistema de condiciones se generaliza así: dentro del controlador de una condición no grave (también conocida como excepción continuable ), es posible saltar a puntos de reinicio predefinidos (también conocidos como reinicios ) que se encuentran entre la expresión de señalización y el controlador de condición. Los reinicios son funciones cerradas sobre algún entorno léxico, lo que permite al programador reparar este entorno antes de salir completamente del controlador de condición o desenrollar la pila incluso parcialmente.
Un ejemplo es la condición ENDPAGE en PL/I; la unidad ON podría escribir líneas de final de página y líneas de encabezado para la página siguiente y luego continuar para reanudar la ejecución del código interrumpido.
Reinicia un mecanismo separado de la política
Además, el manejo de condiciones permite separar el mecanismo de la política . Los reinicios proporcionan varios mecanismos posibles para recuperarse de un error, pero no seleccionan qué mecanismo es el adecuado en una situación determinada. Esa es la tarea del manejador de condiciones, que (ya que se encuentra en un código de nivel superior) tiene acceso a una vista más amplia.
Un ejemplo: supongamos que hay una función de biblioteca cuyo propósito es analizar una sola entrada de archivo syslog . ¿Qué debería hacer esta función si la entrada tiene un formato incorrecto? No hay una única respuesta correcta, porque la misma biblioteca se podría implementar en programas para muchos propósitos diferentes. En un explorador de archivos de registro interactivo, lo correcto podría ser devolver la entrada sin analizar, para que el usuario pueda verla, pero en un programa de resumen de registros automatizado, lo correcto podría ser proporcionar valores nulos para los campos ilegibles, pero abortar con un error, si demasiadas entradas tienen un formato incorrecto.
Es decir, la pregunta solo puede responderse en términos de los objetivos más amplios del programa, que no son conocidos por la función de biblioteca de propósito general. No obstante, salir con un mensaje de error rara vez es la respuesta correcta. Por lo tanto, en lugar de simplemente salir con un error, la función puede establecer reinicios que ofrecen varias formas de continuar, por ejemplo, omitir la entrada del registro, proporcionar valores predeterminados o nulos para los campos ilegibles, solicitar al usuario los valores faltantes o desenrollar la pila y abortar el procesamiento con un mensaje de error. Los reinicios ofrecidos constituyen los mecanismos disponibles para recuperarse de un error; la selección de reinicio por parte del controlador de condiciones proporciona la política .
Crítica
El manejo de excepciones a menudo no se maneja correctamente en el software, especialmente cuando hay múltiples fuentes de excepciones; el análisis del flujo de datos de 5 millones de líneas de código Java encontró más de 1300 defectos en el manejo de excepciones. [13] Citando múltiples estudios previos de otros (1999-2004) y sus propios resultados, Weimer y Necula escribieron que un problema significativo con las excepciones es que "crean rutas de flujo de control ocultas sobre las que es difícil para los programadores razonar". [13] : 8:27 "Si bien try-catch-finally es conceptualmente simple, tiene la descripción de ejecución más complicada en la especificación del lenguaje [Gosling et al. 1996] y requiere cuatro niveles de "if" anidados en su descripción oficial en inglés. En resumen, contiene una gran cantidad de casos extremos que los programadores a menudo pasan por alto". [13] : 8:13–8:14
Las excepciones, como flujo no estructurado, aumentan el riesgo de fugas de recursos (como escapar de una sección bloqueada por un mutex o una que mantiene abierto temporalmente un archivo) o de un estado inconsistente. Existen varias técnicas para la gestión de recursos en presencia de excepciones, la más común es la combinación del patrón dispose con alguna forma de protección de desenrollado (como una finallycláusula), que libera automáticamente el recurso cuando el control sale de una sección de código.
En 1980, Tony Hoare describió el lenguaje de programación Ada como un lenguaje que tiene "...una plétora de características y convenciones de notación, muchas de ellas innecesarias y algunas de ellas, como el manejo de excepciones, incluso peligrosas. [...] No permita que este lenguaje en su estado actual se use en aplicaciones donde la confiabilidad es crítica [...]. El próximo cohete que se extravíe como resultado de un error del lenguaje de programación puede no ser un cohete espacial exploratorio en un viaje inofensivo a Venus: puede ser una ojiva nuclear que explote sobre una de nuestras propias ciudades". [58]
Los desarrolladores de Go creen que el modismo try-catch-finally ofusca el flujo de control , [59] e introdujeron el mecanismo panic/ similar a una excepción. [60] se diferencia de en que solo se puede llamar desde dentro de un bloque de código en una función, por lo que el controlador solo puede limpiar y cambiar los valores de retorno de la función, y no puede devolver el control a un punto arbitrario dentro de la función. [61] El bloque en sí funciona de manera similar a una cláusula.
recover recover()catchdeferdeferfinally
Véase también
- Manejo automatizado de excepciones
- Continuación
- Programación defensiva
- Seguridad de excepción
- Tipos de opciones y tipos de resultados , formas alternativas de manejar errores en programación funcional sin excepciones
Notas
- ^ En, por ejemplo, PL/I, una salida normal de un controlador de excepciones desenrolla la pila.
- ^ El costo de procesamiento es cero solo si no se genera ninguna excepción (aunque habrá un costo de memoria, ya que se necesita memoria para la tabla de búsqueda). Hay un costo (potencialmente significativo) si se genera una excepción (es decir, si
throwse ejecuta). Implementar el manejo de excepciones también puede limitar las posibles optimizaciones del compilador que se pueden realizar.
Referencias
- ^ "Excepciones integradas: documentación de Python 3.10.4". docs.python.org . Consultado el 17 de mayo de 2022 .
- ^ Bloch, Joshua (2008). "Ítem 57: Utilizar excepciones solo en situaciones excepcionales" . Effective Java (Segunda edición). Addison-Wesley. pág. 241. ISBN 978-0-321-35668-0.
- ^ abcde Kiniry, JR (2006). "Excepciones en Java y Eiffel: dos extremos en el diseño y la aplicación de excepciones". Temas avanzados en técnicas de manejo de excepciones (PDF) . Apuntes de clase en informática. Vol. 4119. págs. 288–300. doi :10.1007/11818502_16. ISBN 978-3-540-37443-5. Número de identificación del sujeto 33283674.
- ^ "Stroustrup: Preguntas frecuentes sobre técnicas y estilos de C++". www.stroustrup.com . Archivado desde el original el 2 de febrero de 2018. Consultado el 5 de mayo de 2018 .
- ^ McCarthy, John (12 de febrero de 1979). «Historia de Lisp». www-formal.stanford.edu . Consultado el 13 de enero de 2022 .
- ^ McCarthy, John; Levin, Michael I.; Abrahams, Paul W.; Edwards, Daniel J.; Hart, Timothy P. (14 de julio de 1961). Manual del programador de LISP 1.5 (PDF) . Consultado el 13 de enero de 2022 .
- ^ "La declaración ON" (PDF) . Sistema operativo IBM System/360, especificaciones del lenguaje PL/I (PDF) . IBM. Julio de 1966. pág. 120. C28-6571-3.
- ^ Gabriel y Steele 2008, pág. 3.
- ^ Blanco 1979, pág. 194.
- ^ desde Stroustrup 1994, pág. 392.
- ^ "Excepciones - Documentación para el manejo de excepciones en Perl".
- ^ Stroustrup, Bjarne. "Preguntas frecuentes sobre técnicas y estilos de C++". www.stroustrup.com . Consultado el 12 de enero de 2022 .
- ^ abcd Weimer, W; Necula, GC (2008). "Situaciones excepcionales y confiabilidad de programas" (PDF) . ACM Transactions on Programming Languages and Systems . Vol. 30, no. 2. Archivado (PDF) desde el original el 23 de septiembre de 2015.
- ^ Roberts, Eric S. (21 de marzo de 1989). "Implementación de excepciones en C" (PDF) . DEC Systems Research Center . SRC-RR-40 . Consultado el 4 de enero de 2022 .
{{cite journal}}: Requiere citar revista|journal=( ayuda ) - ^ Christiansen, Tom; Torkington, Nathan (2003). "10.12. Manejo de excepciones". Libro de recetas de Perl (2.ª ed.). Pekín: O'Reilly. ISBN 0-596-00313-7.
- ^ Stroustrup 1994, 16.6 Manejo de excepciones: reanudación versus terminación, págs. 390–393.
- ^ "R: Manejo de condiciones y recuperación". search.r-project.org . Consultado el 5 de diciembre de 2022 .
- ^ Scott Meyers , El software C++ más importante... de todos los tiempos Archivado el 28 de abril de 2011 en Wayback Machine , 2006
- ^ D. Cameron, P. Faust, D. Lenkov, M. Mehta, "Una implementación portátil del manejo de excepciones de C++", Actas de la Conferencia de C++ (agosto de 1992) USENIX .
- ^ Peter Kleissner (14 de febrero de 2009). «Manejo de excepciones de Windows: Peter Kleissner». Archivado desde el original el 14 de octubre de 2013. Consultado el 21 de noviembre de 2009 .Sección de manejo de excepciones estructuradas basado en compilador
- ^ Graham Hutton, Joel Wright, "Compiling Exceptions Correctly Archivado el 11 de septiembre de 2014 en Wayback Machine ". Actas de la 7.ª Conferencia internacional sobre matemáticas de la construcción de programas , 2004.
- ^ Lajoie, Josée (marzo-abril de 1994). "Manejo de excepciones: soporte del mecanismo de ejecución". C++ Report . 6 (3).
- ^ ab Schilling, Jonathan L. (agosto de 1998). "Optimizando el manejo de excepciones en C++". SIGPLAN Notices . 33 (8): 40–47. doi : 10.1145/286385.286390 . S2CID 1522664.
- ^ "Prácticas recomendadas de C++ moderno para excepciones y manejo de errores". Microsoft . 8 de marzo de 2021 . Consultado el 21 de marzo de 2022 .
- ^ ab Stroustrup, Bjarne (18 de noviembre de 2019). "Excepciones y alternativas de C++" (PDF) . Consultado el 23 de marzo de 2022 .
- ^ M. Hof, H. Mössenböck, P. Pirkelbauer, "Manejo de excepciones sin sobrecarga mediante metaprogramación Archivado el 3 de marzo de 2016 en Wayback Machine ", Actas SOFSEM'97 , noviembre de 1997, Lecture Notes in Computer Science 1338 , págs. 423-431.
- ^ Todas las excepciones se manejan, Jim Wilcox, "Todas las excepciones se manejan". 22 de febrero de 2008.
- ^ Biblioteca para desarrolladores de Mac abc , "Excepciones no detectadas Archivado el 4 de marzo de 2016 en Wayback Machine "
- ^ MSDN , Evento AppDomain.UnhandledException Archivado el 4 de marzo de 2016 en Wayback Machine
- ^ El tutorial de Python , "8. Errores y excepciones Archivado el 1 de septiembre de 2015 en Wayback Machine "
- ^ "Prácticas Java -> Proporcionar un controlador de excepciones no detectadas". www.javapractices.com . Archivado desde el original el 9 de septiembre de 2016 . Consultado el 5 de mayo de 2018 .
- ^ PyMOTW (Módulo Python de la semana), "Manejo de excepciones Archivado el 15 de septiembre de 2015 en Wayback Machine "
- ^ "Google Answers: El origen de las excepciones comprobadas". Archivado desde el original el 6 de agosto de 2011. Consultado el 15 de diciembre de 2011 .
- ^ Especificación del lenguaje Java, capítulo 11.2. http://java.sun.com/docs/books/jls/third_edition/html/exceptions.html#11.2 Archivado el 8 de diciembre de 2006 en Wayback Machine.
- ^ Mössenböck, Hanspeter (25 de marzo de 2002). "C# avanzado: número variable de parámetros" (PDF) . Institut für Systemsoftware, Johannes Kepler Universität Linz, Fachbereich Informatik. pag. 32. Archivado (PDF) desde el original el 20 de septiembre de 2011 . Consultado el 5 de agosto de 2011 .
- ^ abc Eckel, Bruce (2006). Pensar en Java (4.ª ed.). Upper Saddle River, Nueva Jersey: Prentice Hall. pp. 347–348. ISBN 0-13-187248-6.
- ^ Gunnerson, Eric (9 de noviembre de 2000). "C# y especificaciones de excepciones". Archivado desde el original el 1 de enero de 2006.
- ^ por Bill Venners; Bruce Eckel (18 de agosto de 2003). "El problema con las excepciones comprobadas: una conversación con Anders Hejlsberg, parte II" . Consultado el 4 de enero de 2022 .
- ^ Juneau, Josh (31 de mayo de 2017). Recetas de Java 9: un enfoque de solución de problemas. Apress. p. 226. ISBN 978-1-4842-1976-8.
- ^ "Ventajas de las excepciones (Tutoriales de Java™: Clases esenciales: Excepciones)". Download.oracle.com. Archivado desde el original el 26 de octubre de 2011. Consultado el 15 de diciembre de 2011 .
- ^ "Excepciones no controladas: la controversia (Tutoriales de Java™: Clases esenciales: Excepciones)". Download.oracle.com. Archivado desde el original el 2011-11-17 . Consultado el 2011-12-15 .
- ^ Bloch 2001:178 Bloch, Joshua (2001). Guía eficaz del lenguaje de programación Java . Addison-Wesley Professional. ISBN 978-0-201-31005-4.
- ^ ab "Bruce Eckel's MindView, Inc: Does Java need Checked Exceptions?" [MindView, Inc. de Bruce Eckel: ¿Java necesita excepciones comprobadas?]. Mindview.net. Archivado desde el original el 5 de abril de 2002. Consultado el 15 de diciembre de 2011 .
- ^ Liskov, BH; Snyder, A. (noviembre de 1979). "Exception Handling in CLU" (PDF) . IEEE Transactions on Software Engineering . SE-5 (6): 546–558. doi :10.1109/TSE.1979.230191. S2CID 15506879 . Consultado el 19 de diciembre de 2021 .
- ^ "Modula-3 - Tipos de procedimientos". .cs.columbia.edu. 1995-03-08. Archivado desde el original el 2008-05-09 . Consultado el 2011-12-15 .
- ^ por Bjarne Stroustrup , El lenguaje de programación C++ , tercera edición, Addison Wesley , 1997. ISBN 0-201-88954-4 . págs. 375-380.
- ^ Reeves, JW (julio de 1996). "Diez pautas para especificaciones de excepciones". C++ Report . 8 (7).
- ^ Sutter, Herb (3 de marzo de 2010). "Informe de viaje: Reunión de normas ISO C++ de marzo de 2010". Archivado desde el original el 23 de marzo de 2010 . Consultado el 24 de marzo de 2010 .
- ^ "OcamlExc - Un analizador de excepciones no detectadas para Objective Caml". Caml.inria.fr. Archivado desde el original el 2011-08-06 . Consultado el 2011-12-15 .
- ^ "Excepciones asincrónicas en Haskell - Marlow, Jones, Moran (ResearchIndex)". Citeseer.ist.psu.edu. Archivado desde el original el 23 de febrero de 2011. Consultado el 15 de diciembre de 2011 .
- ^ Freund, Stephen N.; Mitchell, Mark P. "Excepciones asincrónicas seguras para Python" (PDF) . Consultado el 4 de enero de 2022 .
{{cite journal}}: Requiere citar revista|journal=( ayuda ) - ^ "Desuso de primitivas de subprocesos de Java". Java.sun.com. Archivado desde el original el 26 de abril de 2009. Consultado el 15 de diciembre de 2011 .
- ^ "Interrupciones (Tutoriales de Java™ > Clases Java esenciales > Concurrencia)". docs.oracle.com . Consultado el 5 de enero de 2022 .
- ^ Felker, Rich. «Cancelación de hilos y fugas de recursos». ewontfix.com . Consultado el 5 de enero de 2022 .
- ^ "R: Manejo de condiciones y recuperación". search.r-project.org . Consultado el 25 de marzo de 2024 .
- ^ ¿ De qué se tratan realmente las condiciones (excepciones)? (24 de marzo de 2008). "De qué se tratan realmente las condiciones (excepciones)". Danweinreb.org. Archivado desde el original el 1 de febrero de 2013. Consultado el 18 de septiembre de 2014 .
{{cite web}}: CS1 maint: URL no apta ( enlace ) - ^ "9.1 Conceptos del sistema de condiciones". Franz.com. 2022-07-25. Archivado desde el original el 2024-06-07 . Consultado el 2024-06-07 .
- ^ CAR Hoare. "Las viejas ropas del emperador". Conferencia del Premio Turing de 1980
- ^ "Preguntas frecuentes". Archivado desde el original el 3 de mayo de 2017. Consultado el 27 de abril de 2017. Creemos
que acoplar excepciones a una estructura de control, como en el caso del modismo try-catch-finally, da como resultado un código enrevesado. También tiende a alentar a los programadores a etiquetar demasiados errores comunes, como no poder abrir un archivo, como excepcionales.
- ^ Pánico y recuperación Archivado el 24 de octubre de 2013 en Wayback Machine , Go wiki
- ^ Bendersky, Eli (8 de agosto de 2018). "Sobre los usos y abusos de los pánicos en Go". Sitio web de Eli Bendersky . Consultado el 5 de enero de 2022.
La limitación específica es que la recuperación solo se puede llamar en un bloque de código de aplazamiento, que no puede devolver el control a un punto arbitrario, sino que solo puede realizar limpiezas y ajustar los valores de retorno de la función.
Obras citadas
- Gabriel, Richard P. ; Steele, Guy L. (2008). Un patrón de evolución del lenguaje (PDF) . LISP50: Celebrando el 50 aniversario de Lisp. pp. 1–10. doi :10.1145/1529966.1529967. ISBN 978-1-60558-383-9.
- Goodenough, John B. (1975a). Manejo estructurado de excepciones . Actas del 2º simposio ACM SIGACT-SIGPLAN sobre Principios de lenguajes de programación - POPL '75. págs. 204–224. doi :10.1145/512976.512997.
- Goodenough, John B. (1975). "Manejo de excepciones: problemas y una notación propuesta" (PDF) . Comunicaciones de la ACM . 18 (12): 683–696. CiteSeerX 10.1.1.122.7791 . doi :10.1145/361227.361230. S2CID 12935051.
- Stroustrup, Bjarne (1994). El diseño y la evolución de C++ (1.ª ed.). Reading, Mass.: Addison-Wesley. ISBN 0-201-54330-3.
- White, Jon L (mayo de 1979). NIL: una perspectiva (PDF) . Actas de la Conferencia de usuarios de Macsyma de 1979.