Aserción (informática)

De Wikipedia, la enciclopedia libre
Saltar a: navegación, búsqueda

En programación, una aserción es un predicado (i.e., una sentencia verdadero-falso) incluido en un programa como indicación de que el programador piensa que dicho predicado siempre se cumple en ese punto del flujo de programa.

Por ejemplo, el siguiente código contiene dos aserciones:

x := 5;
{x > 0}
x := x + 1
{x > 1}

x > 0 y x > 1, y las dos son ciertas en dichos puntos de la ejecución.

Las aserciones suelen ser útiles para especificar programas y para razonar la corrección de los mismos. Por ejemplo, una precondición — una aserción al comienzo de una sección de código — determina que se espera que el conjunto de sentencias que la siguen sean ejecutadas. Una postcondición — colocada al final — describe el estado que se espera alcanzar al final de la ejecución.

El ejemplo anterior utiliza la notación introducida por C. A. R. Hoare en su artículo de 1969 "An axiomatic basis for computer programming" ("Una base axiomática para la programación de ordenadores"). Como esta notación normalmente no es aceptada por los compiladores de los lenguajes actuales, los programadores suelen incluir aserciones mediante comentarios. A continuación se incluye un ejemplo en C:

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

Se han incluido los paréntesis para distinguir este uso de los comentarios de su uso habitual.

Varios lenguajes de programación modernos incluyen una sentencia de aserción, que no es más que una aserción que se comprueba en tiempo de ejecución. Si su evaluación resulta falsa, se produce un "error de aserción". La intención de estas sentencias de aserción es facilitar la depuración del programa, evitando que dicho fallo quede sin comprobar.

El uso de aserciones ayuda al programador en las tareas de diseño, desarrollo y razonamiento de un programa.

Uso de las aserciones[editar]

En lenguajes como Eiffel, las aserciones son parte del proceso de diseño, mientras que en otros, como C o Java, solamente son utilizadas para comprobar suposiciones en tiempo de ejecución.

Aserciones en el diseño por contrato[editar]

Las aserciones pueden ser una forma de documentación: pueden describir el estado en que el código empieza su ejecución (precondición), y el estado que el código espera alcanzar cuando finalice (postcondición); asimismo pueden servir de especificación para los invariantes de clase. En Eiffel, tales aserciones se integran en el código y son automáticamente extraídas para documentar la clase. Esto supone una parte importante del método de diseño por contrato.

Esta aproximación también resulta útil en lenguajes que no las soportan explícitamente: la ventaja de usar sentencias de aserción en lugar de aserciones en comentarios es que las primeras pueden ser comprobadas en cada ejecución; si la aserción no se cumple, puede informarse del error. Esto previene que el código y las aserciones se desfasen (un problema que puede ocurrir con las aserciones comentadas).

Aserciones en tiempo de ejecución[editar]

Una aserción puede ser utilizada para verificar que una suposición hecha por el programador durante la implementación del programa sigue siendo válida durante la ejecución del programa. Por ejemplo, considérese el siguiente código en Java:

 int total = contarUsuarios();
 if (total % 2 == 0)
 {
     // total es par
 } 
 else
 {
     // total es impar
     assert(total % 2 == 1);
 }

En Java, % es el operador resto (no módulo) — si el primer operando es negativo, el resultado puede ser también negativo, Aquí, el programador ha asumido que la variable total no es negativa, así que el resto de una división entre 2 siempre será 0 o 1. La aserción explicita esta suposición — si contarUsuarios devuelve un valor negativo, es probable que haya un fallo en el programa.

Una gran ventaja de esta técnica es que cuando se produce algún error éste es inmediata y directamente detectado, evitando posibles daños colaterales ocultos. Puesto que un error de aserción normalmente informa de la línea de código donde se produce, se puede localizar el error sin necesidad de una depuración más exhaustiva.

Las aserciones también suelen colocarse en puntos que se supone que no se alcanzan durante la ejecución. Por ejemplo, las aserciones pueden ser colocadas en la cláusula default de una estructura switch en lenguajes como C, C++ y Java. Los casos que intencionadamente no se manejan pueden provocar errores que aborten el programa.

En Java, las aserciones han sido parte del lenguaje desde la versión 1.4. Los errores de aserción resultan en AssertionError cuando el prorgama es ejecutado con los parámetros correctos, sin los cuales las sentencias de aserción son ignoradas. En C y C++, son añadidas por el fichero de cabeceras estándar assert.h definiendo assert (aserción) como una macro que devuelve un error en caso de fallo y finaliza el programa.

Las inclusión en los lenguajes de construcciones de aserción facilitan el desarrollo guiado por pruebas (Test-driven Development - TDD) al no necesitar de una librería independiente que implemente dichas aserciones.

Aserciones durante el ciclo de desarrollo[editar]

