Stiamo abusando dei metodi statici?


13

Un paio di mesi fa ho iniziato a lavorare in un nuovo progetto e, passando attraverso il codice, mi ha colpito la quantità di metodi statici utilizzati. Non solo i metodi di utilità come collectionToCsvString(Collection<E> elements), ma anche molta logica aziendale è mantenuta in essi.

Quando chiesi al ragazzo responsabile della logica alla base, mi disse che era un modo per fuggire dalla tirannia di Spring . Intraprende questo processo di pensiero: per implementare un metodo di creazione della ricevuta del cliente, potremmo avere un servizio

@Service
public class CustomerReceiptCreationService {

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

Ora, il ragazzo ha detto che non gli piace avere le classi inutilmente gestite da Spring, fondamentalmente perché impone la restrizione che le classi client devono essere stesse bean Spring. Finiamo per avere tutto gestito da Spring, che praticamente ci costringe a lavorare con oggetti apolidi in modo procedurale. Più o meno quanto dichiarato qui https://www.javacodegeeks.com/2011/02/domain-driven-design-spring-aspectj.html

Quindi, invece del codice sopra, ha

public class CustomerReceiptCreator {

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

Potrei discutere al punto di evitare che Spring gestisca le nostre lezioni quando possibile, ma ciò che non vedo è il vantaggio di avere tutto statico. Questi metodi statici sono anche apolidi, quindi non molto OO. Mi sentirei più a mio agio con qualcosa come

new CustomerReceiptCreator().createReceipt()

Afferma che i metodi statici hanno alcuni vantaggi extra. Vale a dire:

  • Più facile da leggere. Importa il metodo statico e dobbiamo solo preoccuparci dell'azione, non di quale classe lo sta facendo.
  • È ovviamente un metodo privo di chiamate DB, quindi economico dal punto di vista delle prestazioni; ed è una buona cosa chiarirlo, quindi il potenziale cliente deve andare nel codice e verificarlo.
  • È più facile scrivere test.

Ma sento solo che c'è qualcosa di completamente sbagliato in questo, quindi mi piacerebbe sentire alcuni pensieri degli sviluppatori più esperti su questo.

Quindi la mia domanda è: quali sono le potenziali insidie ​​di questo modo di programmare?




4
Il staticmetodo che stai illustrando sopra è solo un normale metodo di fabbrica. Rendere statici i metodi di fabbrica è la convenzione generalmente accettata, per una serie di ragioni convincenti. Se il metodo di fabbrica è appropriato qui è una questione diversa.
Robert Harvey,

Risposte:


23

Qual è la differenza tra new CustomerReceiptCreator().createReceipt()e CustomerReceiptCreator.createReceipt()? Praticamente nessuno. L'unica differenza significativa è che il primo caso ha una sintassi notevolmente più imbarazzante. Se segui il primo nella convinzione che in qualche modo evitare metodi statici rende il tuo codice OO migliore, ti sbagli gravemente. La creazione di un oggetto solo per chiamare un singolo metodo su di esso è un metodo statico mediante sintassi ottusa.

Il punto in cui le cose si differenziano è quando si inietta CustomerReceiptCreatorinvece di newing. Consideriamo un esempio:

class OrderProcessor {
    @Inject CustomerReceiptCreator customerReceiptCreator;

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

Confrontiamo questa versione del metodo statico:

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

Il vantaggio della versione statica è che posso facilmente dirti come interagisce con il resto del sistema. Non Se non ho usato variabili globali, so che il resto del sistema non è stato in qualche modo modificato. So che nessun'altra parte del sistema potrebbe influire sulla ricevuta qui Se ho usato oggetti immutabili, so che l'ordine non è cambiato e createReceipt è una funzione pura. In tal caso, posso spostare liberamente / rimuovere / etc questa chiamata senza preoccuparmi di effetti imprevedibili casuali altrove.

Non posso fornire le stesse garanzie se ho iniettato il CustomerReceiptCreator. Potrebbe avere uno stato interno che viene modificato dalla chiamata, potrei essere influenzato o modificare un altro stato. Potrebbero esserci relazioni imprevedibili tra le dichiarazioni nella mia funzione in modo tale che la modifica dell'ordine introdurrà bug sorprendenti.

D'altra parte, cosa succede se CustomerReceiptCreatorimprovvisamente ha bisogno di una nuova dipendenza? Diciamo che deve controllare un flag di funzionalità. Se stessimo iniettando, potremmo fare qualcosa del tipo:

public class CustomerReceiptCreator {
    @Injected FeatureFlags featureFlags;

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

Quindi abbiamo finito, perché il codice chiamante verrà iniettato con un CustomerReceiptCreatorche verrà automaticamente iniettato a FeatureFlags.

E se stessimo usando un metodo statico?

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

Ma aspetta! anche il codice chiamante deve essere aggiornato:

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

Naturalmente, questo lascia ancora la domanda da dove processOrderprovenga FeatureFlags. Se siamo fortunati, la pista finisce qui, in caso contrario, la necessità di passare attraverso FeatureFlags viene spinta più in alto nello stack.

C'è un compromesso qui. Metodi statici che richiedono il passaggio esplicito tra le dipendenze che si traduce in più lavoro. Il metodo iniettato riduce il lavoro, ma rende implicite le dipendenze e quindi nasconde rendendo il codice più difficile da ragionare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.