stringtranslate.com

Afirmación (desarrollo de software)

En programación de computadoras , específicamente cuando se usa el paradigma de programación imperativo , una aserción es un predicado (una función con valores booleanos sobre el espacio de estados , generalmente expresada como una proposición lógica usando las variables de un programa) conectado a un punto en el programa, que siempre debe evaluarse como verdadero en ese punto de la ejecución del código. Las aserciones pueden ayudar a un programador a leer el código, ayudar a un compilador a compilarlo o ayudar al programa a detectar sus propios defectos.

Para este último, algunos programas verifican las afirmaciones evaluando realmente el predicado mientras se ejecutan. Luego, si en realidad no es cierto (un error de aserción), el programa se considera a sí mismo roto y normalmente falla deliberadamente o lanza una excepción de error de aserción .

Detalles

El siguiente código contiene dos afirmaciones, x > 0y x > 1, y de hecho son verdaderas en los puntos indicados durante la ejecución:

x = 1 ; afirmar x > 0 ; x ++ ; afirmar x > 1 ;        

Los programadores pueden utilizar aserciones para ayudar a especificar programas y razonar sobre su corrección. Por ejemplo, una condición previa (una afirmación colocada al comienzo de una sección de código) determina el conjunto de estados bajo los cuales el programador espera que se ejecute el código. Una poscondición , colocada al final, describe el estado esperado al final de la ejecución. Por ejemplo: x > 0 { x++ } x > 1.

El ejemplo anterior utiliza la notación para incluir afirmaciones utilizadas por CAR Hoare en su artículo de 1969. [1] Esa notación no se puede utilizar en los principales lenguajes de programación existentes. Sin embargo, los programadores pueden incluir afirmaciones no verificadas utilizando la función de comentarios de su lenguaje de programación. Por ejemplo, en C++ :

x = 5 ; x = x + 1 ; // {x > 1}      

Las llaves incluidas en el comentario ayudan a distinguir este uso de un comentario de otros usos.

Las bibliotecas también pueden proporcionar funciones de afirmación. Por ejemplo, en C usando glibc con soporte C99:

#incluir <afirmar.h> int f ( vacío ) { int x = 5 ; x = x + 1 ; afirmar ( x > 1 ); }             

Varios lenguajes de programación modernos incluyen aserciones verificadas: declaraciones que se verifican en tiempo de ejecución o, a veces, de forma estática. Si una aserción se evalúa como falsa en tiempo de ejecución, se produce un error de aserción, lo que normalmente provoca que se cancele la ejecución. Esto llama la atención sobre el lugar en el que se detecta la inconsistencia lógica y puede ser preferible al comportamiento que de otro modo resultaría.

El uso de aserciones ayuda al programador a diseñar, desarrollar y razonar sobre un programa.

Uso

En lenguajes como Eiffel , las afirmaciones forman parte del proceso de diseño; Otros lenguajes, como C y Java , los utilizan sólo para comprobar suposiciones en tiempo de ejecución . En ambos casos, se puede comprobar su validez en tiempo de ejecución, pero normalmente también se pueden suprimir.

Afirmaciones en diseño por contrato.

Las aserciones pueden funcionar como una forma de documentación: pueden describir el estado que el código espera encontrar antes de ejecutarse (sus precondiciones ) y el estado en el que el código espera encontrarse cuando termine de ejecutarse ( poscondiciones ); también pueden especificar invariantes de una clase . Eiffel integra dichas afirmaciones en el lenguaje y las extrae automáticamente para documentar la clase. Esto forma una parte importante del método de diseño por contrato .

Este enfoque también es útil en lenguajes que no lo soportan explícitamente: la ventaja de usar declaraciones de aserción en lugar de aserciones en los comentarios es que el programa puede verificar las aserciones cada vez que se ejecuta; si la afirmación ya no es válida, se puede informar un error. Esto evita que el código no esté sincronizado con las afirmaciones.

Afirmaciones para verificación en tiempo de ejecución

