Contenido

No comentes: ¡Asegura!

Hoy he descubierto mucho de ese gran desconocido que es assert.

Resulta que tenía a mi alcance una herramienta de depuración bastante fuerte y aún no he hecho uso de ella.

Con el fin de solucionar este problema, escribo este artículo, aunque no escribiré nada que no se encuentre en la Documentación de Java sobre Assert o la documentación Python.

Test

Lo más básico

Lo primero es saber cómo vamos a usarlo. Suele haber dos maneras de utilizarlo: con un parámetro o con dos. El segundo parámetro indicará el texto a acompañar la excepción cuando el primero de los parámetros no se cumpla.

En python:

1
2
assert expression
assert expression1, expression2

y en java:

1
2
assert expression;
assert expression1 : expression2;

Es importante destacar que, si la expresion1 no se cumple, la expression2 no se evaluará. A menudo, esta segunda expresión debe generar una cadena.

¡¡No utilices Assert!!

De la misma manera que no utilizarías un martillo neumático para apretar un tornillo, no podemos utilizar assert en todos los casos.

Donde no debemos usarlo es en los siguientes supuestos:

  • Métodos públicos: Cuestión estética, ya que queda horrible recibir un “Assertion Error”. Será mejor recibir un “Se ha producido un error”, por lo que en métodos públicos es mejor utilizar una comparación y una excepción. Además, existe la posibilidad de desactivar las aseveraciones, por lo que corremos el riesgo de dejar de comprobar la entrada de un método público.
  • Para hacer trabajo útil en un método, ya que las aseveraciones se pueden desactivar y, por tanto, dejaría de realizarse ese trabajo.

¡¡Utiliza Assert!!

A menudo nos sentimos tentados de escribir comentarios del tipo “Todo bien”, “no debería llegar aquí”, etc. Este tipo de comentario es un problema ya que, por un lado, pueden dejar de ser ciertos y por otro, nadie los está comprobando.

Además, se requieren demasiadas letras para dar la misma semántica (comparo el comentario con su equivalente con assert).

En Python:

1
2
# el valor de i debe ser mayor que 5
assert i > 5

En Java:

1
2
// el valor de i debe ser mayor que 5
assert i > 5;

Cuando tenemos un switch, en ocasiones dejamos el caso por defecto sin gestionar, ya que nunca se llegará allí. ¿O tal vez sí? ¿Qué ocurrirá el día que añadamos una nueva opción? Podemos asegurarnos de contemplarlas:

1
2
3
4
5
6
7
8
9
switch (var)
{
    case value1: hacer_cosas1(); break;
    case value2: hacer_cosas2(); break;
    ...
    case valueN: hacer_cosasN(); break;

   default: assert False, "Valor no contemplado";
}

Precondiciones y postcondiciones

Uno de mis usos favoritos es el de comprobar entradas y salidas. Por ejemplo: tenemos la función que devuelve el factorial de un número, y que debe exigir que la entrada sea mayor que 1 (y que no sea negativa, claro). Asumiendo que no sea un método público:

1
2
3
4
5
def factorial (n):
  assert n > 0, "Valor inválido:" + n;

  if n == 1: return 1
  return n * factorial (n-1)

También podemos asegurarnos de no haber dejado algo con un valor inválido:

1
2
3
4
5
6
7
8
protected List metodo () {
  ArrayList result = null;

  // Hacer muchas cosas con ifs, llamadas a métodos, etc.

  assert result != null, "valor no inicializado";
  return result;
}

En Java, es muy común tener en una clase funciones synchronized y no synchronized que se llaman entre ellas. Eso es un foco de deathlocks dificilísimos de detectar, ya que estamos esperando a que nosotros mismos soltemos el bloqueo. Es como buscar las gafas con las gafas puestas.

Este caso es realmente sencillo de detectar con un assert:

1
2
3
4
5
6
7
8
9
    protected synchronized void metodo_sincronizado() {
      metodo_no_sincronizado();
    }

    protected synchronized void metodo_no_sincronizado() {
      assert Thread.holdsLock(this);

      // hacer cosas
    }

Desactivación

Por último, indicar que estas aseveraciones (por favor, no uséis “aserciones”) se pueden desactivar en un entorno de producción, por lo que no importa si hacéis cosas “caras”. Cuando las desactivéis será como si nunca las hubierais escrito.

En python están ACTIVADAS por defecto. Se debe usar la opción -O para desactivarlas:

1
2
3
4
5
6
7
8
9
$ cat assert.py
assert False, "Aseveraciones activadas"
$ python assert.py
Traceback (most recent call last):
  File "assert.py", line 1, in <module>
    assert False, "Aseveraciones activadas"
AssertionError: Aseveraciones activadas
$ python -O assert.py
$

En Java estarán DESACTIVADAS por defecto, aunque se pueden activar por módulo, por método, etc. Aunque lo más útil es utilizar -ea para activarlas todas.

¡¡Empleadlo con cariño!!