¿Qué hace la palabra clave de afirmación de Java y cuándo debe usarse?


602

¿Cuáles son algunos ejemplos de la vida real para comprender el papel clave de las afirmaciones?


8
En la vida real casi nunca los ves. Conjetura: si usa aserciones, debe pensar en tres estados: la afirmación pasa, la afirmación falla, la afirmación está desactivada, en lugar de solo dos. Y afirmar está desactivado de forma predeterminada, por lo que ese es el estado más probable, y es difícil asegurarse de que esté habilitado para su código. A lo que se suma es que las afirmaciones son una optimización prematura que sería de uso limitado. Como puede ver en la respuesta de @ Bjorn, incluso es difícil encontrar un caso de uso en el que no quiera fallar una afirmación todo el tiempo.
Yishai,

35
@Yishai: "tienes que pensar ... afirmar está desactivado" Si necesitas hacer eso, lo estás haciendo mal. "las afirmaciones son una optimización prematura de uso limitado" Esto está bastante fuera de los rieles. Aquí está la opinión de Sun sobre esto: " Uso de aserciones en la tecnología Java " y también es bueno leerlo: " Los beneficios de programar con aserciones (también conocidas como declaraciones de aserciones) "
David Tonhofer

55
@DavidTonhofer, en la vida real casi nunca los ves. Esto es verificable. Verifique tantos proyectos de código abierto como desee. No digo que no valides invariantes. Eso no es lo mismo. Dicho de otra manera. Si las afirmaciones son tan importantes, ¿por qué están desactivadas por defecto?
Yishai

17
Una referencia, FWIW: La relación entre las aserciones de software y la calidad del código : "También comparamos la eficacia de las aserciones con la de las técnicas populares de búsqueda de errores como las herramientas de análisis estático de código fuente. Observamos en nuestro estudio de caso que con un aumento en la densidad de aserciones en un archivo hay una disminución estadísticamente significativa en la densidad de fallas ".
David Tonhofer

44
@DavidTonhofer David, creo que su amor por la afirmación es por un tipo muy específico de programación que ustedes están haciendo, en mi campo que funciona con aplicaciones web que salen del programa por CUALQUIER razón es la mayor NO NO - Yo personalmente nunca he
afirmación

Respuestas:


426

Se agregaron aserciones (por medio de la palabra clave afirmar ) en Java 1.4. Se utilizan para verificar la corrección de una invariante en el código. Nunca deben activarse en el código de producción, y son indicativos de un error o mal uso de una ruta de código. Se pueden activar en tiempo de ejecución mediante la -eaopción del javacomando, pero no se activan de forma predeterminada.

Un ejemplo:

public Foo acquireFoo(int id) {
  Foo result = null;
  if (id > 50) {
    result = fooService.read(id);
  } else {
    result = new Foo(id);
  }
  assert result != null;

  return result;
}

71
De hecho, Oracle le dice que no use assertpara verificar los parámetros de métodos públicos ( docs.oracle.com/javase/1.4.2/docs/guide/lang/assert.html ). Eso debería arrojar un en Exceptionlugar de matar el programa.
SJuan76

10
Pero aún no explicas por qué existen. ¿Por qué no puede hacer una comprobación if () y lanzar una excepción?
El Mac

77
@ElMac: las afirmaciones son para las partes de desarrollo / depuración / prueba del ciclo; no son para producción. Un bloque if se ejecuta en prod. Las afirmaciones simples no arruinarán el banco, pero las afirmaciones costosas que hacen validación de datos compleja podrían reducir su entorno de producción, por lo que se desactivan allí.
hoodaticus

2
@hoodaticus, ¿te refieres únicamente al hecho de que puedo activar / desactivar todas las afirmaciones para el código de producto? Porque de todos modos puedo hacer una validación de datos compleja y luego manejarla con excepciones. Si tengo un código de producción, podría desactivar las aserciones complejas (y tal vez costosas), porque debería funcionar y ya se probó. En teoría, no deberían derribar el programa porque de todos modos tendrías un problema.
El Mac

8
This convention is unaffected by the addition of the assert construct. Do not use assertions to check the parameters of a public method. An assert is inappropriate because the method guarantees that it will always enforce the argument checks. It must check its arguments whether or not assertions are enabled. Further, the assert construct does not throw an exception of the specified type. It can throw only an AssertionError. docs.oracle.com/javase/8/docs/technotes/guides/language/...
Bakhshi

325

Supongamos que se supone que debes escribir un programa para controlar una central nuclear. Es bastante obvio que incluso el error más pequeño podría tener resultados catastróficos, por lo tanto, su código debe ser libre de errores (suponiendo que la JVM esté libre de errores por el argumento).

