En programación informática , específicamente al usar el paradigma de programación imperativa , una aserción es un predicado (una función booleana sobre el espacio de estados , generalmente expresada como una proposición lógica mediante las variables del programa) conectado a un punto del programa, que siempre debe evaluarse como verdadero en ese punto de la ejecución del código. Las aserciones pueden ayudar al programador a leer el código, al compilador a compilarlo o al programa a detectar sus propios errores.
En este último caso, algunos programas comprueban las aserciones evaluando el predicado durante su ejecución. Si, por el contrario, no es verdadero (un fallo de aserción), el programa se considera defectuoso y, por lo general, se bloquea deliberadamente o lanza una excepción de fallo de aserción .
Detalles
El siguiente código contiene dos aserciones, x > 0y x > 1, y de hecho son verdaderas en los puntos indicados durante la ejecución:
int x = 1 ; assert x > 0 ; x ++ ; assert x > 1 ;Los programadores pueden usar aserciones para ayudar a especificar programas y razonar sobre la corrección del programa. Por ejemplo, una precondición —una aserción colocada al principio 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 postcondició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 aserciones empleada por CAR Hoare en su artículo de 1969. [ 1 ] Dicha notación no puede utilizarse en los lenguajes de programación convencionales actuales. Sin embargo, los programadores pueden incluir aserciones 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 del comentario de otros usos.
Las bibliotecas también pueden proporcionar funciones de aserción. Por ejemplo, en C usando glibc con soporte para C99:
#incluir <assert.h>int f ( void ) { int x = 5 ; x = x + 1 ; assert ( x > 1 ); }Varios lenguajes de programación modernos incluyen aserciones verificadas: instrucciones que se comprueban en tiempo de ejecución o, en ocasiones, de forma estática. Si una aserción se evalúa como falsa en tiempo de ejecución, se produce un fallo de aserción, lo que normalmente provoca la interrupción de la ejecución. Esto resalta el punto donde se detecta la inconsistencia lógica y puede ser preferible al comportamiento que se produciría de otro modo.
El uso de aserciones ayuda al programador a diseñar, desarrollar y razonar sobre un programa.
Uso
En lenguajes como Eiffel , las aserciones forman parte del proceso de diseño; otros lenguajes, como C y Java , las utilizan únicamente para comprobar supuestos 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 el diseño por contrato
Las aserciones pueden funcionar como una forma de documentación: describen el estado que el código espera encontrar antes de ejecutarse (sus precondiciones ) y el estado que espera obtener al finalizar su ejecución ( postcondiciones ); también pueden especificar invariantes de una clase . Eiffel integra estas aserciones en el lenguaje y las extrae automáticamente para documentar la clase. Esto constituye una parte importante del método de diseño por contrato .
Este enfoque también resulta útil en lenguajes que no lo admiten explícitamente: la ventaja de usar aserciones en lugar de comentarios es que el programa puede verificarlas cada vez que se ejecuta; si la aserción deja de ser válida, se puede informar de un error. Esto evita que el código se desincronice con las aserciones.
Por ejemplo, a continuación se muestra el diseño por contrato en C++ (utilizando contratos C++26 ). [ 2 ] [ 3 ]
int f ( const int x ) pre ( x != 1 ) // una aserción de precondición post ( r : r == x && r != 2 ) // una aserción de postcondición; r nombra el objeto resultante de f { contract_assert ( x != 3 ); // una declaración de aserción return x ; }Aserciones para la verificación en tiempo de ejecución
Una aserción puede utilizarse para verificar que una suposición realizada por el programador durante la implementación del programa sigue siendo válida cuando este se ejecuta. Por ejemplo, considere el siguiente código Java :
int total = countNumberOfUsers (); if ( total % 2 == 0 ) { // total es par } else { // total es impar y no negativo assert total % 2 == 1 ; }En Java , %`m` es el operador de resto ( módulo ), y en Java, si su primer operando es negativo, el resultado también puede ser negativo (a diferencia del módulo utilizado en matemáticas). Aquí, el programador ha asumido que total`m` es no negativo, de modo que el resto de una división por 2 siempre será 0 o 1. La aserción hace explícita esta suposición: si countNumberOfUsers`m` devuelve un valor negativo, el programa podría tener un error.
Una gran ventaja de esta técnica es que, cuando se produce un error, se detecta de forma inmediata y directa, en lugar de posteriormente a través de efectos a menudo imperceptibles. Dado que un fallo de aserción suele indicar la ubicación del código, a menudo se puede localizar el error sin necesidad de depurar más a fondo.
Las aserciones también se colocan a veces en puntos donde la ejecución no debería llegar. Por ejemplo, en lenguajes como C , C++ y Javadefault , las aserciones podrían colocarse en la cláusula de la instrucción . Cualquier caso que el programador no maneje intencionalmente generará un error, y el programa se interrumpirá en lugar de continuar silenciosamente en un estado erróneo. En D, dicha aserción se agrega automáticamente cuando una instrucción no contiene una cláusula.switchswitchdefault
En Java , las aserciones forman parte del lenguaje desde la versión 1.4. Los fallos de aserción generan una excepción AssertionErrorcuando el programa se ejecuta con las banderas apropiadas; sin ellas, las sentencias de aserción se ignoran. En C , se añaden mediante el encabezado estándar que define como una macro que señala un error en caso de fallo, generalmente terminando el programa. En C++ , tanto el encabezado como proporcionan la macro.<assert.h>assert(assertion)<assert.h><cassert>assert
El peligro de las aserciones radica en que pueden provocar efectos secundarios, ya sea modificando los datos de memoria o la temporización de los hilos. Por lo tanto, deben implementarse con cuidado para evitar efectos secundarios en el código del programa.
Las estructuras de aserción en un lenguaje permiten un desarrollo guiado por pruebas (TDD) sencillo sin necesidad de utilizar una biblioteca de terceros.
En C# , no existe una macro o palabra clave de aserción, sino clases System.Diagnostics.Debugque System.Diagnostics.Traceproporcionan Assert()métodos.
En Rust , existe una assert!()macro.
Afirmaciones durante el ciclo de desarrollo
Durante el ciclo de desarrollo , el programador suele ejecutar el programa con las aserciones habilitadas. Cuando se produce un fallo en una aserción, el programador recibe una notificación inmediata del problema. Muchas implementaciones de aserciones también detienen la ejecución del programa: esto resulta útil, ya que si el programa continuara ejecutándose tras una infracción de aserción, podría corromper su estado y dificultar la localización de la causa del problema. Utilizando la información proporcionada por el fallo de la aserción (como la ubicación del fallo y, posiblemente, un rastreo de pila , o incluso el estado completo del programa si el entorno admite volcados de memoria o si el programa se ejecuta en un depurador ), el programador suele poder solucionar el problema. Por lo tanto, las aserciones constituyen una herramienta muy potente para la depuración.
Afirmaciones en el entorno de producción
Cuando un programa se implementa en producción , las aserciones suelen desactivarse para evitar cualquier sobrecarga o efecto secundario que puedan tener. En algunos casos, las aserciones están completamente ausentes del código implementado, como en las aserciones de C/C++ mediante macros. En otros casos, como en Java, las aserciones están presentes en el código implementado y pueden activarse en el entorno de producción para la depuración. [ 4 ]
Las aserciones también pueden utilizarse para garantizar al compilador que una determinada condición límite no es alcanzable, lo que permite ciertas optimizaciones que de otro modo no serían posibles. En este caso, deshabilitar las aserciones podría, de hecho, reducir el rendimiento.
Aserciones estáticas
Las aserciones que se comprueban en tiempo de compilación se denominan aserciones 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 introduciendo 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, una aserción estática se puede implementar, por ejemplo, de esta manera:
#define SASSERT(pred) switch(0){case 0:case pred:;}SASSERT ( CONDICIÓN BOOLEANA );Si la (BOOLEAN CONDITION)parte se evalúa como falsa, el código anterior no compilará porque el compilador no permite 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, fuera de una función), por lo que debe estar encapsulada dentro de una función.(sizeof(int)==4)
Otra forma popular [ 5 ] de implementar aserciones en C es:
static char const static_assertion [ ( BOOLEAN CONDITION ) ? 1 : -1 ] = { '!' };Si la (BOOLEAN CONDITION)parte se evalúa como falsa, el código anterior no compilará porque los arreglos no pueden tener una longitud negativa. Si, de hecho, el compilador permite una longitud negativa, el byte de inicialización (la '!'parte) debería provocar un error incluso en los compiladores más permisivos. 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. [ 6 ]
D proporciona aserciones estáticas mediante el uso de static assert. [ 7 ]
Deshabilitar aserciones
La mayoría de los lenguajes permiten habilitar o deshabilitar las aserciones globalmente, y a veces de forma independiente. Las aserciones suelen habilitarse durante el desarrollo y deshabilitarse durante las pruebas finales y al entregar el producto al cliente. No verificar las aserciones evita el costo de evaluarlas y, suponiendo que no tengan efectos secundarios , se obtiene 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 continúe ejecutándose. Esto a veces es preferible.
Algunos lenguajes, incluidos C , YASS y C++ , pueden eliminar completamente las aserciones en tiempo de compilación utilizando el preprocesador .
De manera similar, iniciar el intérprete de Python con " -O " (de "optimizar") como argumento hará que el generador de código Python no emita ningún código de bytes para las aserciones. [ 8 ]
Java requiere que se pase una opción al motor de ejecución para habilitar las aserciones. Si no se especifica esta 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 que el programador las excluya manualmente en tiempo de compilación, colocando cada aserción detrás de una if (false)cláusula.
Los programadores pueden incorporar comprobaciones en su código que permanezcan siempre activas, eludiendo o manipulando los mecanismos normales de comprobación de aserciones del lenguaje.
Comparación con el manejo de errores
Las aserciones se distinguen del manejo rutinario de errores . Las aserciones documentan situaciones lógicamente imposibles y detectan errores de programación: si ocurre lo imposible, entonces es evidente que hay un problema fundamental con el programa. Esto se diferencia del manejo de errores: la mayoría de las condiciones de error son posibles, aunque algunas pueden ser extremadamente improbables en la práctica. Usar aserciones como mecanismo general de manejo de errores no es recomendable: las aserciones no permiten la recuperación de errores; un fallo en una aserción normalmente detendrá la ejecución del programa abruptamente; y las aserciones suelen estar deshabilitadas en el código de producción. Además, las aserciones no muestran un mensaje de error fácil de usar .
Consideremos el siguiente ejemplo de cómo usar una aserción para manejar un error:
int * ptr = ( int * ) malloc ( sizeof ( int ) * 10 ); assert ( ptr ); // usar ptr ...Aquí, el programador sabe que mallocdevolverá un NULLpuntero si no se asigna memoria. Esto es posible: el sistema operativo no garantiza que cada llamada a malloctenga éxito. Si se produce un error de falta de memoria, el programa se interrumpirá inmediatamente. Sin la aserción, el programa continuaría ejecutándose hasta ptrque se desreferenciara, y posiblemente durante más tiempo, dependiendo del hardware específico que se utilice. Mientras las aserciones no estén deshabilitadas, se garantiza una salida inmediata. Pero si se desea un fallo controlado, el programa debe gestionar el fallo. Por ejemplo, un servidor puede tener varios clientes, o puede contener recursos que no se liberarán correctamente, o puede tener cambios sin confirmar para escribir en un almacén de datos. En tales casos, es mejor que falle una sola transacción que interrumpirse abruptamente.
Otro error consiste en confiar en los efectos secundarios de las expresiones utilizadas como argumentos de una aserción. Siempre hay que tener en cuenta que las aserciones podrían no ejecutarse, ya que su único propósito es verificar que una condición que debería ser siempre verdadera, de hecho lo es. Por consiguiente, si el programa se considera libre de errores y se publica, las aserciones podrían deshabilitarse y dejarían de evaluarse.
Consideremos otra versión del ejemplo anterior:
int * ptr ; // La instrucción siguiente falla si malloc() devuelve NULL, // pero no se ejecuta en absoluto al compilar con -NDEBUG! assert ( ptr = ( int * ) malloc ( sizeof ( int ) * 10 )); // usar ptr: ptr no se inicializa al compilar con -NDEBUG! ...Esto podría parecer una forma inteligente de asignar el valor de retorno de mallocy ptrcomprobar 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 el programa se considera libre de errores y se libera, la assert()instrucción se elimina, por lo que malloc()no se llama, lo que resulta en ptrno inicializado. Esto podría potencialmente resultar en un fallo 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 mitigar este problema.
Los compiladores modernos pueden emitir una advertencia al encontrar el código anterior. [ 9 ]
Historia
En 1947, en sus informes [ 10 ] sobre el diseño de la máquina IAS , von Neumann y Goldstine describieron algoritmos que utilizaban una versión temprana de diagramas de flujo , en los que incluían afirmaciones: «Puede ser cierto que, siempre que C alcance un punto determinado del diagrama de flujo, una o más variables ligadas poseerán necesariamente ciertos valores específicos, o ciertas propiedades, o satisfarán ciertas propiedades entre sí. Además, en ese punto, podemos indicar la validez de estas limitaciones. Por ello, representaremos cada área en la que se afirma la validez de dichas limitaciones con una casilla especial, a la que llamamos casilla de afirmación».
El método asertivo para demostrar la corrección de los programas fue defendido por Alan Turing . En una charla titulada "Verificando una rutina extensa" en Cambridge, el 24 de junio de 1949, Turing sugirió: "¿Cómo se puede verificar una rutina extensa para asegurar que sea correcta? Para que la tarea de verificación no sea demasiado difícil, el programador debería realizar una serie de aserciones definidas que puedan verificarse individualmente y de las cuales se derive fácilmente la corrección de todo el programa". [ 11 ]
Véase también
Referencias
- ↑ Hoare, CAR (octubre de 1969). "Una base axiomática para la programación de computadoras" . Communications of the ACM . 12 (10): 576– 580. doi : 10.1145/363235.363259 . S2CID 207726175 .
- ^ Joshua Berne, Timur Doumler, Andrzej Krzemieński (13 de febrero de 2025). "Contratos para C++" (PDF) . open-std.org . Grupo de Trabajo 22.
{{cite web}}: CS1 maint: varios nombres: lista de autores ( enlace ) - ↑ "Aserciones de contrato (desde C++26)" . cppreference.com . cppreference . Consultado el 9 de noviembre de 2025 .
- ↑ Programación con aserciones , habilitación y deshabilitación de aserciones
- ↑ Jon Jagger, Aserciones en tiempo de compilación en C , 1999.
- ↑ GNU, "Serie de lanzamientos de GCC 4.3 : cambios, nuevas características y correcciones"
- ↑ "Aserciones estáticas" . Referencia del lenguaje D. La Fundación del lenguaje D. Consultado el 16 de marzo de 2022 .
- ↑ Documentación oficial de Python, instrucción assert
- ↑ "Opciones de advertencia (Uso de la colección de compiladores GNU (GCC))" .
- ↑ 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.
- ↑ Alan Turing. Checking a Large Routine , 1949; citado en CAR Hoare, "The Emperor's Old Clothes", conferencia del Premio Turing de 1980.
Enlaces externos
- Una perspectiva histórica sobre la verificación de aserciones en tiempo de ejecución en el desarrollo de software por Lori A. Clarke, David S. Rosenblum en: ACM SIGSOFT Software Engineering Notes 31(3):25-37, 2006
- Afirmaciones: una perspectiva personal por CAR Hoare en: IEEE Annals of the History of Computing, Volumen: 25, Número: 2 (2003), Páginas: 14–25
- Mi compilador no me entiende, por Poul-Henning Kamp, en: ACM Queue 10(5), mayo de 2012
- Uso de afirmaciones por John Regehr
- Métodos formales
- Lógica en informática
- Construcciones condicionales
- Depuración
- Pruebas de software