¿Qué hace la palabra clave Java assert y cuándo se debe usar?

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

Las aserciones (mediante la palabra clave assert ) se agregaron en Java 1.4. Se usan para verificar la corrección de un invariante en el código. Nunca deberían 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 por medio de la opción -ea en el comando java , pero no están activados de manera 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; } 

Supongamos que se supone que debes escribir un progtwig para controlar una planta de energía 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 estar libre de errores (suponiendo que la JVM no tiene errores por el bien del 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 lugar o a ninguna parte, por lo tanto, no se puede calcular que tengan este valor exacto, al menos no dentro de un lapso razonable de código. Dado este problema, no hay manera de demostrar que su código es correcto en su totalidad. Pero lo que puedes hacer es demostrar que al menos encuentras cada error cuando sucede.

Esta idea se basa en el paradigma Diseño por contrato (DbC): primero debe definir (con precisión matemática) qué debe hacer su método y luego verificarlo 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 esto es bastante obvio para funcionar bien, la mayoría de los progtwigdores no verán el error oculto dentro de este (sugerencia: el Ariane V se estrelló debido a un error similar). Ahora DbC define que siempre debe verificar la entrada y la salida de una función para verificar que funcionó correctamente. Java puede hacer esto a través de 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 ahora falla, la notará. Sabrá que hay un problema en su código, usted sabe dónde está y usted sabe qué lo causó (similar a las 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 dañe lo que sea que controle.

Las excepciones de Java son un concepto similar, pero no pueden verificar todo. Si quiere incluso más cheques (a costa de la velocidad de ejecución), necesita usar aserciones. Hacerlo inflará 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 tu código, lo detectarás. No hay forma de que un error se deslice y cause problemas más adelante.

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

Las aserciones son una herramienta de fase de desarrollo para detectar errores en tu 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 usted entrega al cliente. Son controles internos para asegurarse de que las suposiciones que está haciendo sean correctas. El ejemplo más común es probar nulo. Muchos métodos están escritos 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 debe ser nulo. Entonces, si es nulo, hay un error en su código en alguna parte que necesita rastrear. Pero el código anterior nunca te dirá esto. Entonces, en un esfuerzo bien intencionado para escribir un código “seguro”, también está ocultando un error. Es mucho mejor escribir un 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 afirmaciones cuando pruebe el código durante el desarrollo. (Y persuadir a tus colegas para que hagan esto también suele ser difícil, lo cual me resulta muy molesto).

Ahora, algunos de sus colegas objetarán este código, argumentando que aún debe poner la verificación nula 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é ahí para el código de producción, pero durante el desarrollo, ya no oculta el error cuando el widget es nulo.

Aquí hay un ejemplo del mundo real: una vez escribí un método que comparaba 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 método equals() en el caso en que thisValue no sea nulo. Pero asume que el método equals() cumple correctamente el contrato de equals() manejando adecuadamente un parámetro nulo.

Un colega se opuso a mi código, diciéndome que muchas de nuestras clases tienen métodos buggies equals() que no prueban nulo, así que debería poner ese control en este método. Es discutible si esto es prudente, o si debemos forzar el error, para que podamos detectarlo y solucionarlo, pero le dediqué 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; } 

El control adicional aquí, other != null , solo es necesario si el método equals() no comprueba si es nulo según lo exige su contrato.

En lugar de participar en un debate infructuoso con mi colega sobre la conveniencia de dejar que el código defectuoso permanezca en nuestra base de códigos, simplemente incluí dos afirmaciones en el código. Estas afirmaciones me harán saber, durante la fase de desarrollo, si una de nuestras clases falla al implementar equals() correctamente, entonces puedo 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 objective de una afirmación es hacerte saber si hay un error, no solo en tu código, sino en tu código base . (Las afirmaciones aquí en realidad marcarán errores en otras clases).

  3. Incluso si mi colega estuviera seguro de que nuestras clases fueron escritas correctamente, las afirmaciones aquí todavía serían útiles. Se agregarán nuevas clases que podrían no probar nulo, y este método puede marcar esos errores para nosotros.

  4. En desarrollo, siempre debe activar afirmaciones, incluso si el código que ha escrito no utiliza 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 a mi colega le complace que la verificación nula esté allí, y que este método se ejecutará correctamente incluso si el método equals() tiene errores. Estoy contento porque atraparé cualquier método de error equals() en desarrollo.

Además, debe probar su política de aserción al poner en 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 stack en la secuencia de salida.

Muchas buenas respuestas que explican qué hace la palabra clave assert , pero pocas respondiendo a la pregunta real, “¿cuándo debería usarse la palabra clave assert en la vida real?”

La respuesta: casi nunca .

Las afirmaciones, como concepto, son maravillosas. El código bueno tiene muchas declaraciones if (...) throw ... (y sus parientes como Objects.requireNonNull y Math.addExact ). Sin embargo, ciertas decisiones de diseño han limitado en gran medida la utilidad de la palabra clave assert .

La idea principal detrás de la palabra clave assert es la optimización prematura, y la característica principal es poder desactivar fácilmente todos los controles. De hecho, las verificaciones de assert están desactivadas por defecto.

Sin embargo, es críticamente importante que los controles invariables sigan realizándose en la producción. Esto se debe a que la cobertura de prueba perfecta es imposible, y todo el código de producción tendrá errores que las afirmaciones deberían ayudar a diagnosticar y mitigar.

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

Ocasionalmente, uno podría sentirse tentado a escribir un cheque invariable que tarda un tiempo indeseablemente largo en procesar (y se lo llama con la frecuencia suficiente para que importe). Sin embargo, tales verificaciones ralentizarán las pruebas, lo que tampoco es deseable. Tales cheques que consumen mucho tiempo generalmente se escriben como pruebas unitarias. Sin embargo, a veces puede tener sentido usar assert por este motivo.

No use assert simplemente 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 simplemente no puede ayudarse a sí mismo, y puede controlar cómo se lanza su aplicación, entonces puede usar assert pero siempre habilite las afirmaciones en producción. Es cierto que esto es lo que tiendo a hacer. Estoy presionando para una anotación lombok que hará que assert actúe más como if (...) throw ... Vote por ello aquí.

(Rant: los desarrolladores de JVM eran un montón de codificadores terribles que optimizaban prematuramente. Es por eso que escuchas sobre tantos problemas de seguridad en el plugin de Java y JVM. Rechazaron incluir controles y aserciones básicos en el código de producción, y seguimos paga el precio.)

Este es el caso de uso más común. Supongamos que está encendiendo un valor enum:

 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 tu enumeración y se olvidará de agregarlo a tu statement de cambio. Esto produce un error que puede ser complicado de atrapar, porque los efectos no se sentirán hasta después de haber dejado la instrucció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; } 

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

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