Java no es un lenguaje verificable, lo que significa que no puede calcular que el resultado de su operación será perfecto. La razón principal de esto son los punteros: pueden apuntar a cualquier parte o en ninguna parte, por lo tanto, no se puede calcular que tengan este valor exacto, al menos no dentro de un rango razonable de código. Dado este problema, no hay forma de demostrar que su código es correcto en su conjunto. Pero lo que puede hacer es demostrar que al menos encuentra cada error cuando sucede.

Esta idea se basa en el paradigma de diseño por contrato (DbC): primero define (con precisión matemática) lo que se supone que debe hacer su método y luego lo verifica probándolo durante la ejecución real. Ejemplo:

// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
  return a + b;
}

Si bien es bastante obvio que funciona bien, la mayoría de los programadores no verán el error oculto dentro de este (pista: el Ariane V se bloqueó debido a un error similar). Ahora DbC define que siempre debe verificar la entrada y salida de una función para verificar que funcionó correctamente. Java puede hacer esto mediante aserciones:

// Calculates the sum of a (int) + b (int) and returns the result (int).
int sum(int a, int b) {
    assert (Integer.MAX_VALUE - a >= b) : "Value of " + a + " + " + b + " is too large to add.";
  final int result = a + b;
    assert (result - a == b) : "Sum of " + a + " + " + b + " returned wrong sum " + result;
  return result;
}

Si esta función fallara alguna vez, lo notará. Sabrás que hay un problema en tu código, sabes dónde está y sabes qué lo causó (similar a Excepciones). Y lo que es aún más importante: deja de ejecutar correctamente cuando sucede para evitar que cualquier código adicional funcione con valores incorrectos y potencialmente causar daños a lo que controla.

Las excepciones de Java son un concepto similar, pero no pueden verificar todo. Si desea incluso más comprobaciones (a costa de la velocidad de ejecución), debe usar aserciones. Hacerlo hinchará su código, pero al final puede entregar un producto en un tiempo de desarrollo sorprendentemente corto (cuanto antes solucione un error, menor será el costo). Y además: si hay algún error dentro de su código, lo detectará. No hay forma de que un error se deslice y cause problemas más adelante.

Esto todavía no es una garantía para un código libre de errores, pero está mucho más cerca de eso que los programas habituales.


29
Elegí este ejemplo porque presenta errores ocultos en un código aparentemente libre de errores muy bien. Si esto es similar a lo que alguien más presentó, entonces tal vez tenían la misma idea en mente. ;)
DosEl

8
Usted elige afirmar porque falla cuando la afirmación es falsa. Un if puede tener algún comportamiento. Golpear los casos marginales es el trabajo de Unit Testing. El uso de Design by Contract especificó el contrato bastante bien, pero como con los contratos de la vida real, necesita un control para asegurarse de que se respeten. Con las aserciones se inserta un perro guardián que lo hará cuando no se respete el contrato. ¡Piense en ello como un abogado fastidioso que grita "INCORRECTO" cada vez que hace algo que está fuera o en contra de un contrato que firmó y luego lo envía a su casa para que no pueda continuar trabajando e incumplir aún más el contrato!
Eric

55
Necesario en este caso simple: no, pero el DbC define que cada resultado debe ser verificado. Imagine que alguien ahora modifica esa función a algo mucho más complejo, luego tiene que adaptar también la verificación posterior y de repente se vuelve útil.
DosEl

