¿Estamos abusando de los métodos estáticos?

12

Hace un par de meses comencé a trabajar en un nuevo proyecto y, al pasar por el código, me paró la cantidad de métodos estáticos utilizados. No solo los métodos de utilidad como collectionToCsvString(Collection<E> elements) , sino también mucha lógica empresarial se mantiene en ellos.

Cuando le pregunté al hombre responsable de la razón detrás de esto, dijo que era una forma de escapar de la tiranía de Spring . Se trata de algo relacionado con este proceso de pensamiento: para implementar un método de creación de recibos de clientes, podríamos tener un servicio

@Service
public class CustomerReceiptCreationService {

    public CustomerReceipt createReceipt(Object... args) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        return receipt;
    }
}

Ahora, el tipo dijo que no le gusta que Spring administre las clases innecesariamente, básicamente porque impone la restricción de que las clases de los clientes deben ser los Spring Spring. Terminamos con todo gestionado por Spring, lo que nos obliga a trabajar con objetos sin estado de manera procesal. Más o menos lo que se indica aquí enlace

Así que en lugar del código anterior, él tiene

public class CustomerReceiptCreator {

    public static CustomerReceipt createReceipt(Object... args) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        return receipt;
    }
}

Podría argumentar hasta el punto de evitar que Spring administre nuestras clases cuando sea posible, pero lo que no veo es el beneficio de tener todo lo estático. Estos métodos estáticos también son sin estado, por lo que tampoco son muy OO. Me sentiría más cómodo con algo como

new CustomerReceiptCreator().createReceipt()

Afirma que los métodos estáticos tienen algunos beneficios adicionales. A saber:

  • Más fácil de leer. Importa el método estático y solo hay que preocuparnos. sobre la acción, no qué clase lo está haciendo.
  • es obviamente un método libre de llamadas DB, por lo que el rendimiento es barato; y es una buena cosa para dejarlo claro, para que el cliente potencial tenga que entrar en el código y verifica eso.
  • Más fácil de escribir pruebas.

Pero siento que hay algo que no está del todo bien con esto, así que me gustaría escuchar los pensamientos de algunos desarrolladores más experimentados sobre esto.

Entonces, mi pregunta es, ¿cuáles son los peligros potenciales de esta forma de programación?

    
pregunta user3748908 07.03.2016 - 21:33

1 respuesta

22

¿Cuál es la diferencia entre new CustomerReceiptCreator().createReceipt() y CustomerReceiptCreator.createReceipt() ? Bastante ninguno. La única diferencia significativa es que el primer caso tiene una sintaxis mucho más incómoda. Si sigue el primero en la creencia de que, de alguna manera, evitar los métodos estáticos hace que su código sea mejor OO, está muy equivocado. Crear un objeto solo para invocar un método único en él es un método estático por sintaxis obtusa.

Cuando las cosas se ponen diferentes es cuando se inyecta CustomerReceiptCreator en lugar de new ing. Consideremos un ejemplo:

class OrderProcessor {
    @Inject CustomerReceiptCreator customerReceiptCreator;

    void processOrder(Order order) {
        ...
        CustomerReceipt receipt = customerReceiptCreator.createReceipt(order);
        ...
    }
}

Comparemos esto con una versión de método estático:

void processOrder(Order order) {
    ...
    CustomerReceipt receipt = CustomerReceiptCreator.createReceipt(order);
    ...
}

La ventaja de la versión estática es que puedo decirle fácilmente cómo interactúa con el resto del sistema. No lo hace Si no he usado variables globales, sé que el resto del sistema no se ha modificado de alguna manera. Sé que ninguna otra parte del sistema podría estar afectando el recibo aquí. Si he usado objetos inmutables, sé que el orden no ha cambiado y que createReceipt es una función pura. En ese caso, puedo mover / eliminar / etc libremente esta llamada sin preocuparme por efectos aleatorios impredecibles en otra parte.

No puedo ofrecer las mismas garantías si he inyectado el CustomerReceiptCreator . Puede tener un estado interno que se modifica con la llamada, puede que me afecte o cambie otro estado. Puede haber relaciones impredecibles entre sentencias en mi función, de modo que cambiar el orden introducirá errores sorprendentes.

Por otra parte, ¿qué sucede si CustomerReceiptCreator de repente necesita una nueva dependencia? Digamos que necesita comprobar una característica de la bandera. Si estuviéramos inyectando, podríamos hacer algo como:

public class CustomerReceiptCreator {
    @Injected FeatureFlags featureFlags;

    public CustomerReceipt createReceipt(Order order) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        if (featureFlags.isFlagSet(Flags::FOOBAR)) {
           ...
        }
        return receipt;
    }
}

Luego hemos terminado, porque el código de llamada se inyectará con un CustomerReceiptCreator que se inyectará automáticamente un FeatureFlags .

¿Qué pasaría si estuviéramos usando un método estático?

public class CustomerReceiptCreator {
    public static CustomerReceipt createReceipt(Order order, FeatureFlags featureFlags) {
        CustomerReceipt receipt = new CustomerReceipt();
        // creation logic
        if (featureFlags.isFlagSet(Flags::FOOBAR)) {
           ...
        }
        return receipt;
    }
}

¡Pero espera! el código de llamada también debe actualizarse:

void processOrder(Order order) {
    ...
    CustomerReceipt receipt = CustomerReceiptCreator.createReceipt(order, featureFlags);
    ...
}

Por supuesto, esto todavía deja la pregunta de dónde processOrder obtiene su FeatureFlags de. Si tenemos suerte, el camino termina aquí; si no, la necesidad de pasar por FeatureFlags se incrementa en la pila.

Hay una compensación aquí. Métodos estáticos que requieren pasar de manera explícita las dependencias que resultan en más trabajo. El método inyectado reduce el trabajo, pero hace que las dependencias sean implícitas y, por lo tanto, ocultas, lo que hace que el código sea más difícil de razonar.

    
respondido por el Winston Ewert 07.03.2016 - 23:35

Lea otras preguntas en las etiquetas