Stiamo abusando dei metodi statici?

12

Un paio di mesi fa ho iniziato a lavorare in un nuovo progetto, e quando ho letto il codice mi sono accorto della quantità di metodi statici usati. Non solo i metodi di utilità come collectionToCsvString(Collection<E> elements) , ma anche una grande quantità di logica aziendale è contenuta in essi.

Quando ho chiesto al ragazzo responsabile della logica dietro a questo, ha detto che era un modo di fuggire dalla tirannia di Spring . Va in giro questo processo di pensiero: per implementare un metodo di creazione scontrino 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 classi gestite inutilmente da Spring, fondamentalmente perché impone la restrizione che le classi client debbano essere Spring bean stesse. Finiamo per avere tutto gestito da Spring, che praticamente ci costringe a lavorare con oggetti stateless in modo procedurale. Più o meno ciò che è affermato qui link

Quindi, invece del codice precedente, ha

public class CustomerReceiptCreator {

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

Potrei argomentare sul punto di evitare Spring che gestisce le nostre classi quando possibile, ma quello che non vedo è il vantaggio di avere tutto statico. Questi metodi statici sono anche stateless, quindi anche non molto OO. Mi sentirei più a mio agio con qualcosa come

new CustomerReceiptCreator().createReceipt()

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

  • Più facile da leggere. Importa il metodo statico e abbiamo solo bisogno di cura sull'azione, non su ciò che la classe sta facendo.
  • È ovviamente un metodo privo di chiamate DB, quindi a buon mercato dal punto di vista delle prestazioni; ed è una buona cosa per chiarire, in modo che il potenziale cliente abbia bisogno di entrare il codice e controllalo.
  • Più facile scrivere test.

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

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

    
posta user3748908 07.03.2016 - 21:33
fonte

1 risposta

22

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

Dove le cose diventano diverse è quando iniettate CustomerReceiptCreator invece di new ing. Consideriamo un esempio:

class OrderProcessor {
    @Inject CustomerReceiptCreator customerReceiptCreator;

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

Confrontiamo questa versione di un metodo statico:

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

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

Non posso dare le stesse garanzie se ho iniettato il CustomerReceiptCreator . Potrebbe avere uno stato interno che viene modificato dalla chiamata, potrebbe essere interessato o modificare un altro stato. Ci potrebbero essere relazioni imprevedibili tra le affermazioni nella mia funzione in modo tale che la modifica dell'ordine introduca bug sorprendenti.

D'altra parte, cosa succede se CustomerReceiptCreator ha improvvisamente bisogno di una nuova dipendenza? Diciamo che è necessario controllare un flag di funzionalità. Se stessimo iniettando, potremmo fare qualcosa come:

public class CustomerReceiptCreator {
    @Injected FeatureFlags featureFlags;

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

Allora abbiamo finito, perché il codice chiamante verrà iniettato con un CustomerReceiptCreator che verrà automaticamente iniettato a FeatureFlags .

E se usassimo 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 su dove processOrder ottiene il suo FeatureFlags da. Se siamo fortunati, la pista finisce qui, se no, la necessità di passare FeatureFlags viene spinta più in alto nello stack.

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

    
risposta data 07.03.2016 - 23:35
fonte

Leggi altre domande sui tag