44
Lamento resucitar esto, pero tengo una pregunta específica. ¿Cuál es la diferencia entre lo que hizo @TwoThe y en lugar de usar afirmar simplemente arrojando un new IllegalArgumentExceptioncon el mensaje? Quiero decir, aparte de tener que agregar throwsa la declaración del método y al código para administrar esa excepción en otro lugar. ¿Por qué assertinsetad de lanzar una nueva excepción? ¿O por qué no un en iflugar de assert? Realmente no puedo entender esto :(
Blueriver

14
-1: La afirmación para verificar el desbordamiento es incorrecta si apuede ser negativa. La segunda afirmación es inútil; para valores int, siempre es el caso que a + b - b == a. Esa prueba solo puede fallar si la computadora está fundamentalmente rota. Para defenderse de esa contingencia, debe verificar la coherencia en varias CPU.
Kevin Cline

63

Las aserciones son una herramienta de fase de desarrollo para detectar errores en su código. Están diseñados para eliminarse fácilmente, por lo que no existirán en el código de producción. Por lo tanto, las afirmaciones no son parte de la "solución" que entrega al cliente. Son verificaciones internas para asegurarse de que las suposiciones que está haciendo sean correctas. El ejemplo más común es probar nulo. Muchos métodos se escriben así:

void doSomething(Widget widget) {
  if (widget != null) {
    widget.someMethod(); // ...
    ... // do more stuff with this widget
  }
}

Muy a menudo en un método como este, el widget simplemente nunca debería ser nulo. Entonces, si es nulo, hay un error en su código en algún lugar que necesita rastrear. Pero el código anterior nunca te dirá esto. Entonces, en un esfuerzo bien intencionado para escribir código "seguro", también está ocultando un error. Es mucho mejor escribir código como este:

/**
 * @param Widget widget Should never be null
 */
void doSomething(Widget widget) {
  assert widget != null;
  widget.someMethod(); // ...
    ... // do more stuff with this widget
}

De esta manera, se asegurará de detectar este error temprano. (También es útil especificar en el contrato que este parámetro nunca debe ser nulo). Asegúrese de activar las aserciones cuando pruebe su código durante el desarrollo. (Y persuadir a sus colegas para que hagan esto también es a menudo difícil, lo que me parece muy molesto).

Ahora, algunos de sus colegas se opondrán a este código, argumentando que aún debe poner el cheque nulo para evitar una excepción en la producción. En ese caso, la afirmación sigue siendo útil. Puedes escribirlo así:

void doSomething(Widget widget) {
  assert widget != null;
  if (widget != null) {
    widget.someMethod(); // ...
    ... // do more stuff with this widget
  }
}

De esta manera, sus colegas estarán felices de que la verificación nula esté allí para el código de producción, pero durante el desarrollo, ya no estará ocultando el error cuando el widget esté nulo.

Aquí hay un ejemplo del mundo real: una vez escribí un método que comparó dos valores arbitrarios para la igualdad, donde cualquiera de los valores podría ser nulo:

/**
 * Compare two values using equals(), after checking for null.
 * @param thisValue (may be null)
 * @param otherValue (may be null)
 * @return True if they are both null or if equals() returns true
 */
public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
  } else {
    result = thisValue.equals(otherValue);
  }
  return result;
}

Este código delega el trabajo del equals()método en el caso en que thisValue no sea nulo. Pero supone que el equals()método cumple correctamente el contrato equals()al manejar adecuadamente un parámetro nulo.

Un colega se opuso a mi código, diciéndome que muchas de nuestras clases tienen equals()métodos con errores que no prueban nulo, por lo que debería poner esa verificación en este método. Es discutible si esto es sabio, o si debemos forzar el error, para que podamos detectarlo y solucionarlo, pero lo aplacé a mi colega y puse un cheque nulo, que he marcado con un comentario:

public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
  } else {
    result = otherValue != null && thisValue.equals(otherValue); // questionable null check
  }
  return result;
}

La verificación adicional aquí, other != nullsolo es necesaria si el equals()método no puede verificar nulo como lo requiere su contrato.

En lugar de entablar un debate infructuoso con mi colega sobre la sabiduría de dejar que el código con errores permanezca en nuestra base de código, simplemente pongo dos afirmaciones en el código. Estas afirmaciones me harán saber, durante la fase de desarrollo, si una de nuestras clases no se implementa equals()correctamente, para que pueda solucionarlo:

public static boolean compare(final Object thisValue, final Object otherValue) {
  boolean result;
  if (thisValue == null) {
    result = otherValue == null;
    assert otherValue == null || otherValue.equals(null) == false;
  } else {
    result = otherValue != null && thisValue.equals(otherValue);
    assert thisValue.equals(null) == false;
  }
  return result;
}

Los puntos importantes a tener en cuenta son estos:

  1. Las afirmaciones son solo herramientas de fase de desarrollo.

  2. El objetivo de una afirmación es hacerle saber si hay un error, no solo en su código, sino en su base de código . (Las afirmaciones aquí en realidad marcarán errores en otras clases).

  3. Incluso si mi colega confiara en que nuestras clases fueron escritas correctamente, las afirmaciones aquí aún serían útiles. Se agregarán nuevas clases que podrían fallar al probar null, y este método puede marcar esos errores por nosotros.

  4. En el desarrollo, siempre debe activar las aserciones, incluso si el código que ha escrito no usa aserciones. Mi IDE está configurado para hacer esto siempre de forma predeterminada para cualquier nuevo ejecutable.

  5. Las afirmaciones no cambian el comportamiento del código en producción, por lo que mi colega está feliz de que la verificación nula esté allí y de que este método se ejecute correctamente incluso si el equals()método tiene errores. Estoy feliz porque detectaré cualquier equals()método defectuoso en desarrollo.

Además, debe probar su política de aserción colocando una aserción temporal que fallará, por lo que puede estar seguro de que se le notificará, ya sea a través del archivo de registro o un seguimiento de la pila en la secuencia de salida.


¡Buenos puntos sobre "ocultar un error" y cómo las afirmaciones exponen errores durante el desarrollo!
nobar

