Cómo usar aserciones en Java

Escribir programas que funcionen correctamente en tiempo de ejecución puede ser un desafío. Esto se debe a que nuestras suposiciones sobre cómo se comportará nuestro código cuando se ejecute a menudo son incorrectas. El uso de la función de aserciones de Java es una forma de verificar que su lógica de programación sea sólida.

Este tutorial presenta las afirmaciones de Java. Primero aprenderá qué son las afirmaciones y cómo especificarlas y usarlas en su código. A continuación, descubrirá cómo utilizar las afirmaciones para hacer cumplir las condiciones previas y posteriores. Finalmente, comparará afirmaciones con excepciones y descubrirá por qué necesita ambas en su código.

descargar Obtener el código Descargar el código fuente para ver ejemplos en este tutorial. Creado por Jeff Friesen para JavaWorld.

¿Qué son las afirmaciones de Java?

Antes de JDK 1.4, los desarrolladores solían utilizar comentarios para documentar suposiciones sobre la corrección del programa. Sin embargo, los comentarios son inútiles como mecanismo para probar y depurar suposiciones. El compilador ignora los comentarios, por lo que no hay forma de utilizarlos para la detección de errores. Los desarrolladores tampoco actualizan los comentarios cuando cambian el código.  

En JDK 1.4, las afirmaciones se introdujeron como un nuevo mecanismo para probar y depurar suposiciones sobre nuestro código. En esencia, las aserciones  son entidades compilables que se ejecutan en tiempo de ejecución, asumiendo que las ha habilitado para la prueba del programa. Puede programar afirmaciones para que le notifiquen los errores donde ocurren, reduciendo en gran medida la cantidad de tiempo que de otra manera dedicaría a depurar un programa defectuoso.

Las afirmaciones se utilizan para codificar los requisitos que hacen que un programa sea correcto o no mediante la prueba de condiciones (expresiones booleanas) para valores verdaderos y notificando al desarrollador cuando tales condiciones son falsas. El uso de afirmaciones puede aumentar enormemente su confianza en la exactitud de su código.

Cómo escribir una aserción en Java

Las afirmaciones se implementan a través de la assertdeclaración y la java.lang.AssertionErrorclase. Esta declaración comienza con la palabra clave asserty continúa con una expresión booleana. Se expresa sintácticamente de la siguiente manera:

afirmar BooleanExpr ;

Si se BooleanExprevalúa como verdadero, no sucede nada y la ejecución continúa. Sin embargo, si la expresión se evalúa como falsa, AssertionErrorse crea una instancia y se lanza, como se muestra en el Listado 1.

Listado 1:AssertDemo.java (versión 1)

public class AssertDemo {public static void main (String [] args) {int x = -1; afirmar x> = 0; }}

La afirmación en el Listado 1 indica la creencia del desarrollador de que la variable xcontiene un valor que es mayor o igual a 0. Sin embargo, este no es claramente el caso; la assertejecución de la declaración da como resultado un lanzamiento AssertionError.

Compile el Listado 1 ( javac AssertDemo.java) y ejecútelo con las aserciones habilitadas ( java -ea AssertDemo). Debe observar el siguiente resultado:

Excepción en el hilo "main" java.lang.AssertionError en AssertDemo.main (AssertDemo.java:6)

Este mensaje es algo críptico ya que no identifica qué causó AssertionErrorque se lanzara. Si desea un mensaje más informativo, utilice la assertdeclaración expresada a continuación:

afirmar BooleanExpr : expr ;

Aquí, exprhay cualquier expresión (incluida la invocación de un método) que puede devolver un valor; no puede invocar un método con un voidtipo de retorno. Una expresión útil es un literal de cadena que describe el motivo del error, como se demuestra en el Listado 2.

Listado 2:AssertDemo.java (versión 2)

public class AssertDemo {public static void main (String [] args) {int x = -1; afirmar x> = 0: "x <0"; }}

Compile el Listado 2 ( javac AssertDemo.java) y ejecútelo con las aserciones habilitadas ( java -ea AssertDemo). Esta vez, debe observar la siguiente salida ligeramente expandida, que incluye el motivo del lanzamiento AssertionError:

Excepción en el hilo "main" java.lang.AssertionError: x <0 en AssertDemo.main (AssertDemo.java:6)

En cualquier ejemplo, ejecutar AssertDemosin la -eaopción (habilitar aserciones) no genera salida. Cuando las aserciones no están habilitadas, no se ejecutan, aunque todavía están presentes en el archivo de clase.

Condiciones previas y posteriores

Las afirmaciones prueban las suposiciones de un programa al verificar que sus diversas condiciones previas y posteriores no se violen, alertando al desarrollador cuando ocurre una violación:

  • Una condición previa es una condición que debe evaluarse como verdadera antes de la ejecución de alguna secuencia de código. Las condiciones previas garantizan que las personas que llaman mantengan sus contratos con los destinatarios.
  • Una condición posterior es una condición que se debe evaluar como verdadera después de la ejecución de alguna secuencia de código. Las condiciones posteriores garantizan que los destinatarios mantengan sus contratos con los llamantes.

Condiciones previas

Puede hacer cumplir las condiciones previas en los métodos y constructores públicos haciendo comprobaciones explícitas y lanzando excepciones cuando sea necesario. Para los métodos auxiliares privados, puede hacer cumplir las condiciones previas especificando aserciones. Considere el Listado 3.

Listado 3:AssertDemo.java (versión 3)

