En este artículo, aprenderemos sobre el uso de constantes en Java con un enfoque en patrones comunes y anti-patrones.

Comenzaremos con algunas convenciones básicas para definir constantes. A partir de ahí, pasaremos a los antipatrones comunes antes de terminar con un vistazo a los patrones comunes.

Convenciones

Una constante es una variable cuyo valor no cambiará después de que se haya definido.

Veamos los conceptos básicos para definir una constante:


private static final int CONSTANTE = 1;

Algunos de los patrones que veremos abordarán la decisión del modificador de acceso público o privado. Hacemos nuestras constantes estáticas y finales y les damos un tipo apropiado, ya sea una primitiva de Java, una clase o una enumeración. El nombre debe ser todo en mayúsculas con las palabras separadas por guiones bajos, a veces conocido como caso de serpiente gritando. Por último, aportamos el valor en sí.

Anti-Patrones

Primero, comencemos por aprender qué no hacer. Veamos un par de anti-patrones comunes que podemos encontrar al trabajar con constantes de Java.

Números mágicos

Los números mágicos son literales numéricos en un bloque de código:

if (numero == 3.14159265359) {
    // ...
}

Son difíciles de entender para otros desarrolladores. Además, si usamos un número en todo nuestro código, es difícil lidiar con cambiar el valor. En cambio, deberíamos definir el número como una constante.

Una clase grande de constantes globales

Cuando iniciamos un proyecto, puede parecer natural crear una clase llamada Constants o Utils con la intención de definir allí todas las constantes para la aplicación. Para proyectos más pequeños, esto podría estar bien, pero consideremos un par de razones por las que esta no es una solución ideal.

Primero, imaginemos que tenemos cien o más constantes en nuestra clase de constantes. Si la clase no se mantiene, tanto para mantenerse al día con la documentación como para refactorizar ocasionalmente las constantes en agrupaciones lógicas, se volverá bastante ilegible. Incluso podríamos terminar con constantes duplicadas con nombres ligeramente diferentes. Es probable que este enfoque nos dé problemas de legibilidad y mantenibilidad en cualquier cosa menos en los proyectos más pequeños.

Además de la logística de mantener la clase Constants en sí, también estamos creando otros problemas de mantenibilidad al fomentar demasiada interdependencia con esta clase de constantes globales y varias otras partes de nuestra aplicación.

En un aspecto más técnico, el compilador de Java coloca el valor de la constante en variables de referencia en las clases en las que las usamos. Entonces, si cambiamos una de nuestras constantes en nuestra clase de constantes y solo recompilamos esa clase y no la clase de referencia, podemos obtener valores constantes inconsistentes.

El Anti-Patrón de Interfaz de Constantes

El patrón de interfaz de constantes es cuando definimos una interfaz que contiene todas las constantes para cierta funcionalidad y luego tenemos las clases que necesitan esas funcionalidades para implementar la interfaz.

Definamos una interfaz de constantes para una calculadora:

public interface ConstantesDeCalculadora {
    double PI = 3.14159265359;
    double NUMERO_MAXIMO = 0x1.fffffffffffffP+1023;
    enum Operacion {SUMA, RESTA, MULTIPLICACION, DIVISION};
}

A continuación, implementaremos nuestra interfaz ConstantesDeCalculadora:

public class Geometria implements ConstantesDeCalculadora {    
    public double operarDosNumeros(double numeroUno, double numeroDos, Operacion operacion) {
       // operar
    }
}

El primer argumento en contra del uso de una interfaz de constantes es que va en contra del propósito de una interfaz. Estamos destinados a utilizar interfaces para crear un contrato para el comportamiento que van a proporcionar nuestras clases de implementación. Cuando creamos una interfaz llena de constantes, no estamos definiendo ningún comportamiento.

En segundo lugar, el uso de una interfaz constante nos abre a problemas de tiempo de ejecución causados por el sombreado de campo. Veamos cómo podría suceder eso al definir una constante NUMERO_MAXIMO dentro de nuestra clase Geometria:


public static final double NUMERO_MAXIMO = 100000000000000000000.0;

Una vez que definimos esa constante en nuestra clase Geometria, ocultamos el valor en la interfaz ConstantesDeCalculadora para nuestra clase. Entonces podríamos obtener resultados inesperados.

Otro argumento en contra de este anti-patrón es que causa contaminación del espacio de nombres. Nuestras CalculatorConstants ahora estarán en el espacio de nombres para cualquiera de nuestras clases que implementen la interfaz, así como cualquiera de sus subclases.