Ninguno de estos controles es lento, por lo que no hay razón para desactivarlos en la producción. Deben convertirse en declaraciones de registro, para que pueda detectar problemas que no aparecen en su "fase de desarrollo". (Realmente, de todos modos, no existe una fase de desarrollo. El desarrollo termina cuando decides dejar de mantener tu código)
Aleksandr Dubinsky,

20

Muchas buenas respuestas explican lo que hace la assertpalabra clave, pero pocas responden la pregunta real, "¿cuándo debe assertusarse la palabra clave en la vida real?"

La respuesta: casi nunca .

Las afirmaciones, como concepto, son maravillosas. Un buen código tiene muchas if (...) throw ...declaraciones (y a sus familiares les gusta Objects.requireNonNully Math.addExact). Sin embargo, ciertas decisiones de diseño han limitado enormemente la utilidad de la assert palabra clave en sí.

La idea principal detrás de la assertpalabra clave es la optimización prematura, y la característica principal es poder desactivar fácilmente todas las comprobaciones. De hecho, las assertverificaciones están desactivadas por defecto.

Sin embargo, es de vital importancia que los controles invariantes continúen realizándose en producción. Esto se debe a que la cobertura de prueba perfecta es imposible, y todo el código de producción tendrá errores cuyas afirmaciones deberían ayudar a diagnosticar y mitigar.

Por lo tanto, se if (...) throw ...debe preferir el uso de , tal como se requiere para verificar los valores de los parámetros de los métodos públicos y para el lanzamiento IllegalArgumentException.

Ocasionalmente, uno podría verse tentado a escribir un cheque invariable que demore un tiempo indeseablemente largo (y se lo llama con la frecuencia suficiente para que tenga importancia). Sin embargo, dichos controles retrasarán las pruebas, lo que tampoco es deseable. Estos controles que requieren mucho tiempo generalmente se escriben como pruebas unitarias. Sin embargo, a veces puede tener sentido usarassert por este motivo.

No lo use assertsimplemente porque es más limpio y más bonito que if (...) throw ...(y lo digo con mucho dolor, porque me gusta limpio y bonito). Si no puede evitarlo y puede controlar cómo se inicia su aplicación, no dude en utilizarla, assertpero siempre habilite las afirmaciones en producción. Es cierto que esto es lo que tiendo a hacer. Estoy presionando por una anotación de lombok que hará assertque actúe más como if (...) throw .... Vota por eso aquí.

(Rant: los desarrolladores de JVM eran un montón de codificadores horribles y de optimización prematura. Es por eso que escuchaste sobre tantos problemas de seguridad en el complemento de Java y JVM. Se negaron a incluir comprobaciones básicas y afirmaciones en el código de producción, y seguimos paga el precio.)


2
@aberglas Una cláusula general es catch (Throwable t). No hay ninguna razón para no intentar atrapar, iniciar sesión o volver a intentar / recuperar de OutOfMemoryError, AssertionError, etc.
Aleksandr Dubinsky

1
He atrapado y recuperado de OutOfMemoryError.
MiguelMunoz

1
No estoy de acuerdo Muchas de mis afirmaciones se usan para asegurarme de que mi API se llame correctamente. Por ejemplo, podría escribir un método privado que solo debería llamarse cuando un objeto tiene un candado. Si otro desarrollador llama a ese método desde una parte del código que no bloquea el objeto, la afirmación les dirá de inmediato que cometieron un error. Hay muchos errores como este que, con certeza, pueden quedar atrapados en la fase de desarrollo, y las afirmaciones son muy útiles en estos casos.
MiguelMunoz

2
@MiguelMunoz En mi respuesta dije que la idea de las afirmaciones es muy buena. Es la implementación de la assertpalabra clave es mala. Editaré mi respuesta para que quede más claro que me refiero a la palabra clave, no al concepto.
Aleksandr Dubinsky

2
Me gusta el hecho de que arroja un AssertionError en lugar de una Excepción. Demasiados desarrolladores aún no han aprendido que no deberían atrapar Exception si el código solo puede arrojar algo como IOException. He tenido errores en mi código que se tragaron por completo porque alguien detectó Exception. Las afirmaciones no quedan atrapadas en esta trampa. Las excepciones son para situaciones que espera ver en el código de producción. En cuanto al registro, también debe registrar todos sus errores, aunque los errores son poco frecuentes. Por ejemplo, ¿realmente desea dejar pasar un OutOfMemoryError sin iniciar sesión?
Miguel Muñoz

14

Este es el caso de uso más común. Supongamos que está cambiando un valor de enumeración:

switch (fruit) {
  case apple:
    // do something
    break;
  case pear:
    // do something
    break;
  case banana:
    // do something
    break;
}

Mientras manejes cada caso, estás bien. Pero algún día, alguien agregará higo a su enumeración y olvidará agregarlo a su declaración de cambio. Esto produce un error que puede ser difícil de detectar, porque los efectos no se sentirán hasta después de que haya dejado la declaración de cambio. Pero si escribe su interruptor de esta manera, puede atraparlo de inmediato:

switch (fruit) {
  case apple:
    // do something
    break;
  case pear:
    // do something
    break;
  case banana:
    // do something
    break;
  default:
    assert false : "Missing enum value: " + fruit;
}

44
Es por eso que debe tener advertencias habilitadas y advertencias tratadas como errores. Cualquier compilador medio decente es capaz de decirte, si solo permites que te diga, que te falta una verificación de enumeración, y lo hará en el momento de la compilación, que es indescriptiblemente mejor que (tal vez, un día) descubrirlo en tiempo de ejecución
Mike Nakis

99
¿Por qué usar una afirmación aquí en lugar de una excepción de algún tipo, por ejemplo, una excepción ilegalmente legal?
liltitus27

44
Esto arrojará un AssertionErrorsi las aserciones están habilitadas ( -ea). ¿Cuál es el comportamiento deseado en la producción? ¿Un no-op silencioso y un posible desastre más adelante en la ejecución? Probablemente no. Sugeriría un explícito throw new AssertionError("Missing enum value: " + fruit);.
aioobe

1
Hay un buen argumento para simplemente lanzar un AssertionError. En cuanto al comportamiento adecuado en la producción, el objetivo de las afirmaciones es evitar que esto suceda en la producción. Las aserciones son una herramienta de fase de desarrollo para detectar errores, que pueden eliminarse fácilmente del código de producción. En este caso, no hay razón para eliminarlo del código de producción. Pero en muchos casos, las pruebas de integridad pueden ralentizar las cosas. Al poner estas pruebas dentro de las aserciones, que no se usan en el código de producción, puede escribir pruebas exhaustivas, sin preocuparse de que ralenticen su código de producción.
Miguel Muñoz

Esto parece estar mal. En mi humilde opinión, no debe usar defaultpara que el compilador pueda advertirle sobre casos faltantes. Puede en returnlugar de break(esto puede necesitar extraer el método) y luego manejar el caso que falta después del cambio. De esta manera obtienes tanto la advertencia como la oportunidad assert.
maaartinus

12

Las afirmaciones se utilizan para verificar las condiciones posteriores y las condiciones previas "nunca deben fallar". El código correcto nunca debe fallar en una afirmación; cuando se disparan, deben indicar un error (con suerte en un lugar que esté cerca del lugar real del problema).

Un ejemplo de una afirmación podría ser verificar que un grupo particular de métodos se llame en el orden correcto (por ejemplo, que hasNext() se llama antes next()en un Iterator).


1
No tiene que llamar a hasNext () antes de next ().
DJClayworth

66
@DJClayworth: Tampoco es necesario que evites activar aserciones. :-)
Donal Fellows

8

¿Qué hace la palabra clave afirmar en Java?

Veamos el bytecode compilado.

Concluiremos que:

public class Assert {
    public static void main(String[] args) {
        assert System.currentTimeMillis() == 0L;
    }
}

genera casi el mismo bytecode que:

public class Assert {
    static final boolean $assertionsDisabled =
        !Assert.class.desiredAssertionStatus();
    public static void main(String[] args) {
        if (!$assertionsDisabled) {
            if (System.currentTimeMillis() != 0L) {
                throw new AssertionError();
            }
        }
    }
}

donde Assert.class.desiredAssertionStatus()es truecuando-ea se pasa en la línea de comando, y falso de lo contrario.

Usamos System.currentTimeMillis()para asegurarnos de que no se optimice (lo assert true;hizo).

El campo sintético se genera de modo que Java solo necesita llamar Assert.class.desiredAssertionStatus()una vez en el momento de la carga, y luego almacena en caché el resultado allí. Ver también: ¿Cuál es el significado de "sintético estático"?

Podemos verificar eso con:

javac Assert.java
javap -c -constants -private -verbose Assert.class

Con Oracle JDK 1.8.0_45, se generó un campo estático sintético (consulte también: ¿Cuál es el significado de "sintético estático"? ):

static final boolean $assertionsDisabled;
  descriptor: Z
  flags: ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC

junto con un inicializador estático:

 0: ldc           #6                  // class Assert
 2: invokevirtual #7                  // Method java/lang Class.desiredAssertionStatus:()Z
 5: ifne          12
 8: iconst_1
 9: goto          13
12: iconst_0
13: putstatic     #2                  // Field $assertionsDisabled:Z
16: return

y el método principal es:

 0: getstatic     #2                  // Field $assertionsDisabled:Z
 3: ifne          22
 6: invokestatic  #3                  // Method java/lang/System.currentTimeMillis:()J
 9: lconst_0
10: lcmp
11: ifeq          22
14: new           #4                  // class java/lang/AssertionError
17: dup
18: invokespecial #5                  // Method java/lang/AssertionError."<init>":()V
21: athrow
22: return