import java.io.FileInputStream; import java.io.InputStream; import java.io.IOException; class PNG {/ ** * Cree una instancia PNG, lea el archivo PNG especificado y descodifique * en estructuras adecuadas. * * @param especificación de archivo ruta y nombre del archivo PNG para leer * * @throws NullPointerException cuando filespeces *null* / PNG (String filepec) arroja IOException {// Aplicar condiciones previas en constructores y // métodos no privados. if (filespec == null) throw new NullPointerException ("filespec es nulo"); try (FileInputStream fis = new FileInputStream (especificación de archivo)) {readHeader (fis); }} private void readHeader (InputStream is) throws IOException {// Confirma que se cumple la condición previa en los // métodos auxiliares privados. afirmar es! = nulo: "nulo pasado a es"; }} public class AssertDemo {public static void main (String [] args) lanza IOException {PNG png = new PNG ((args.length == 0)? null: args [0]); }}

La PNGclase del Listado 3 es el comienzo mínimo de una biblioteca para leer y decodificar archivos de imagen PNG (gráficos de red portátiles). El constructor compara explícitamente filespeccon null, arrojando NullPointerExceptioncuando este parámetro contiene null. El punto es hacer cumplir la condición previa de que filespecno contener null.

No es apropiado especificar assert filespec != null;porque la condición previa mencionada en el Javadoc del constructor no se cumpliría (técnicamente) cuando se deshabilitaran las aserciones. (De hecho, sería un honor porque FileInputStream()arrojaría NullPointerException, pero no deberías depender de un comportamiento indocumentado).

Sin embargo, assertes apropiado en el contexto del readHeader()método auxiliar privado , que eventualmente se completará para leer y decodificar el encabezado de 8 bytes de un archivo PNG. La condición previa de que issiempre se pase un valor no nulo siempre se mantendrá.

Postcondiciones

Las poscondiciones se especifican normalmente a través de aserciones, independientemente de si el método (o constructor) es público o no. Considere el Listado 4.

Listado 4:AssertDemo.java (versión 4)

public class AssertDemo { public static void main(String[] args) { int[] array = { 20, 91, -6, 16, 0, 7, 51, 42, 3, 1 }; sort(array); for (int element: array) System.out.printf("%d ", element); System.out.println(); } private static boolean isSorted(int[] x) { for (int i = 0; i  x[i + 1]) return false; return true; } private static void sort(int[] x) { int j, a; // For all integer values except the leftmost value ... for (int i = 1; i  0 && x[j - 1] > a) { // Shift left value -- x[j - 1] -- one position to its right -- // x[j]. x[j] = x[j - 1]; // Update insert position to shifted value's original position // (one position to the left). j--; } // Insert a at insert position (which is either the initial insert // position or the final insert position), where a is greater than // or equal to all values to its left. x[j] = a; } assert isSorted(x): "array not sorted"; } }

Listing 4 presents a sort() helper method that uses the insertion sort algorithm to sort an array of integer values. I’ve used assert to check the postcondition of x being sorted before sort() returns to its caller.

The example in Listing 4 demonstrates an important characteristic of assertions, which is that they’re typically expensive to execute. For this reason, assertions are usually disabled in production code. In Listing 4, isSorted() must scan through the entire array, which can be time-consuming in the case of a lengthy array.

Assertions vs. exceptions in Java

Developers use assertions to document logically impossible situations and detect errors in their programming logic. At runtime, an enabled assertion alerts a developer to a logic error. The developer refactors the source code to fix the logic error and then recompiles this code.

Developers use Java’s exception mechanism to respond to non-fatal (e.g., running out of memory) runtime errors, which may be caused by environmental factors, such as a file not existing, or by poorly written code, such as an attempt to divide by 0. An exception handler is often written to gracefully recover from the error so that the program can continue to run.

Assertions are no substitute for exceptions. Unlike exceptions, assertions don’t support error recovery (assertions typically halt program execution immediately — AssertionError isn’t meant to be caught); they are often disabled in production code; and they typically don’t display user-friendly error messages (although this isn’t an issue with assert). It’s important to know when to use exceptions rather than assertions.

When to use exceptions

Suppose you’ve written a sqrt() method that calculates the square root of its argument. In a non-complex number context, it’s impossible to take the square root of a negative number. Therefore, you use an assertion to fail the method if the argument is negative. Consider the following code fragment:

public double sqrt(double x) { assert x >= 0 : "x is negative"; // ... }

It’s inappropriate to use an assertion to validate an argument in this public method. An assertion is intended to detect errors in programming logic and not to safeguard a method from erroneous arguments. Besides, if assertions are disabled, there is no way to deal with the problem of a negative argument. It’s better to throw an exception, as follows:

public double sqrt(double x) { if (x < 0) throw new IllegalArgumentException("x is negative"); // ... }

The developer might choose to have the program handle the illegal argument exception, or simply propagate it out of the program where an error message is displayed by the tool that runs the program. Upon reading the error message, the developer can fix whatever code led to the exception.

Es posible que haya notado una sutil diferencia entre la afirmación y la lógica de detección de errores. La aserción prueba x >= 0, mientras que la lógica de detección de errores prueba x < 0. La afirmación es optimista: asumimos que el argumento está bien. Por el contrario, la lógica de detección de errores es pesimista: asumimos que el argumento no es correcto. Las afirmaciones documentan la lógica correcta, mientras que las excepciones documentan el comportamiento incorrecto en tiempo de ejecución.

En este tutorial, ha aprendido a usar afirmaciones para documentar la lógica correcta del programa. También ha aprendido por qué las afirmaciones no reemplazan las excepciones y ha visto un ejemplo en el que el uso de una excepción sería más eficaz.

Esta historia, "Cómo usar aserciones en Java" fue publicada originalmente por JavaWorld.