Buscar
Social
Ofertas laborales ES
« Introduccrión a J2ME | Main | Macromedia y SUN también firman un acuerdo »
miércoles
may012002

Introduccrión a las aserciones de J2SE 1.4


Aserciones (assertions)


Fecha de creación: 24.4.2002

Revisión 1.1 (24.4.2002)

Emili Miedes de Elías
emiedes@iti.upv.es

Copyright (c) 2002, Emili Miedes de Elías. Este documento puede ser distribuido solo bajo los términos y condiciones de la licencia de Documentación de javaHispano v1.0 o posterior (la última versión se encuentra en /licencias/).



Introducción



Una aserción es una condición lógica insertada en nuestro código fuente. La idea es expresar condiciones que asumimos que son ciertas. El sistema se encargará de comprobarlas y avisar mediante una excepción en caso de que no se cumplan.



Por ejemplo, supongamos que hacemos una búsqueda en un vector, con garantía de éxito, de algún valor mayor que 1000:


for (int i = 0; i < 10; i++) {
if (v[i] > 1000)
return i;
}




Si, de alguna manera, sabemos que en el vector hay algún valor mayor que 1000, sabemos que el return se va a ejecutar para algún i, y que por tanto, no se va a llegar más allá del bucle.
Para garantizar esto, después del bucle podríamos incluir la siguiente sentencia:


assert false;



En el caso de que el bucle termine sin haber ejecutado el return, se evaluará la expresión del assert. En este caso, la expresión se evalúa directamente a false por lo que el sistema lanza una excepción (de tipo AssertionError), informando de esta situación.



La expresión a evaluar puede ser todo lo complicada que queramos. El único requisito es que, sintácticamente, sea de tipo lógico.



En el siguiente ejemplo, usamos una clase que nos devuelve un valor, en teoría siempre positivo.


try {
resultado = Clase.operacionComplicada();

} catch (Throwable t) {
t.printStackTrace();
}

assert resultado > 0;



El assert nos ayuda a garantizar que el resultado obtenido cumple la restricción establecida.



Sintaxis y semántica de las aserciones



Hay dos formas de escribir las aserciones:


assert expresion;



y


assert expresion1 : expresion2;




En el primer caso, el significado es el que ya se ha descrito: si el resultado de evaluar expresion es true la ejecución continúa normalmente y si es false, se lanza una excepción de tipo AssertionError.



En el segundo caso, primero se evalúa expresion1. Si el resultado es true, la ejecución continúa.
Si el resultado es false, entonces se evalúa expresion2 y el resultado se pasa como argumento al constructor del AssertionError lanzado.



Así, el primer caso es un caso particular del segundo. En caso de que la expresión se evalúe a falso, se utiliza el constructor sin parámetros de AssertionError.


Usos de las aserciones


Precondiciones



Una precondición es una condición lógica que impone restricciones a la ejecución de un determinado fragmento de código; típicamente, un método.
Por ejemplo, nos puede interesar diseñar un método para calcular raíces cuadradas de números estrictamente positivos. Formalmente, estableceríamos la siguiente condición:


{ x > 0 }



siendo x el argumento del método.



Podemos imponer esta restricción a los usuarios de ese método, de dos maneras complementarias:


  • Por contrato: En la documentación del método, se establece un contrato entre el implementador y el usuario del método. En el ejemplo anterior, el resultado sólo está definido para números estrictamente positivos. El usuario que viole este contrato no deberá esperar el correcto funcionamiento del método.



    Sin embargo, a menudo es necesario blindar incluso los métodos que están protegidos por un contrato, para garantizar la solidez y robustez del método concreto y de la aplicación a nivel global. Esto se puede conseguir escribiendo las asserciones adecuadas, que validen los datos de entrada, de acuerdo a las restricciones del contrato.



  • Por código: Lo primero que hay que hacer en el método es comprobar que el argumento es válido. En el ejemplo anterior, serviría algo como:


    if (x <= 0)
    throw new IllegalArgumentException();



    Esta sentencia se puede sustituir por


    assert (x > 0);






La diferencia entre una y otra y el porqué de usar una u otra se verá en un apartado posterior.


Postcondiciones



Una postcondición es una condición lógica que impone restricciones al estado alcanzado después de la ejecución de un fragmento de código (típicamente, un método).



Por ejemplo, en un método para ordenar los elementos de un vector, al final del método se podría comprobar que todos los elementos están efectivamente ordenados:


public int [] ordenar (int [] v) {
int [] v2;

v2 = ...

assert (v2.estaOrdenado());
}

private boolean estaOrdenado (int [] v) {
boolean ordenado;

// Comprobar si v está ordenado
ordenado = ...;

return ordenado;
}



Control del flujo



Las aserciones permiten comprobar en tiempo de ejecución que el flujo del programa es correcto. Por ejemplo, dado el siguiente código:


if (a > 0)
tratarNumeroPositivo(a);
else if (a == 0)
descartarNulo();
else
tratarNumeroNegativo(a);



podríamos sustituir la última parte por:


else {
assert (a < 0);
tratarNumeroNegativo(a);
}



También es útil para clasificar un valor entre un conjunto determinado de valores y excluir excepciones:


switch (op) {
case AND:
sumar();
break;
case OR:
restar();
break;
case NOT:
negar();
break;
default:
assert false;
}
...



El assert utilizado es útil cuando sabemos que op va a tener uno de los tres valores contemplados y queremos asegurarnos de ello.



Otro ejemplo, con un bucle while:


while (saldo > 0) {
apostar(100);
}
assert (saldo <= 0);



Habilitando y deshabilitando las aserciones