Concluimos que:

  • no hay soporte de nivel de bytecode para assert : es un concepto de lenguaje Java
  • assertpodría emularse bastante bien con las propiedades del sistema -Pcom.me.assert=truepara reemplazar -eaen la línea de comando, y a throw new AssertionError().

2
Entonces, ¿la catch (Throwable t)cláusula también puede detectar violaciones de afirmación? Para mí, eso limita su utilidad solo en el caso en que el cuerpo de la afirmación lleva mucho tiempo, lo cual es raro.
Evgeni Sergeev

1
No estoy seguro de por qué limita la utilidad de la afirmación. Nunca deberías atrapar un Throwable excepto en casos muy raros. Si necesita atrapar Throwable pero desea que no atrape afirmaciones, puede atrapar el AssertionErrorprimero y volver a lanzarlo.
MiguelMunoz

7

Un ejemplo del mundo real, de una clase Stack (de Assertion in Java Articles )

public int pop() {
   // precondition
   assert !isEmpty() : "Stack is empty";
   return stack[--num];
}

80
Esto estaría mal visto en C: una afirmación es algo que NUNCA debería suceder; abrir una pila vacía debería arrojar una NoElementsException o algo similar. Ver la respuesta de Donal.
Konerak,

44
Estoy de acuerdo. Aunque esto está tomado de un tutorial oficial, es un mal ejemplo.
DJClayworth


77
Probablemente hay una pérdida de memoria allí. Debe establecer stack [num] = null; para que el GC haga su trabajo correctamente.
H.Rabiee

3
Creo que en un método privado, sería correcto usar una aserción, ya que sería extraño tener excepciones para un mal funcionamiento de una clase o método. En un método público, llamándolo desde algún lugar externo, realmente no se puede saber cómo lo usa el otro código. ¿Verifica realmente isEmpty () o no? Usted no sabe
Vlasec

7

Una aserción permite detectar defectos en el código. Puede activar las aserciones para probar y depurar mientras las deja fuera cuando su programa esté en producción.

¿Por qué afirmar algo cuando sabes que es verdad? Solo es cierto cuando todo funciona correctamente. Si el programa tiene un defecto, podría no ser cierto. Detectar esto antes en el proceso le permite saber que algo está mal.

Una assertdeclaración contiene esta declaración junto con un Stringmensaje opcional .

La sintaxis para una declaración de aserción tiene dos formas:

assert boolean_expression;
assert boolean_expression: error_message;

Aquí hay algunas reglas básicas que rigen dónde se deben usar las afirmaciones y dónde no se deben usar. Las afirmaciones deben usarse para:

  1. Validación de parámetros de entrada de un método privado. NO para métodos públicos. publicLos métodos deben arrojar excepciones regulares cuando se pasan parámetros incorrectos.

  2. En cualquier lugar del programa para garantizar la validez de un hecho que casi con certeza es cierto.

Por ejemplo, si está seguro de que solo será 1 o 2, puede usar una afirmación como esta:

...
if (i == 1)    {
    ...
}
else if (i == 2)    {
    ...
} else {
    assert false : "cannot happen. i is " + i;
}
...
  1. Validar las condiciones de publicación al final de cualquier método. Esto significa que, después de ejecutar la lógica de negocios, puede usar aserciones para asegurarse de que el estado interno de sus variables o resultados sea consistente con lo que espera. Por ejemplo, un método que abre un socket o un archivo puede usar una aserción al final para garantizar que el socket o el archivo estén realmente abiertos.

Las afirmaciones no deben usarse para:

  1. Validación de parámetros de entrada de un método público. Dado que las aserciones no siempre se ejecutan, se debe utilizar el mecanismo de excepción regular.

  2. Validar restricciones en algo que es ingresado por el usuario. Lo mismo que arriba.

  3. No debe usarse para efectos secundarios.

Por ejemplo, este no es un uso apropiado porque aquí la aserción se usa por su efecto secundario de llamar al doSomething()método.

public boolean doSomething() {
...    
}
public void someMethod() {       
assert doSomething(); 
}

El único caso en el que esto podría justificarse es cuando intenta averiguar si las aserciones están habilitadas o no en su código:   

boolean enabled = false;    
assert enabled = true;    
if (enabled) {
    System.out.println("Assertions are enabled");
} else {
    System.out.println("Assertions are disabled");
}

5

Además de todas las excelentes respuestas proporcionadas aquí, la guía de programación oficial de Java SE 7 tiene un manual bastante conciso sobre el uso assert; con varios ejemplos puntuales de cuándo es una buena (y, lo que es más importante, una mala) idea usar afirmaciones, y cómo es diferente de lanzar excepciones.

Enlace


