Aserción (desarrollo de software)
En la programación de computadoras, específicamente cuando se usa el paradigma de programación imperativa, una afirmación es un predicado (una función de valor booleano sobre el espacio de estado, 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 en 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 lo último, algunos programas comprueban las aserciones evaluando el predicado mientras se ejecutan. Entonces, si de hecho no es cierto (un error de aserción), el programa se considera que está roto y, por lo general, se bloquea deliberadamente o genera una excepción de error de aserción.
Detalles
El siguiente código contiene dos aserciones, x > 0
y x > 1, y de hecho son verdaderas en los puntos indicados durante la ejecución:
x = 1;afirmación x ■ 0;x++;afirmación x ■ 1;
Los programadores pueden usar aserciones para ayudar a especificar programas y razonar acerca de la corrección del programa. 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 condición posterior, colocada al final, describe el estado esperado al final de la ejecución. Por ejemplo: x > 0 { x++ } x > 1.
El ejemplo anterior usa la notación para incluir aserciones usada por C. A. R. Hoare en su artículo de 1969. 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 }
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 aserción. Por ejemplo, en C usando glibc con soporte C99:
#include - No.int f()vacío){} int x = 5; x = x + 1; afirmación()x ■ 1);}
Varios lenguajes de programación modernos incluyen aserciones verificadas: declaraciones que se verifican en tiempo de ejecución o, a veces, estáticamente. Si una aserción se evalúa como falsa en tiempo de ejecución, se produce un error de aserción, lo que normalmente hace que se cancele la ejecución. Esto llama la atención sobre la ubicación en la que se detecta la incoherencia lógica y puede ser preferible al comportamiento que se produciría de otro modo.
El uso de afirmaciones 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 usan solo para verificar suposiciones en tiempo de ejecución. En ambos casos, se puede verificar su validez en tiempo de ejecución, pero generalmente 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 condiciones previas) y el estado en el que el código espera que resulte cuando termine de ejecutarse (poscondiciones); también pueden especificar invariantes de una clase. Eiffel integra tales 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 afirmaciones en lugar de afirmaciones en los comentarios es que el programa puede comprobar las afirmaciones cada vez que se ejecuta; si la afirmación ya no se cumple, se puede informar un error. Esto evita que el código no esté sincronizado con las aserciones.
Afirmaciones para verificación en tiempo de ejecución
Se puede usar una aserció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(); si ()total % 2 == 0) {} // total es incluso } más {} // total es extraño y no negativo afirmación total % 2 == 1; }
En Java, %
es el operador resto (módulo), y en Java, si su primer operando es negativo, el resultado puede también ser negativo (a diferencia del módulo usado en matemáticas). Aquí, el programador ha supuesto que total
no es negativo, por lo que el resto de una división con 2 siempre será 0 o 1. La afirmación hace explícita esta suposición: if countNumberOfUsers
devuelve un valor negativo, el programa puede tener un error.
Una de las principales ventajas de esta técnica es que, cuando se produce un error, se detecta de forma inmediata y directa, 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 más depuración.
A veces, las aserciones también se colocan en puntos que se supone que la ejecución no debe alcanzar. Por ejemplo, las aserciones podrían colocarse en la cláusula default
de la instrucción switch
en lenguajes como C, C++ y Java. Cualquier caso que el programador no maneje intencionalmente generará un error y el programa se cancelará en lugar de continuar silenciosamente en un estado erróneo. En D, dicha aserción se agrega automáticamente cuando una instrucción switch
no contiene una cláusula default
.
En Java, las aserciones han sido parte del lenguaje desde la versión 1.4. Los errores de aserción generan un AssertionError
cuando el programa se ejecuta con las banderas apropiadas, sin las cuales se ignoran las declaraciones de aserción. En C, se agregan con el encabezado estándar assert.h
que define assert (asertion)
como una macro que señala un error en el caso de falla, generalmente terminando el programa. En C++, los encabezados assert.h
y cassert
proporcionan la macro assert
.
El peligro de las aserciones es que pueden causar efectos secundarios ya sea al cambiar los datos de la memoria o al cambiar la sincronización del subproceso. Las aserciones deben implementarse con cuidado para que no causen efectos secundarios en el código del programa.
Las construcciones de afirmación en un lenguaje permiten un desarrollo basado en pruebas (TDD) fácil 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 ocurre una falla de aserción, el programador es notificado inmediatamente del 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. Con la información proporcionada por el error de aserción (como la ubicación del error y quizás un seguimiento de la pila, o incluso el estado completo del programa si el entorno admite volcados del núcleo o si el programa se ejecuta en un depurador), el programador generalmente puede corregir el problema. Por lo tanto, las aserciones proporcionan una herramienta muy poderosa en la depuración.
Afirmaciones en 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 Java, las aserciones están presentes en el código implementado y se pueden activar en el campo para la depuración.
Las aserciones también se pueden usar para prometer al compilador que una condición de borde determinada no es realmente alcanzable, lo que permite 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 de tiempo de compilación, pero también se pueden usar en lenguajes de bajo nivel como C mediante la introducción de código ilegal si (y solo si) la aserción falla. C11 y C++11 admiten aserciones 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){case 0:case pred:;}SASSERT() BOOLEAN CONDICIÓN );
Si la parte (BOOLEAN CONDITION)
se evalúa como falsa, entonces el código anterior no se compilará porque el compilador no permitirá dos etiquetas de casos con la misma constante. 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. Esta construcción no funciona en el ámbito del archivo (es decir, no dentro de una función), por lo que debe envolverse dentro de una función.
Otra forma popular de implementar aserciones en C es:
estática char const static_assertion[ ()BOOLEAN CONDICIÓN) ? 1 : -1 ] = {}'! '};
Si la parte (BOOLEAN CONDITION)
se evalúa como falsa, entonces el código anterior no se compilará porque las matrices pueden no tener 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 permisivos 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 de construcción de nombres únicos. Los compiladores modernos admiten una definición de preprocesador __COUNTER__
que facilita la construcción de nombres únicos, al devolver números crecientes monótonamente para cada unidad de compilación.
D proporciona aserciones estáticas mediante el uso de aserción estática
.
Deshabilitar aserciones
La mayoría de los idiomas permiten habilitar o deshabilitar aserciones globalmente y, a veces, de forma independiente. Las aserciones a menudo se activan durante el desarrollo y se desactivan durante las pruebas finales y en el momento de la entrega al cliente. No verificar las afirmaciones evita el costo de evaluar las afirmaciones mientras (suponiendo que las afirmaciones no tengan 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 se habría abortado continuará ejecutándose. Esto es a veces preferible.
Algunos lenguajes, incluidos C y C++, pueden eliminar por completo las aserciones en tiempo de compilación mediante el preprocesador.
Del mismo modo, iniciar el intérprete de Python con "-O" (para "optimizar") como argumento hará que el generador de código de Python no emita ningún código de bytes para afirmaciones.
Java requiere que se pase una opción al motor de tiempo de ejecución para habilitar aserciones. En ausencia de la opción, las afirmaciones 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 del programador colocando manualmente cada afirmación detrás de un if (falso)
cláusula.
Los programadores pueden crear verificaciones en su código que siempre estén activas omitiendo o manipulando los mecanismos normales de verificación de afirmaciones del lenguaje.
Comparación con el manejo de errores
Las aserciones son distintas del manejo de errores de rutina. Las aserciones documentan situaciones lógicamente imposibles y descubren errores de programación: si ocurre lo imposible, entonces algo fundamental está claramente mal con el programa. Esto es distinto del manejo de errores: la mayoría de las condiciones de error son posibles, aunque es muy poco probable que algunas ocurran en la práctica. El uso de aserciones como un mecanismo de manejo de errores de propósito general es imprudente: las aserciones no permiten la recuperación de errores; una falla de aserción normalmente detendrá la ejecución del programa abruptamente; y las aserciones a menudo están deshabilitadas en el código de producción. Las aserciones 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()int) * 10); afirmación()ptr); // use ptr ...
Aquí, el programador es consciente de que malloc
devolverá un puntero NULL si no se asigna memoria. Esto es posible: el sistema operativo no garantiza que todas las llamadas a malloc
tengan éxito. Si ocurre un error de falta de memoria, el programa se cancelará inmediatamente. Sin la afirmación, el programa continuaría ejecutándose hasta que se elimine la referencia a ptr
, y posiblemente más tiempo, según el hardware específico que se utilice. Siempre que las aserciones no estén deshabilitadas, se garantiza una salida inmediata. Pero si se desea una falla elegante, el programa tiene que manejar la falla. 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 sea. En consecuencia, si se considera que el programa está libre de errores y se libera, las aserciones pueden desactivarse y ya no se evaluarán.
Considere otra versión del ejemplo anterior:
int *ptr; // Declaración abajo falla si malloc() devuelve NULL, // pero no se ejecuta en absoluto cuando compilando con - NDEBUG! afirmación()ptr = malloc()tamaño()int) * 10)); // use ptr: ptr no se inicializa al compilar con -NDEBUG! ...
Esto podría parecer una forma inteligente de asignar el valor de retorno de malloc
a ptr
y verificar si es NULL
en un solo paso, pero la llamada malloc
y la asignación a ptr
es un efecto secundario de evaluar la expresión que forma la condición assert
. Cuando el parámetro NDEBUG
se pasa al compilador, como cuando se considera que el programa está libre de errores y se libera, se elimina la sentencia assert()
, por lo que malloc()
no se llama, por lo que ptr
no se inicializa. Esto podría resultar potencialmente en una falla de segmentación o un error de puntero nulo similar 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 cuando se encuentran con el código anterior.
Historia
En los informes de 1947 de von Neumann y Goldstine sobre su diseño para la máquina IAS, describieron algoritmos utilizando una versión anterior de diagramas de flujo, en los que incluían afirmaciones: "Puede ser cierto que siempre que C realmente alcanza 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, podemos, en tal punto, indicar la validez de estas limitaciones. Por esta razón, indicaremos cada área en la que se afirma la validez de dichas limitaciones, mediante un recuadro especial, que llamamos recuadro de afirmación."
El método asertivo para probar la corrección de los programas fue defendido por Alan Turing. En una charla "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 verifica no tenga una tarea demasiado difícil, el programador debe hacer una serie de afirmaciones definidas que pueden verificarse individualmente, y de las cuales se sigue fácilmente la corrección de todo el programa.;.
Contenido relacionado
Transporte en Angola
Visión por computador
Controlabilidad de la red