Por defecto, las aserciones están deshabilitadas. Para habilitarlas, hay varias alternativas:


  • Habilitar las aserciones en todas las clases de todos los paquetes:


    java -enableassertions




  • Habilitar las aserciones en el paquete com.javahispano y sus subpaquetes:


    java -enableassertions:com.javahispano...




  • Habilitar las aserciones en todas las clases que no pertenecen a ningún paquete, que estén en el directorio actual:


    java -enableassertions:...




  • Habilitar las aserciones en la clase com.javahispano.Util:


    java -enableassertions:com.javahispano.Util






Además, existe un parámetro -disableassertions, para deshabilitar explicitamente las aserciones, que se utiliza de forma similar.



Es posible combinar ambos parámetros. En este caso, el orden de los mismos es importante. Por ejemplo:


java -disableassertions:com.javahispano... -enableassertions:com.javahispano.Util



deshabilita las aserciones en todas las clases del paquete com.javahispano y de sus subpaquetes, excepto las de la clase com.javahispano.Util.



(Existen dos parámetros más, -enablesystemassertions y -disablesystemassertions para controlar el uso de las aserciones en el código de las clases del sistema. Su uso se describe en la documentación oficial de la versión 1.4 del SDK).



Por otra parte, es posible habilitar y deshabilitar por programa, el uso de las aserciones. Para ello, en la clase java.lang.ClassLoader se definen varios métodos (setDefaultAssertionStatus, setPackageAssertionStatus, setClassAssertionStatus y clearAssertionStatus) cuyo uso se describe en la documentación oficial de la versión 1.4 del SDK.


Ventajas de las aserciones



Las aserciones son especialmente útiles en tiempo de desarrollo y depurado ya que ayudan a seguir la ejecución del código de forma sencilla y limpia.
Una forma práctica de utilizar las aserciones consiste en utilizarlas para depurar el código y una vez que el código es correcto y estable, deshabilitarlas global o individualmente (por clases o paquetes) para agilizar la ejecución del código.



Esta forma de utilizarlas pone de manifiesto la ventaja de las aserciones frente a las clásicas sentencias if comprobando expresiones lógicas: según el esquema clásico, la sentencia if siempre se evalúa, mientras que usando las aserciones (y habilitándolas y deshabilitándolas a conveniencia), éstas sólo se evalúan cuando es necesario.



Por otra parte, existe una diferencia (subjetiva) entre el uso de aserciones y sentencias if. El uso de sentencias if parece más indicado en aquellas situaciones en las que la condición a evaluar puede ser tanto cierta como falsa de acuerdo con el funcionamiento normal del código. Sin embargo, las aserciones parecen más indicadas cuando se da por supuesto que una condición se cumple y se desea confimar que efectivamente es así.


Efectos laterales



La única restricción sintáctica que se impone a las condiciones utilizadas en las aserciones es que sean expresiones lógicas. Sin embargo, una expresión como (x = true) incluida en una aserción, es una expresión lógica pero también un efecto lateral. Nótese que la evaluación de x depende de que las aserciones estén o no activadas (si no están activadas, el assert no se ejecutará y la variable x no será modificada). Si el resto de código depende del valor de x, es posible que el código no se ejecute correctamente a menos que las aserciones estén activadas.



Se desaconseja de forma rotunda el uso de las aserciones con efectos laterales, pues la ejecución correcta del código dependerá de ellos y del estado de las aserciones.
Existe un caso concreto en el que se admite el uso de efectos laterales, que será explicado en un apartado posterior.


Compatibilidad de código



Las aserciones introducen una nueva palabra reservada (assert). ¿Qué pasa con el código fuente que contiene algún identificador llamado assert? Esta nueva característica del lenguaje hace que ese código sea incorrecto. Un compilador de la versión 1.4 o posterior dará un error de compilación si encuentra algún identificador llamado assert. (No hay problema con los ficheros .class ya compilados).



Para solucionar esta incidencia, los compiladores de la versión 1.4 generan el código según la versión 1.3, es decir, no admiten los identificadores llamados assert pero tampoco activan las aserciones (ya que en la versión 1.3 no existían las aserciones).



Para permitir el modo 1.4, y por tanto, el uso de aserciones, se usa el parámetro -source 1.4 en la orden de compilación:


javac -source 1.4 *.java



Cómo saber si las aserciones están activas



Existe un patrón muy sencillo que permite saber en tiempo de ejecución si las aserciones están activadas o no:


boolean asercionesActivadas = false;
assert (asercionesActivadas = true);




En el caso de que las aserciones estén activadas, el assert se ejecutará, produciendo intencionadamente un efecto lateral: poner a true el indicador. En caso de que no estén activadas, la linea assert no será ejecutada y el indicador tendrá el valor false con el que ha sido inicializado.



A pesar de que la evaluación de la condición produce efectos laterales, en este caso, éstos están perfectamente delimitados. Es de esperar que en cada ocasión que se haga uso de la variable asercionesActivadas, se tenga en cuenta la influencia de su valor en el resto del código, según las aserciones estén o no activadas (precisamente ése es el objetivo de esta variable!!).



Conclusión



En la versión 1.4 del SDK se introducen las aserciones (a pesar de que estaban contempladas en el diseño original del lenguaje) como mecanismo para evaluar condiciones lógicas que ayuden a determinar la correción de nuestro código fuente.



Recursos

Acerca del autor

Emili Miedes de Elías

Emili trabaja en el Instituto Tecnológico de Informática (Universidad Politécnica de Valencia) desarrollando en Java. En sus ratos libres, colabora con javaHispano y desarrolla software opensource (buscar por emiedes en SourceForge).

Reader Comments

There are no comments for this journal entry. To create a new comment, use the form below.
Comentarios deshabilitados
Comentarios deshabilitados en esta noticia.