1
Estoy de acuerdo. El artículo tiene muchos ejemplos excelentes. Me gustó especialmente el de asegurarme de que solo se llama a un método cuando el objeto tiene un bloqueo.
MiguelMunoz

4

Assert es muy útil cuando se desarrolla. Lo usas cuando algo simplemente no puede suceder si su código funciona correctamente. Es fácil de usar y puede permanecer en el código para siempre, ya que se desactivará en la vida real.

Si existe alguna posibilidad de que la condición pueda ocurrir en la vida real, entonces debe manejarla.

Me encanta, pero no sé cómo activarlo en Eclipse / Android / ADT. Parece estar apagado incluso cuando se depura. (Hay un hilo sobre esto, pero se refiere al 'Java vm', que no aparece en la Configuración de ejecución ADT).


1
Para habilitar la afirmación en eclipse IDE, siga tutoringcenter.cs.usfca.edu/resources/…
Ayaz Pasha

No creo que haya una manera de activar afirmaciones en Android. Esto es muy decepcionante.
MiguelMunoz

3

Aquí hay una afirmación que escribí en un servidor para un proyecto Hibernate / SQL. Un bean de entidad tenía dos propiedades booleanas efectivas, llamadas isActive e isDefault. Cada uno podría tener un valor de "Y" o "N" o nulo, que se trató como "N". Queremos asegurarnos de que el cliente del navegador esté limitado a estos tres valores. Entonces, en mis setters para estas dos propiedades, agregué esta afirmación:

assert new HashSet<String>(Arrays.asList("Y", "N", null)).contains(value) : value;

Observe lo siguiente.

  1. Esta afirmación es solo para la fase de desarrollo. Si el cliente envía un valor incorrecto, lo detectaremos temprano y lo arreglaremos, mucho antes de llegar a la producción. Las afirmaciones son por defectos que puede detectar temprano.

  2. Esta afirmación es lenta e ineficiente. Esta bien. Las afirmaciones son libres de ser lentas. No nos importa porque son herramientas solo de desarrollo. Esto no ralentizará el código de producción porque las aserciones estarán deshabilitadas. (Hay cierto desacuerdo sobre este punto, que trataré más adelante). Esto me lleva al siguiente punto.

  3. Esta afirmación no tiene efectos secundarios. Podría haber probado mi valor contra un conjunto final estático no modificable, pero ese conjunto se habría mantenido en producción, donde nunca se usaría.

  4. Esta afirmación existe para verificar el correcto funcionamiento del cliente. Entonces, cuando lleguemos a la producción, nos aseguraremos de que el cliente esté funcionando correctamente, de modo que podamos desactivar la afirmación de forma segura.

  5. Algunas personas preguntan esto: si la afirmación no es necesaria en la producción, ¿por qué no simplemente eliminarla cuando haya terminado? Porque aún los necesitará cuando comience a trabajar en la próxima versión.

Algunas personas han argumentado que nunca debes usar afirmaciones, porque nunca puedes estar seguro de que todos los errores hayan desaparecido, por lo que debes mantenerlos disponibles incluso en producción. Por lo tanto, no tiene sentido usar la declaración de aserción, ya que la única ventaja de las afirmaciones es que puede desactivarlas. Por lo tanto, de acuerdo con este pensamiento, usted (casi) nunca debe usar afirmaciones. Estoy en desacuerdo. Ciertamente es cierto que si una prueba pertenece a la producción, no debe usar una afirmación. Pero esta prueba no pertenece a la producción. Este es para detectar un error que probablemente nunca llegue a producción, por lo que puede apagarse de manera segura cuando haya terminado.

Por cierto, podría haberlo escrito así:

assert value == null || value.equals("Y") || value.equals("N") : value;

Esto está bien solo para tres valores, pero si el número de valores posibles aumenta, la versión HashSet se vuelve más conveniente. Elegí la versión HashSet para hacer mi punto sobre la eficiencia.


Dudo mucho que el uso de tales diminuta una HashSetaporta ninguna ventaja de la velocidad a través de una ArrayList. Además, las creaciones de conjunto y lista dominan el tiempo de búsqueda. Estarían bien cuando se usa una constante. Dicho todo esto, +1.
maaartinus

Todo cierto. Lo hice de esta manera ineficiente para ilustrar mi punto de que las afirmaciones son libres de ser lentas. Este podría hacerse más eficiente, pero hay otros que no pueden. En un excelente libro llamado "Writing Solid Code", Steve Maguire cuenta una afirmación en Microsoft Excel para probar el nuevo código de actualización incremental que omitió celdas que no deberían cambiar. Cada vez que el usuario realiza un cambio, la aserción recalcularía toda la hoja de cálculo para asegurarse de que los resultados coincidieran con los de la función de actualización incremental. Realmente ralentizó la versión de depuración, pero detectaron todos sus errores antes.
MiguelMunoz

