¿"Estático" como una pista semántica sobre la apatridia?

11

Recientemente realicé una refactorización de un proyecto de tamaño mediano en Java para volver y agregar pruebas unitarias. Cuando me di cuenta de lo difícil que era burlarse de los solteros y las estadísticas, finalmente "entendí" lo que he estado leyendo sobre ellos todo este tiempo. (Soy una de esas personas que necesita aprender de la experiencia. Oh, bueno).

Entonces, ahora que estoy usando Spring para crear los objetos y conectarlos, me estoy deshaciendo de las staticpalabras clave izquierda y derecha. (Si pudiera querer burlarme de él, no es realmente estático en el mismo sentido que Math.abs (), ¿verdad?) La cuestión es que me había acostumbrado a usar staticpara denotar que un método no confiaba en cualquier estado del objeto. Por ejemplo:

//Before
import com.thirdparty.ThirdPartyLibrary.Thingy;
public class ThirdPartyLibraryWrapper {
    public static Thingy newThingy(InputType input) {
         new Thingy.Builder().withInput(input).alwaysFrobnicate().build();
    }
}

//called as...
ThirdPartyLibraryWrapper.newThingy(input);
//After
public class ThirdPartyFactory {
    public Thingy newThingy(InputType input) {
         new Thingy.Builder().withInput(input).alwaysFrobnicate().build();
    }
}

//called as...
thirdPartyFactoryInstance.newThingy(input);

Entonces, aquí es donde se pone sensible. Me gustaba la vieja manera porque la letra mayúscula me decía que, al igual que Math.sin (x), ThirdPartyLibraryWrapper.newThingy (x) hacía lo mismo de la misma manera cada vez. No hay estado de objeto para cambiar cómo hace el objeto lo que le pido que haga. Aquí hay algunas respuestas posibles que estoy considerando.

  • Nadie más se siente así, así que hay algo mal conmigo. ¡Tal vez no he internalizado realmente la forma OO de hacer las cosas! Tal vez estoy escribiendo en Java pero pensando en FORTRAN o somesuch. (Lo cual sería impresionante ya que nunca he escrito FORTRAN).
  • Tal vez estoy usando la estática como una especie de proxy para la inmutabilidad con el propósito de razonar sobre el código. Dicho esto, ¿qué pistas debo tener en mi código para que alguien venga a mantenerlo para saber qué tiene estado y qué no?
  • ¿Quizás esto debería ser gratis si elijo buenas metáforas de objetos? por ejemplo thingyWrapper, no parece que tenga un estado independiente de la envoltura, Thingyque puede ser mutable. Del mismo modo, un thingyFactorysonido debería ser inmutable, pero podría tener diferentes estrategias que se eligen en la creación.
leopardo
fuente

Respuestas:

12

Me gustaba la vieja manera porque la letra mayúscula me decía que, al igual que Math.sin (x), ThirdPartyLibraryWrapper.newThingy (x) hacía lo mismo de la misma manera cada vez. No hay estado de objeto para cambiar cómo hace el objeto lo que le pido que haga.

Esa es la forma correcta de hacerlo, sí. Dicho esto, staticno confiere garantías de inmutabilidad o persistencia del estado. Que lo use como una sugerencia para tales garantías tiene sentido, pero de ninguna manera está asegurado.

Considera lo siguiente:

public static class Logger
{
    public static int LogLevel { get; set; }

    public static void Log(string message, int logLevel)
    {
        if (logLevel >= LogLevel)
        {
            // logs the message, but only if it is important enough.
        }
    }
}

Esta clase no solo tiene estado, sino que el comportamiento del Logger.Log()método cambia cuando se cambia ese estado. Es un ejercicio perfectamente legítimo de un staticpatrón de clase, pero no tiene ninguna de las garantías semánticas que sugiere.

Robert Harvey
fuente
Le he dado la marca de verificación porque presenta un contrapunto útil a la base de mi intuición, pero todavía me gustaría tener su opinión sobre cómo debo representar ese contrato / expectativa sobre la falta de estado y la falta de efectos secundarios a través de la codificación convenciones
leoger
1
Normalmente, tales expectativas se incluyen en la documentación del método; Lo mismo se aplica a la seguridad del hilo.
Robert Harvey
5

En mi opinión, lo que usted está diciendo (todas las funciones estáticas son nulipotentes) es una buena forma de escribir código en la mayoría de los casos, pero no creo que cuando observe una función estática deba suponer automáticamente que no tiene efectos secundarios. . La situación puede ser más compleja; tomemos, por ejemplo, la función Class.forName(String)que parece no tener estado pero que en realidad carga una clase en la memoria y eventualmente crea instancias de campos estáticos / ejecuta el inicializador estático; este es un ejemplo de función idempotente (las llamadas eventuales después de la primera no hacen ninguna diferencia) pero no es una función pura (no se puede decir que no tenga efectos secundarios). Esta también es una buena opción, pero puede haber casos en los que tres llamadas distintas a la misma función produzcan tres resultados diferentes; por ejemplo si llamasThread.currentThread() en una aplicación multiproceso no hay garantía de que recibirá el mismo hilo cada vez.

Dicho esto, ¿qué pistas debo tener en mi código para que alguien venga a mantenerlo para saber qué tiene estado y qué no?

Una solución adecuada es documentar (Javadoc por ejemplo); Además, del nombre de la función y de lo que hace a veces se puede inferir que la función estática es una función pura. Por ejemplo, no hay razón para que alguien crea que Assert.assertTrue(boolean)desde JUnit cambiaría algún estado en alguna parte. Por otro lado, cuando System.clearProperty(String)se llama a una función como , es bastante obvio que habrá efectos secundarios.

m3th0dman
fuente
¿Cuál es la diferencia entre "nullipotente" y "apátrida"?
Pacerier
@Pacerier No debería haber ninguna diferencia.
m3th0dman el
4

Dicho esto, ¿qué pistas debo tener en mi código para que alguien venga a mantenerlo para saber qué tiene estado y qué no?

Puedes hacerlos estáticos. FxCop en el mundo de C # lo empujará a hacer cosas estáticas que no hagan variables miembro de referencia. Esto es lo correcto (generalmente).

Cuando me di cuenta de lo difícil que era burlarse de los solteros y las estadísticas, finalmente "entendí" lo que he estado leyendo sobre ellos todo este tiempo.

Moverlos a instancias quizás no sea el enfoque correcto. En cambio, desacople las dependencias de los métodos estáticos de las clases que los usan. Puede probar los métodos estáticos de forma aislada y luego reemplazar la dependencia de los consumidores cuando necesiten probarse.

Telastyn
fuente
¿Puedes pintarme un ejemplo de "desacoplar las dependencias de los métodos estáticos"? No me queda claro qué estás activando. Ya he ocultado los detalles de implementación en un método estático. Al final del día, la clase que lo usa necesita alcanzar esa funcionalidad. ¿Está diciendo que debería crear un objeto de instancia que tenga envoltorios delgados para todos los métodos estáticos?
leoger
1
@leoger: la clase que utiliza el método estático no necesita alcanzar la funcionalidad si está probando esa clase, de lo contrario no se estaría burlando del método estático.
Telastyn