¿Qué hace la palabra clave assert en Java?

Veamos el bytecode comstackdo.

Concluiremos que:

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

genera casi el mismo bytecode exacto como:

 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 true cuando -ea se pasa en la línea de comando, y falso en caso contrario.

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

El campo sintético se genera de modo que Java solo necesita llamar a 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 sintético estático (ver también: ¿Cuál es el significado de “estático sinté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."":()V 21: athrow 22: return 

Concluimos que:

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

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

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

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

Enlazar

Una afirmación permite detectar defectos en el código. Puede activar las aserciones para probarlas y depurarlas y dejarlas desactivadas cuando su progtwig esté en producción.

¿Por qué afirmar algo cuando sabes que es verdad? Solo es cierto cuando todo funciona correctamente. Si el progtwig tiene un defecto, puede que en realidad no sea cierto. Detectar esto antes en el proceso te permite saber que algo está mal.

Una instrucción assert contiene esta statement junto con un mensaje String opcional.

La syntax para una statement afirmativa 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. Validar los parámetros de entrada de un método privado. NO para métodos públicos. public métodos public deberían arrojar excepciones regulares cuando pasan parámetros incorrectos.

  2. En cualquier lugar del progtwig para garantizar la validez de un hecho que es casi seguro 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 comercial, puede usar las aserciones para garantizar 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 asegurarse de que el socket o el archivo estén efectivamente abiertos.

Las afirmaciones no deberían usarse para:

  1. Validación de parámetros de entrada de un método público. Como las aserciones no siempre se pueden ejecutar, se debe usar el mecanismo de excepción regular.

  2. Validación de restricciones en algo que el usuario ingresa. 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 llamada del método doSomething() .

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

El único caso donde 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"); } 

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

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

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

Aquí hay una afirmación que escribí en un servidor para un proyecto de Hibernate / SQL. Un bean de entidad tenía dos propiedades booleanas efectivas, llamadas isActive y 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 instaladores para estas dos propiedades, agregué esta afirmación:

 assert new HashSet(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 reparará, mucho antes de que lleguemos a la producción. Las afirmaciones son por defectos que puedes detectar temprano.

  2. Esta afirmación es lenta e ineficiente. Esta bien. Las afirmaciones son libres de ser lento. No nos importa porque son herramientas solo de desarrollo. Esto no ralentizará el código de producción porque las afirmaciones estarán deshabilitadas. (Hay un poco de desacuerdo sobre este punto, que abordaré más adelante). Esto me lleva a mi próximo punto.

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

  4. Esta afirmación existe para verificar el funcionamiento correcto 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 manera segura.

  5. Algunas personas preguntan esto: si la afirmación no es necesaria en producción, ¿por qué no simplemente eliminarlas cuando termines? Porque todavía los necesitarás cuando comiences a trabajar en la próxima versión.

Algunas personas han argumentado que nunca debes usar aserciones, porque nunca puedes estar seguro de que todos los errores se han ido, por lo que debes mantenerlos a salvo incluso en producción. Por lo tanto, no tiene sentido utilizar la statement de afirmación, ya que la única ventaja de afirmar es que puede desactivarla. Por lo tanto, de acuerdo con este pensamiento, usted debe (casi) nunca usar aseveraciones. Estoy en desacuerdo. Es cierto que si una prueba pertenece a la producción, no debes usar una afirmación. Pero esta prueba no pertenece a la producción. Este es para atrapar un error que probablemente no llegue a la producción, por lo que se puede apagar con seguridad cuando haya terminado.

Por cierto, podría haberlo escrito así:

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

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

Las aserciones están deshabilitadas por defecto. Para habilitarlos, debemos ejecutar el progtwig con -ea options (la granularidad puede ser variada). Por ejemplo, java -ea AssertionsDemo .

Hay dos formatos para usar aserciones:

  1. Simple: ej. assert 1==2; // This will raise an AssertionError 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 dado también y por lo tanto es mejor. Aunque la syntax real es assert expr1:expr2 donde expr2 puede ser cualquier expresión que devuelva un valor, la he usado más a menudo solo para imprimir un mensaje.

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

“assert” se utiliza principalmente como una herramienta de depuración por los desarrolladores de software durante el proceso de depuración. Los mensajes de confirmación nunca deberían aparecer. Muchos idiomas proporcionan una opción de tiempo de comstackción que hará que todos los “asertos” sean ignorados, para usarlos 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 de lógica, porque si se encuentra con una condición de error tal que no puede continuar, simplemente puede “lanzarlas al air, “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 catcher. (Y el receptor puede ver la traza inversa completa de las llamadas que tuvieron lugar).

Además, los llamantes de 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 simple estrategia hace que el diseño y la depuración del código 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 un código de acceso que también es “una excepción a la regla … ” ¡fly ball! “

La aserción se usa básicamente para depurar la aplicación o se usa para reemplazar el manejo de excepciones de alguna aplicación para verificar la validez de una aplicación.

La aserción funciona en tiempo de ejecución. A simple example, that can explain the whole concept very simply, is herein – What does the assert keyword do in Java? (WikiAnswers).

Basically, “assert true” will pass and “assert false” will fail. Let’s looks at how this will work:

 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; } } 

assert is a keyword. It was introduced in JDK 1.4. The are two types of assert s

  1. Very simple assert statements
  2. Simple assert statements.

By default all assert statements will not be executed. If an assert statement receives false, then it will automatically raise an assertion error.