Totalmente de acuerdo Las afirmaciones son una especie de pruebas: son menos versátiles que las pruebas comunes, pero pueden cubrir métodos privados y son mucho más baratas de escribir. Intentaré usarlos aún más.
maaartinus

2

Las aserciones se usan básicamente para depurar la aplicación o se usan para reemplazar el manejo de excepciones de alguna aplicación para verificar la validez de una aplicación.

La afirmación funciona en tiempo de ejecución. Un ejemplo simple, que puede explicar todo el concepto de manera muy simple, es aquí: ¿qué hace la palabra clave de aserción en Java? (WikiAnswers).


2

Las aserciones están deshabilitadas por defecto. Para habilitarlos debemos ejecutar el programa con -eaopciones (se puede variar la granularidad). Por ejemplo, java -ea AssertionsDemo.

Hay dos formatos para usar aserciones:

  1. Simple: ej. assert 1==2; // This will raise an AssertionError.
  2. Mejor: assert 1==2: "no way.. 1 is not equal to 2"; esto generará un AssertionError con el mensaje que se muestra también y, por lo tanto, es mejor. Aunque la sintaxis real es assert expr1:expr2donde expr2 puede ser cualquier expresión que devuelva un valor, la he usado con más frecuencia solo para imprimir un mensaje.

1

Para recapitular (y esto es cierto para muchos lenguajes, no solo Java):

"afirmar" se utiliza principalmente como una ayuda de depuración por los desarrolladores de software durante el proceso de depuración. Los mensajes de afirmación nunca deberían aparecer. Muchos idiomas proporcionan una opción de tiempo de compilación que hará que se ignoren todos los "asertos", para su uso en la generación de código de "producción".

Las "excepciones" son una forma práctica de manejar todo tipo de condiciones de error, ya sea que representen o no errores lógicos, porque, si se encuentra con una condición de error tal que no puede continuar, simplemente puede "lanzarlos al aire, "desde donde sea que estés, esperando que alguien más esté listo para" atraparlos ". El control se transfiere en un solo paso, directamente desde el código que arrojó la excepción, directamente al guante del receptor. (Y el receptor puede ver el seguimiento completo de las llamadas que se han realizado).

Además, las personas que llaman a esa subrutina no tienen que verificar si la subrutina tuvo éxito: "si estamos aquí ahora, debe haber tenido éxito, porque de lo contrario habría arrojado una excepción y no estaríamos aquí ahora". Esta estrategia simple hace que el diseño de código y la depuración sean mucho, mucho más fáciles.

Las excepciones permiten convenientemente que las condiciones de error fatal sean lo que son: "excepciones a la regla". Y, para que sean manejados por una ruta de código que también es "una excepción a la regla ... " ¡fly ball! "


1

Las afirmaciones son cheques que pueden desconectarse. Raramente se usan. ¿Por qué?

  • No deben usarse para verificar argumentos de métodos públicos, ya que no tiene control sobre ellos.
  • No deben usarse para controles simples, result != nullya que estos controles son muy rápidos y casi no hay nada que guardar.

Entonces, ¿qué queda? Las comprobaciones costosas para las condiciones realmente se espera que sean ciertas. Un buen ejemplo serían los invariantes de una estructura de datos como RB-tree. En realidad, en ConcurrentHashMapJDK8, hay algunas afirmaciones significativas para el TreeNodes.

  • Realmente no desea activarlos en producción, ya que podrían dominar fácilmente el tiempo de ejecución.
  • Es posible que desee activarlos o desactivarlos durante las pruebas.
  • Definitivamente desea activarlos cuando trabaje en el código.

A veces, el cheque no es realmente costoso, pero al mismo tiempo, está bastante seguro de que pasará. En mi código, por ejemplo, hay

assert Sets.newHashSet(userIds).size() == userIds.size();

donde estoy bastante seguro de que la lista que acabo de crear tiene elementos únicos, pero quería documentarla y verificarla dos veces.


0

Básicamente, "afirmar verdadero" pasará y "afirmar falso" fallará. Veamos cómo funcionará esto:

public static void main(String[] args)
{
    String s1 = "Hello";
    assert checkInteger(s1);
}

private static boolean checkInteger(String s)
{
    try {
        Integer.parseInt(s);
        return true;
    }
    catch(Exception e)
    {
        return false;
    }
}

-8

assertes una palabra clave Fue introducido en JDK 1.4. Hay dos tipos de asserts

  1. assertDeclaraciones muy simples
  2. assertDeclaraciones simples

Por defecto assert, no se ejecutarán todas las declaraciones. Si una assertdeclaración recibe falso, automáticamente generará un error de aserción.


1
No proporciona ningún ejemplo de la vida real, que es el objetivo de la pregunta
rubenafo

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.