Si está utilizando la palabra clave 'estática' sin la palabra clave 'final', esto debería ser una señal para considerar cuidadosamente su diseño. Incluso la presencia de un 'final' no es un pase libre, ya que un objeto final estático mutable puede ser igual de peligroso.
Estimaría en algún lugar alrededor del 85% de las veces que veo un 'estático' sin un 'final', es INCORRECTO. A menudo, encontraré soluciones extrañas para enmascarar u ocultar estos problemas.
Por favor no cree mutables estáticas. Especialmente colecciones. En general, las Colecciones deben inicializarse cuando se inicializa su objeto contenedor y deben diseñarse de modo que se restablezcan u olviden cuando se olvida su objeto contenedor.
El uso de estática puede crear errores muy sutiles que causarán dolor a los ingenieros. Lo sé, porque he creado y cazado estos errores.
Si desea más detalles, siga leyendo ...
¿Por qué no usar estadísticas?
Hay muchos problemas con la estática, incluida la escritura y ejecución de pruebas, así como errores sutiles que no son obvios de inmediato.
El código que se basa en objetos estáticos no se puede probar fácilmente en la unidad, y las estadísticas no se pueden burlar fácilmente (por lo general).
Si usa estadísticas, no es posible intercambiar la implementación de la clase para probar componentes de nivel superior. Por ejemplo, imagine un CustomerDAO estático que devuelve los objetos del Cliente que carga de la base de datos. Ahora tengo una clase CustomerFilter, que necesita acceder a algunos objetos de Customer. Si CustomerDAO es estático, no puedo escribir una prueba para CustomerFilter sin inicializar primero mi base de datos y completar información útil.
Y la población e inicialización de la base de datos lleva mucho tiempo. Y en mi experiencia, su marco de inicialización de DB cambiará con el tiempo, lo que significa que los datos se transformarán y las pruebas pueden romperse. Es decir, imagine que el Cliente 1 solía ser un VIP, pero el marco de inicialización de DB cambió, y ahora el Cliente 1 ya no es VIP, pero su prueba estaba codificada para cargar el Cliente 1 ...
Un mejor enfoque es crear una instancia de CustomerDAO y pasarlo al CustomerFilter cuando se construye. (Un enfoque aún mejor sería usar Spring u otro marco de Inversión de Control.
Una vez que haga esto, puede simular o eliminar rápidamente un DAO alternativo en su CustomerFilterTest, lo que le permite tener más control sobre la prueba,
Sin el DAO estático, la prueba será más rápida (sin inicialización de db) y más confiable (porque no fallará cuando cambie el código de inicialización de db). Por ejemplo, en este caso, asegurarse de que el Cliente 1 sea y siempre será un VIP, en lo que respecta a la prueba.
Ejecutando pruebas
Las estadísticas causan un problema real cuando se ejecutan conjuntos de pruebas unitarias juntas (por ejemplo, con su servidor de Integración Continua). Imagine un mapa estático de objetos de Socket de red que permanece abierto de una prueba a otra. La primera prueba podría abrir un Socket en el puerto 8080, pero se olvidó de borrar el Mapa cuando la prueba se derribó. Ahora, cuando se inicia una segunda prueba, es probable que se bloquee cuando intenta crear un nuevo Socket para el puerto 8080, ya que el puerto todavía está ocupado. Imagine también que las referencias de Socket en su Colección estática no se eliminan, y (con la excepción de WeakHashMap) nunca son elegibles para la recolección de basura, lo que causa una pérdida de memoria.
Este es un ejemplo demasiado generalizado, pero en sistemas grandes, este problema ocurre TODO EL TIEMPO. La gente no piensa en las pruebas unitarias que inician y detienen su software repetidamente en la misma JVM, pero es una buena prueba del diseño de su software, y si tiene aspiraciones de alta disponibilidad, es algo que debe tener en cuenta.
Estos problemas a menudo surgen con objetos de marco, por ejemplo, sus capas de acceso a la base de datos, almacenamiento en caché, mensajería y registro. Si está utilizando Java EE o algunos de los mejores frameworks, probablemente manejen mucho de esto por usted, pero si como yo está tratando con un sistema heredado, es posible que tenga muchos frameworks personalizados para acceder a estas capas.
Si la configuración del sistema que se aplica a estos componentes del marco cambia entre las pruebas unitarias, y el marco de prueba unitaria no destruye y reconstruye los componentes, estos cambios no pueden tener efecto, y cuando una prueba se basa en esos cambios, fallarán .
Incluso los componentes no marco están sujetos a este problema. Imagine un mapa estático llamado OpenOrders. Escribe una prueba que crea algunas órdenes abiertas y comprueba para asegurarse de que todas estén en el estado correcto, luego finaliza la prueba. Otro desarrollador escribe una segunda prueba que coloca los pedidos que necesita en el mapa de OpenOrders, luego afirma que el número de pedidos es exacto. Ejecutadas individualmente, ambas pruebas pasarían, pero cuando se ejecutan juntas en una suite, fallarán.
Peor aún, la falla puede basarse en el orden en que se ejecutaron las pruebas.
En este caso, al evitar la estática, se evita el riesgo de persistencia de datos en las instancias de prueba, lo que garantiza una mejor confiabilidad de la prueba.
Errores sutiles
Si trabaja en un entorno de alta disponibilidad, o en cualquier lugar donde los subprocesos puedan iniciarse y detenerse, la misma preocupación mencionada anteriormente con los conjuntos de pruebas unitarias puede aplicarse cuando su código también se ejecuta en producción.
Cuando se trata de hilos, en lugar de usar un objeto estático para almacenar datos, es mejor usar un objeto inicializado durante la fase de inicio del hilo. De esta manera, cada vez que se inicia el subproceso, se crea una nueva instancia del objeto (con una configuración potencialmente nueva) y evita que los datos de una instancia del subproceso se filtren a la siguiente instancia.
Cuando un hilo muere, un objeto estático no se restablece ni se recolecta basura. Imagine que tiene un hilo llamado "EmailCustomers", y cuando se inicia, completa una colección de cadenas estática con una lista de direcciones de correo electrónico, luego comienza a enviar cada una de las direcciones. Digamos que el hilo se interrumpe o cancela de alguna manera, por lo que su marco de alta disponibilidad reinicia el hilo. Luego, cuando se inicia el hilo, vuelve a cargar la lista de clientes. Pero debido a que la colección es estática, podría retener la lista de direcciones de correo electrónico de la colección anterior. Ahora algunos clientes pueden recibir correos electrónicos duplicados.
Un aparte: final estático
El uso de "static final" es efectivamente el equivalente de Java de un C #define, aunque existen diferencias de implementación técnica. AC / C ++ #define es intercambiado fuera del código por el preprocesador, antes de la compilación. Un "final estático" de Java terminará en la memoria residente en la pila. De esa manera, es más similar a una variable de "constante estática" en C ++ que a una #define.
Resumen
Espero que esto ayude a explicar algunas razones básicas por las que las estadísticas son problemáticas. Si está utilizando un marco Java moderno como Java EE o Spring, etc., es posible que no encuentre muchas de estas situaciones, pero si está trabajando con un gran cuerpo de código heredado, pueden volverse mucho más frecuentes.