Assertions. (Afirmaciones) Un primer acercamiento

El concepto de afirmaciones (assertions) se atribuye a aquellas sentencias booleanas colocadas en un punto específico de un programa, las cuales serán verdaderas hasta que se demuestre lo contrario.

Este tipo de sentencias se utilizan como ayuda en las correcciones de un programa. Por ejemplo:

• Una precondición: es una afirmación colocada al inicio de una sección de código, determinando el conjunto de sentencias bajo las cuáles se espera que el código sea ejecutado.

private void setRefreshInterval(int interval) {

// Confirma la precondición en un método no público.
assert interval > 0 && interval <= 1000/MAX_REFRESH_RATE : interval;
... // Define interval
}

• Una postcondición se coloca al final, describiendo la sentencia esperada al final de la ejecución.

public BigInteger modInverse(BigInteger m) {
if (m.signum <= 0)
throw new ArithmeticException(“Negativos: “ + m);
... // Se realizan los cálculos
assert this.multiply(result).mod(m).equals(ONE) : this;
return result;
}

Algunos lenguajes de programación modernos incluyen sentencias de tipo “assertions”, que son analizadas en tiempo de ejecución.

Lenguajes de programación tales como Java, utilizan este concepto. Así, la declaración de una afirmación sería:

assert Expression i;

Dónde Expression i es una expresión booleana.

Un candidato para su uso es un bloque tipo switch sin la definición de un tipo default. La ausencia de este caso, indica que un programador asume que uno de los casos siempre será ejecutado. Suponiendo que un ciclo case está definido de la siguiente manera:

switch(carta) {
case Carta.TRÉBOLES:
...
break;
case Carta.DIAMANTES:
...
break;
case Carta.CORAZONES:
...
break;
case Carta.ESPADAS:
...
}

Esto probablemente indica una afirmación que la variable “carta” tendrá uno de los cuatro valores definidos. Para probar esta afirmación se debe agregar el siguiente caso tipo default:

default:
assert false : carta;

Si la variable carta adquiere otro valor y las afirmaciones están definidas, la afirmación fallará y un AssertionError será generado. Una alternativa aceptable es:

default:
throw new AssertionError(carta);

Esta opción ofrece protección aún si las afirmaciones no están definidas, de esta manera la sentencia throw no se ejecutará hasta que el programa halla fallado.

Manejo de afirmaciones fallidas.
Si una afirmación falla, el primer paso es comunicarle al usuario que existe una falla, este aviso puede ser a través de mostrar un mensaje de error en la terminal o guardarlo en un archivo de log. Una vez que la falla se ha analizado y el mensaje de error ha sido desplegado, existen diferentes alternativas:

    • Terminar el programa.
    • Permitir su ejecución.
    • Seguir una excepción para regresar a la ruta de código que tiene el error.

 

Esto intenta ser más fácil de depurar que un mensaje de error o un comportamiento de falla que podría resultar si la afirmación fuera falsa y sin revisión.

Diferencia entre afirmaciones y manejo de errores rutinarios
Debemos aclarar que las afirmaciones se distinguen del manejo de errores rutinario.

Las afirmaciones deben utilizarse para documentar situaciones lógicamente imposibles, de tal forma que cuando se disparen es porque se ha descubierto un error de programación. Es decir, si lo imposible ocurre es porque hay algo fundamental que está mal, ¿cierto?

Esto es distinto al manejo de errores tradicional, ya que la mayoría de las condiciones de error son posibles, y por eso contemplamos que puedan suceder y nos preparamos para manejarlas adecuadamente, sin necesidad de detener la ejecución del programa (cosa que sucede al encontrar una afirmación fallida).

Limitaciones de las afirmaciones.
Como cualquier otro código, las afirmaciones están sujetas a tener errores. Un error en una afirmación puede provocar que ésta reporte un error en donde no existe tal. Esto no es tan grave ya que al analizar el error, el desarrollador puede facilmente darse cuenta que en realidad el programa está funcionando correctamente y la afirmación es la que está mal definida.

Otro problema ocasionado por una mala definición de una afirmación es que no logre detectar las condiciones que se supone que debería atrapar. Es por esto que al igual que cualquier otro elemento de código, también deberíamos probar las afirmaciones para asegurarnos de que funcionan como esperamos.

Otro inconveniente con el uso de afirmaciones es que pueden llegar a afectar el desempeño de un programa debido al procesamiento adicional que requieren. Esto no es de gran importancia en ambientes de desarrollo, pero definitivamente en ambientes de producción debemos tenerlo en cuenta. Lo que se hace para evitar esto es que las afirmaciones normalmente se eliminan del código que va a ser utilizado en producción. Para facilitar esto y deshabilitar las afirmaciones sin necesidad de modificar el código, la mayoría de los lenguajes modernos soporta la habilitación o deshabilitación de afirmaciones en tiempo de ejecución a través del uso de banderas al momento de invocar un programa.

Cuando una afirmación falla debido a que encuentra un error, la ejecución del programa se detiene por completo. Esto es útil porque las fallas detectadas por afirmaciones son condiciones completamente anormales, las cuales se deben resolver en el momento. Sin embargo, esto puede llegar a ser un inconveniente durante el ciclo de pruebas de un programa, ya que las afirmaciones pueden entorpecer el proceso de pruebas ya que no permiten continuar la ejecución de pruebas hasta que la condición que causa la afirmación es arreglada, o la afirmación es deshabilitada por completo.