Se puede utilizar una afirmación para verificar que una suposición hecha por el programador durante la implementación del programa sigue siendo válida cuando se ejecuta el programa. Por ejemplo, considere el siguiente código Java :

 int total = countNumberOfUsers (); if ( total % 2 == 0 ) { // el total es par } else { // el total es impar y no negativo afirmar total % 2 == 1 ; }                      

En Java , %es el operador restante ( módulo ), y en Java, si su primer operando es negativo, el resultado también puede ser negativo (a diferencia del módulo usado en matemáticas). Aquí, el programador ha asumido que totalno es negativo, de modo que el resto de una división con 2 siempre será 0 o 1. La afirmación hace explícita esta suposición: si countNumberOfUsersdevuelve un valor negativo, es posible que el programa tenga un error.

Una ventaja importante de esta técnica es que cuando se produce un error se detecta inmediata y directamente, en lugar de hacerlo más tarde a través de efectos a menudo oscuros. Dado que una falla de aserción generalmente informa la ubicación del código, a menudo se puede identificar el error sin realizar más depuraciones.

A veces, las afirmaciones también se colocan en puntos a los que se supone que la ejecución no debe llegar. Por ejemplo, las afirmaciones podrían colocarse en la defaultcláusula de la switchdeclaración en lenguajes como C , C++ y Java . Cualquier caso que el programador no maneje intencionalmente generará un error y el programa abortará en lugar de continuar silenciosamente en un estado erróneo. En D, dicha afirmación se agrega automáticamente cuando una switchdeclaración no contiene una defaultcláusula.

En Java , las aserciones han sido parte del lenguaje desde la versión 1.4. Los errores de aserción provocan que se genere un error AssertionErrorcuando el programa se ejecuta con los indicadores apropiados, sin los cuales se ignoran las declaraciones de aserción. En C , se agregan mediante el encabezado estándar assert.hque se define como una macro que señala un error en caso de falla, generalmente finalizando el programa. En C++ , tanto los encabezados como proporcionan la macro. assert (assertion) assert.hcassertassert

El peligro de las afirmaciones es que pueden causar efectos secundarios ya sea al cambiar los datos de la memoria o al cambiar la sincronización del hilo. Las afirmaciones deben implementarse con cuidado para que no causen efectos secundarios en el código del programa.

Las construcciones de aserción en un lenguaje permiten un desarrollo basado en pruebas (TDD) sencillo sin el uso de una biblioteca de terceros.

Afirmaciones durante el ciclo de desarrollo

Durante el ciclo de desarrollo , el programador normalmente ejecutará el programa con las aserciones habilitadas. Cuando se produce un error de aserción, se notifica inmediatamente al programador sobre el problema. Muchas implementaciones de aserciones también detendrán la ejecución del programa: esto es útil, ya que si el programa continúa ejecutándose después de que ocurra una violación de aserción, podría corromper su estado y hacer que la causa del problema sea más difícil de localizar. Usando la información proporcionada por la falla de aserción (como la ubicación de la falla y tal vez un seguimiento de la pila , o incluso el estado completo del programa si el entorno admite volcados de núcleo o si el programa se ejecuta en un depurador ), el programador generalmente puede arreglar el problema. Por tanto, las afirmaciones proporcionan una herramienta muy poderosa en la depuración.

Afirmaciones en el entorno de producción.

Cuando un programa se implementa en producción , las aserciones generalmente se desactivan para evitar cualquier sobrecarga o efectos secundarios que puedan tener. En algunos casos, las aserciones están completamente ausentes del código implementado, como en las aserciones de C/C++ a través de macros. En otros casos, como en Java, las aserciones están presentes en el código implementado y se pueden activar en el campo para su depuración. [2]

Las afirmaciones también se pueden utilizar para prometer al compilador que una determinada condición de borde no es realmente alcanzable, permitiendo así ciertas optimizaciones que de otro modo no serían posibles. En este caso, deshabilitar las afirmaciones podría reducir el rendimiento.

Afirmaciones estáticas

Las afirmaciones que se verifican en tiempo de compilación se denominan afirmaciones estáticas.

Las aserciones estáticas son particularmente útiles en la metaprogramación de plantillas en tiempo de compilación , pero también se pueden usar en lenguajes de bajo nivel como C al introducir código ilegal si (y solo si) la aserción falla. C11 y C++11 admiten afirmaciones estáticas directamente a través de static_assert. En versiones anteriores de C, se puede implementar una aserción estática, por ejemplo, así:

#define SASSERT(pred) switch(0){caso 0:caso pred:;}SASSERT ( CONDICIÓN BOOLEANA );   

Si la (BOOLEAN CONDITION)parte se evalúa como falsa, entonces el código anterior no se compilará porque el compilador no permitirá dos etiquetas de caso con la misma constante. La expresión booleana debe ser un valor constante en tiempo de compilación; por ejemplo, sería una expresión válida en ese contexto. Esta construcción no funciona en el ámbito del archivo (es decir, no dentro de una función) y, por lo tanto, debe incluirse dentro de una función.(sizeof(int)==4)

Otra forma popular [3] de implementar afirmaciones en C es:

static char const static_assertion [ ( CONDICIÓN BOOLEAN ) ? 1 : -1 ] = { '!' };            

Si la (BOOLEAN CONDITION)parte se evalúa como falsa, entonces el código anterior no se compilará porque es posible que las matrices no tengan una longitud negativa. Si, de hecho, el compilador permite una longitud negativa, entonces el byte de inicialización (la '!'parte) debería hacer que incluso los compiladores demasiado indulgentes se quejen. La expresión booleana debe ser un valor constante en tiempo de compilación; por ejemplo, (sizeof(int) == 4)sería una expresión válida en ese contexto.

Ambos métodos requieren un método para construir nombres únicos. Los compiladores modernos admiten una __COUNTER__definición de preprocesador que facilita la construcción de nombres únicos, al devolver números que aumentan monótonamente para cada unidad de compilación. [4]

D proporciona afirmaciones estáticas mediante el uso de static assert. [5]

Deshabilitar afirmaciones

La mayoría de los lenguajes permiten habilitar o deshabilitar afirmaciones globalmente y, a veces, de forma independiente. Las afirmaciones a menudo se habilitan durante el desarrollo y se deshabilitan durante las pruebas finales y en el momento del lanzamiento al cliente. No verificar las afirmaciones evita el costo de evaluarlas mientras (suponiendo que las afirmaciones estén libres de efectos secundarios ) sigue produciendo el mismo resultado en condiciones normales. En condiciones anormales, deshabilitar la verificación de aserciones puede significar que un programa que habría abortado continuará ejecutándose. A veces esto es preferible.

Algunos lenguajes, incluidos C , YASS y C++ , pueden eliminar completamente las afirmaciones en tiempo de compilación utilizando el preprocesador .

De manera similar, iniciar el intérprete de Python con "-O" (para "optimizar") como argumento hará que el generador de código Python no emita ningún código de bytes para las afirmaciones. [6]

Java requiere que se pase una opción al motor de tiempo de ejecución para habilitar las aserciones. En ausencia de la opción, las aserciones se omiten, pero siempre permanecen en el código a menos que un compilador JIT las optimice en tiempo de ejecución o las excluya en tiempo de compilación a través de que el programador coloque manualmente cada aserción detrás de una if (false)cláusula.

Los programadores pueden incorporar comprobaciones en su código que siempre están activas evitando o manipulando los mecanismos normales de verificación de afirmaciones del lenguaje.

Comparación con el manejo de errores

Las afirmaciones son distintas del manejo de errores de rutina . Las afirmaciones documentan situaciones lógicamente imposibles y descubren errores de programación: si ocurre lo imposible, entonces es evidente que algo fundamental está mal en el programa. Esto es distinto del manejo de errores: la mayoría de las condiciones de error son posibles, aunque es extremadamente improbable que algunas ocurran en la práctica. No es aconsejable utilizar aserciones como mecanismo de manejo de errores de propósito general: las aserciones no permiten la recuperación de errores; un error en la aserción normalmente detendrá abruptamente la ejecución del programa; y las afirmaciones a menudo están deshabilitadas en el código de producción. Las afirmaciones tampoco muestran un mensaje de error fácil de usar.

Considere el siguiente ejemplo del uso de una aserción para manejar un error:

 int * ptr = malloc ( tamaño de ( int ) * 10 ); afirmar ( ptr ); // usar ptr ...        

Aquí, el programador es consciente de que mallocdevolverá un NULLpuntero si no se asigna memoria. Esto es posible: el sistema operativo no garantiza que todas las llamadas mallocsean exitosas. Si ocurre un error de falta de memoria, el programa abortará inmediatamente. Sin la afirmación, el programa continuaría ejecutándose hasta que ptrse eliminara la referencia, y posiblemente por más tiempo, dependiendo del hardware específico que se esté utilizando. Mientras las afirmaciones no estén deshabilitadas, se garantiza una salida inmediata. Pero si se desea un fallo elegante, el programa tiene que manejar el fallo. Por ejemplo, un servidor puede tener varios clientes, o puede contener recursos que no se liberarán limpiamente, o puede tener cambios no confirmados para escribir en un almacén de datos. En tales casos, es mejor fallar en una sola transacción que abortar abruptamente.

Otro error es confiar en los efectos secundarios de las expresiones utilizadas como argumentos de una afirmación. Siempre se debe tener en cuenta que las afirmaciones pueden no ejecutarse en absoluto, ya que su único propósito es verificar que una condición que siempre debería ser verdadera, de hecho lo es. En consecuencia, si se considera que el programa está libre de errores y se publica, las aserciones pueden deshabilitarse y ya no se evaluarán.

Considere otra versión del ejemplo anterior:

 int * ptr ; // La siguiente declaración falla si malloc() devuelve NULL, // ¡pero no se ejecuta en absoluto cuando se compila con -NDEBUG! afirmar ( ptr = malloc ( tamaño de ( int ) * 10 )); // usa ptr: ¡ptr no se inicializa al compilar con -NDEBUG! ...          

Esto podría parecer una forma inteligente de asignar el valor de retorno de malloca ptry verificar si está NULLen un solo paso, pero la mallocllamada y la asignación a ptres un efecto secundario de evaluar la expresión que forma la assertcondición. Cuando el NDEBUGparámetro se pasa al compilador, como cuando se considera que el programa está libre de errores y se publica, la assert()declaración se elimina, por lo que malloc()no se llama y se deja ptrsin inicializar. Potencialmente, esto podría resultar en una falla de segmentación o un error similar de puntero nulo mucho más adelante en la ejecución del programa, causando errores que pueden ser esporádicos y/o difíciles de rastrear. Los programadores a veces usan una definición VERIFY(X) similar para aliviar este problema.

Los compiladores modernos pueden emitir una advertencia al encontrar el código anterior. [7]

Historia

En informes de 1947 de von Neumann y Goldstine [8] sobre su diseño para la máquina IAS , describieron algoritmos usando una versión temprana de diagramas de flujo , en los que incluían afirmaciones: "Puede ser cierto que siempre que C realmente alcance un cierto punto en el diagrama de flujo, una o más variables ligadas necesariamente poseerán ciertos valores específicos, o poseerán ciertas propiedades, o satisfarán ciertas propiedades entre sí. Además, en ese punto podemos indicar la validez de estas limitaciones. Denotaremos cada área en la que se afirma la validez de tales limitaciones mediante una casilla especial, que llamamos casilla de afirmación".

Alan Turing defendió el método afirmativo para demostrar la corrección de los programas . En una charla sobre "Comprobación de una gran rutina" en Cambridge, el 24 de junio de 1949, Turing sugirió: "¿Cómo se puede comprobar una gran rutina en el sentido de asegurarse de que es correcta? Para que el hombre que la comprueba no tenga dificultades demasiado difíciles". tarea, el programador debe hacer una serie de afirmaciones definidas que puedan comprobarse individualmente y de las que se deduzca fácilmente la corrección de todo el programa". [9]

Ver también

Referencias

  1. CAR Hoare , Una base axiomática para la programación informática, Comunicaciones de la ACM , 1969.
  2. ^ Programación con aserciones, habilitación y deshabilitación de aserciones
  3. ^ Jon Jagger, Afirmaciones de tiempo de compilación en C , 1999.
  4. ^ GNU, "Serie de lanzamientos GCC 4.3: cambios, nuevas funciones y correcciones"
  5. ^ "Afirmaciones estáticas". Referencia del lenguaje D. La Fundación del Lenguaje D. Consultado el 16 de marzo de 2022 .
  6. ^ Documentos oficiales de Python, declaración de afirmación
  7. ^ "Opciones de advertencia (uso de la colección de compiladores GNU (GCC))".
  8. ^ Goldstine y von Neumann. "Planificación y Codificación de problemas para un Instrumento de Computación Electrónica" Archivado el 12 de noviembre de 2018 en Wayback Machine . Parte II, Volumen I, 1 de abril de 1947, pág. 12.
  9. ^ Alan Turing. Comprobación de una gran rutina, 1949; citado en CAR Hoare, "The Emperor's Old Clothes", conferencia del Premio Turing de 1980.

enlaces externos