Patrones

Anteriormente, vimos la forma apropiada para definir constantes. Veamos algunas otras buenas prácticas para definir constantes dentro de nuestras aplicaciones.

Buenas prácticas generales

Si las constantes están relacionadas lógicamente con una clase, podemos definirlas allí. Si vemos un conjunto de constantes como miembros de un tipo enumerado, podemos usar una enumeración para definirlas.

Definamos algunas constantes en una clase Calculadora:

public class Calculadora {
    public static final double PI = 3.14159265359;
    private static final double NUMERO_MAXIMO = 0x1.fffffffffffffP+1023;
    public enum Operacion {
        SUMA,
        RESTA,
        DIVISION,
        MULTIPLICACION
    }
 
    public double operar(double numeroUno, double numeroDos, Operacion operacion) {
        if (numeroUno > NUMERO_MAXIMO) {
            throw new IllegalArgumentException("'numeroUno' es muy grande");
        }
        if (numeroDos > NUMERO_MAXIMO) {
            throw new IllegalArgumentException("'numeroDos' Es muy grande");
        }
        double respuesta = 0;
        
        switch(operacion) {
            case SUMA:
                respuesta = numeroUno + numeroDos;
                break;
            case RESTA:
                respuesta = numeroUno - numeroDos;
                break;
            case DIVISION:
                respuesta = numeroUno / numeroDos;
                break;
            case MULTIPLICACION:
                respuesta = numeroUno * numeroDos;
                break;
        }
        
        return respuesta;
    }
}

En nuestro ejemplo, hemos definido una constante para NUMERO_MAXIMO que solo estamos planeando usar en la clase Calculadora, por lo que la configuramos como privada. Queremos que otras clases puedan usar PI y la enumeración de Operación, por lo que las configuramos como públicas.

Consideremos algunas de las ventajas de usar una enumeración para la Operación. La primera ventaja es que limita los valores posibles. Imagine que nuestro método toma una cadena para el valor de la operación con la expectativa de que se proporcione una de las cuatro cadenas constantes. Podemos prever fácilmente un escenario en el que un desarrollador que llama al método envía su propio valor de cadena. Con la enumeración, los valores se limitan a los que definimos. También podemos ver que las enumeraciones son especialmente adecuadas para usar en declaraciones de cambio.

Clase de constantes

Ahora que hemos visto algunas buenas prácticas generales, consideremos el caso en el que una clase de constantes podría ser una buena idea. Imaginemos que nuestra aplicación contiene un paquete de clases que necesitan realizar varios tipos de cálculos matemáticos. En este caso, probablemente tenga sentido para nosotros definir una clase de constantes en ese paquete para las constantes que usaremos en nuestras clases de cálculos.

Creemos una clase ConstantesMatematicas:

public final class ConstantesMatematicas {
    public static final double PI = 3.14159265359;
    static final double PROPORCION_AUREA = 1.6180;
    static final double ACELERACION_GRAVITACIONAL = 9.8;
    static final double EULER = 2.7182818284590452353602874713527;
    
    public enum Operation {
        SUMA,
        RESTA,
        DIVISION,
        MULTIPLICACION
    }
    
    private ConstantesMatematicas() {
        
    }
}

Lo primero que debemos notar es que nuestra clase es definitiva para evitar que se extienda. Además, hemos definido un constructor privado para que no se pueda crear una instancia. Finalmente, podemos ver que hemos aplicado las otras buenas prácticas que discutimos anteriormente en el artículo. Nuestro PI constante es público porque anticipamos la necesidad de acceder a él fuera de nuestro paquete. Las otras constantes las hemos dejado como privadas del paquete, para que podamos acceder a ellas dentro de nuestro paquete. Hemos hecho que todas nuestras constantes sean estáticas y finales y las hemos nombrado en screaming snake case Las operaciones son un conjunto específico de valores, por lo que hemos usado una enumeración para definirlos.

Podemos ver que nuestra clase de constantes específicas a nivel de paquete es diferente de una clase de constantes globales grande porque está localizada en nuestro paquete y contiene constantes relevantes para las clases de ese paquete.

Conclusión

En este artículo, consideramos los pros y los contras de algunos de los patrones y antipatrones más populares que se ven al usar constantes en Java. Comenzamos con algunas reglas básicas de formato, antes de cubrir los antipatrones. Después de conocer un par de antipatrones comunes, analizamos los patrones que a menudo vemos aplicados a las constantes.

Categorized in:

Tagged in:

, , ,