Durante el ciclo de desarrollo, el programador normalmente ejecuta su programa con las aserciones activadas. Cuando una aserción resulta falsa y se produce el correspondiente error, el programador automáticamente recibe un aviso. Muchas implementaciones además detienen la ejecución del programa — esto resulta útil ya que si el programa sigue ejecutándose tras la violación de una aserción, éste entra en un estado corrupto que puede hacer más difícil la localización del problema. Gracias a la información proporcionada por el error de aserción (punto del código que ha provocado el fallo, quizás el stack trace o incluso todo el contexto de la aserción violada), el programador puede corregir el problema. De este modo, las aserciones sirven como potente herramienta de depuración.

Aserciones estáticas[editar]

Las aserciones que son comprobadas en tiempo de compilación reciben el nombre de aserciones estáticas. Este tipo de aserciones resultan particularmente útiles en la metaprogramación de plantillas.

Desactivación de las aserciones[editar]

Las aserciones suelen ser implementadas de modo que puedan ser activadas o desactivadas, normalmente en el conjunto del programa. Sin embargo, los lenguajes que distinguen entre distintos tipos de aserciones – p.ej. pre y postcondiciones – suelen permitir activarlas o desactivarlas de forma independiente. Si las aserciones son desactivadas, no se comprueban en tiempo de ejecución. De esta forma el programador puede colocar aserciones en lugares donde en otro caso no tendría sentido ponerlas (p.ej., comprobar que no se accede a un array mediante índices fuera de rango). Puesto que las aserciones son en principio una herramienta de desarrollo, normalmente son desactivadas cuando el programa en cuestión es publicado. Por otra parte, como algunas versiones del programa pueden incluir aserciones y otras no, es primordial que su desactivación no modifique el comportamiento del programa. En otras palabras, las aserciones deben ser libres de efectos colaterales. Una alternativa en el caso de C o C++ es redefinir la macro assert para evaluar la expresión incluso cuando las aserciones están desactivadas.

La eliminación de las aserciones del código de producción es un proceso que se realiza de forma casi automática. Normalmente se realiza mediante compilación condicional, por ejemplo utilizando el preprocesador de C o C++ o pasando un parámetro de ejecución, como en Java. Algunas personas, sin embargo, se oponen a la eliminación de las aserciones basándose en una analogía que compara la ejecución con aserciones y sin ellas en la fase de desarrollo con la práctica de la natación en una piscina con la vigilancia de un socorrista para después nadar solo en el mar. Asimismo añaden que las aserciones también ayudan a desarrollar un programa a prueba de fallos.

Comparación con los manejadores de errores[editar]

Es importante establecer las semejanzas y diferencias entre las aserciones y las rutinas de control de errores. Las aserciones deberían ser utilizadas para documentar situaciones lógicamente imposibles y descubrir posibles errores de programación— si ese "imposible" ocurre, es que algo fundamental está claramente equivocado. Esto difiere del control de errores: la mayoría de las condiciones de error son posibles, aunque deberían ser muy poco probables en la práctica. El uso de aserciones como mecanismo general de control de errores suele ser algo desacertado: las aserciones no permiten al programa recuperarse de los errores, y un fallo de aserción suele suponer una terminación abrupta del programa. Asimismo las aserciones no suelen mostrar mensajes de error comprensibles por el usuario.

Considérese el siguiente ejemplo de uso de aserciones para manejar un error:

  int *ptr = malloc(sizeof(int) * 10);
  assert(ptr != NULL);
  // usar ptr

Aquí, el programador advierte que malloc puede devolver un puntero a NULL si no resulta posible realizar la asignación de memoria. Esto es posible: el sistema operativo no garantiza que cada llamada a malloc termine con éxito, y el programa debe estar preparado para manejar el error. Una aserción quizás no es la mejor elección en este caso, ya que un error causado por malloc no es lógicamente imposible — es una posibildad legítima, aunque no es muy común que suceda. En este ejemplo, es cierto que la aserción resulta útil, aclarando que el programador ha decidido de forma deliberada no construir una rutina robusta de control de errores de asignación de memoria.

El programador también puede decidirse por una utilización mixta de aserciones y rutinas estándar de control de errores para un mismo problema. Esto es útil en situaciones donde el programador quiere recibir aviso inmediato del error, pero con la seguridad de que será manejado de forma correcta en situaciones reales de explotación del programa. En el ejemplo anterior, esto supondría añadir una rutina de control de errores, pero también habría que mantener la aserción para que el programador supiese del fallo de memoria durante el proceso de depuración. Esto permite al programador localizar errores de forma más sencilla, lo que a su vez hará que el usuario final pueda ejecutar el programa sin recibir mensajes de error innecesarios. Esta táctica solamente resulta útil cuando el error especificado no supone la terminación abrupta del programa o afecte a los datos que éste maneja, sino que hará que el programa siga funcionando aunque sea más lentamente o de forma menos eficiente.

Enlaces externos[editar]