Usare molti metodi statici è una cosa negativa?


97

Tendo a dichiarare come statici tutti i metodi in una classe quando quella classe non richiede di tenere traccia degli stati interni. Ad esempio, se devo trasformare A in B e non fare affidamento su uno stato interno C che può variare, creo una trasformazione statica. Se c'è uno stato interno C che voglio essere in grado di regolare, aggiungo un costruttore per impostare C e non uso una trasformazione statica.

Ho letto vari consigli (incluso su StackOverflow) di NON abusare dei metodi statici, ma non riesco ancora a capire cosa ci sia di sbagliato nella regola pratica sopra.

È un approccio ragionevole o no?

Risposte:


152

Esistono due tipi di metodi statici comuni:

  • Un metodo statico "sicuro" darà sempre lo stesso output per gli stessi input. Non modifica le variabili globali e non chiama metodi statici "non sicuri" di nessuna classe. In sostanza, stai usando un tipo limitato di programmazione funzionale: non aver paura di questi, vanno bene.
  • Un metodo statico "non sicuro" modifica lo stato globale o i proxy in un oggetto globale o un altro comportamento non verificabile. Questi sono rimandi alla programmazione procedurale e dovrebbero essere rifattorizzati se possibile.

Ci sono alcuni usi comuni della statica "non sicura" - ad esempio, nel pattern Singleton - ma tieni presente che nonostante i nomi carini che li chiami, stai solo mutando le variabili globali. Pensa attentamente prima di utilizzare statiche non sicure.


Questo era esattamente il problema che dovevo risolvere: l'uso, o meglio l'abuso, degli oggetti Singleton.
overslacked

Grazie per l'eccellente risposta. La mia domanda è: se i singleton vengono passati come parametri ai metodi statici, ciò rende il metodo statico non sicuro?
Tony D

1
I termini "funzione pura" e "funzione impura" sono nomi dati nella programmazione funzionale a ciò che chiamate statica "sicura" e "non sicura".
Omnimike

19

Un oggetto senza uno stato interno è una cosa sospetta.

Normalmente, gli oggetti incapsulano stato e comportamento. Un oggetto che incapsula solo il comportamento è strano. A volte è un esempio di peso leggero o peso mosca .

Altre volte, è un design procedurale eseguito in un linguaggio a oggetti.


6
Ho sentito quello che stai dicendo, ma come può qualcosa come un oggetto matematico incapsulare qualcosa di diverso dal comportamento?
JonoW

10
Ha solo detto sospettoso, non sbagliato, e ha assolutamente ragione.
Bill K

2
@JonoW: la matematica è un caso molto speciale in cui ci sono molte funzioni senza stato. Ovviamente, se stai facendo programmazione funzionale in Java, avresti molte funzioni senza stato.
S.Lott

13

Questo è davvero solo un seguito alla grande risposta di John Millikin.


Sebbene possa essere sicuro rendere statici i metodi senza stato (che sono praticamente funzioni), a volte può portare a un accoppiamento difficile da modificare. Considera di avere un metodo statico in quanto tale:

public class StaticClassVersionOne {
    public static void doSomeFunkyThing(int arg);
}

Che chiami come:

StaticClassVersionOne.doSomeFunkyThing(42);

Il che va benissimo e molto conveniente, finché non ti imbatti in un caso in cui devi modificare il comportamento del metodo statico e scopri che sei strettamente vincolato StaticClassVersionOne. Forse potresti modificare il codice e andrebbe bene, ma se ci fossero altri chiamanti dipendenti dal vecchio comportamento, sarà necessario tenerne conto nel corpo del metodo. In alcuni casi, il corpo del metodo può diventare piuttosto brutto o non mantenibile se cerca di bilanciare tutti questi comportamenti. Se dividi i metodi potresti dover modificare il codice in diversi punti per tenerne conto o effettuare chiamate a nuove classi.

Ma considera se avessi creato un'interfaccia per fornire il metodo e l'hai data ai chiamanti, ora che il comportamento deve cambiare, una nuova classe può essere creata per implementare l'interfaccia, che è più pulita, più facilmente testabile e più manutenibile, e che invece viene data ai chiamanti. In questo scenario le classi chiamanti non devono essere alterate o addirittura ricompilate e le modifiche vengono localizzate.

Può o non può essere una situazione probabile, ma penso che valga la pena considerarla.


5
Ritengo che questo non sia solo uno scenario probabile, ma rende la statica l'ultima risorsa. Anche la statistica rende il TDD un incubo. Ovunque si utilizzi la statica, non è possibile eseguire il mock up, è necessario sapere quali sono l'input e l'output per testare una classe non correlata. Ora, se modifichi il comportamento della statica, i tuoi test su classi non correlate che utilizzano quella statica vengono interrotti. Inoltre, diventa una dipendenza nascosta che non è possibile trasmettere al costruttore per notificare agli sviluppatori una dipendenza potenzialmente importante.
DanCaveman

6

L'altra opzione è aggiungerli come metodi non statici sull'oggetto di origine:

cioè, cambiando:

public class BarUtil {
    public static Foo transform(Bar toFoo) { ... }
}

in

public class Bar {
    ...
    public Foo transform() { ...}
}

tuttavia in molte situazioni questo non è possibile (ad esempio, la normale generazione di codice di classe da XSD / WSDL / ecc.), o renderà la classe molto lunga, e i metodi di trasformazione possono spesso essere un vero problema per oggetti complessi e li vuoi solo nella loro classe separata. Quindi sì, ho metodi statici nelle classi di utilità.


5

Le classi statiche vanno bene fintanto che vengono utilizzate nei posti giusti.

Vale a dire: metodi che sono metodi "foglia" (non modificano lo stato, semplicemente trasformano l'input in qualche modo). Buoni esempi di questo sono cose come Path.Combine. Questo genere di cose sono utili e rendono la sintassi più concisa.

I problemi che ho con la statica sono numerosi:

In primo luogo, se hai classi statiche, le dipendenze sono nascoste. Considera quanto segue:

public static class ResourceLoader
{
    public static void Init(string _rootPath) { ... etc. }
    public static void GetResource(string _resourceName)  { ... etc. }
    public static void Quit() { ... etc. }
}

public static class TextureManager
{
    private static Dictionary<string, Texture> m_textures;

    public static Init(IEnumerable<GraphicsFormat> _formats) 
    {
        m_textures = new Dictionary<string, Texture>();

        foreach(var graphicsFormat in _formats)
        {
              // do something to create loading classes for all 
              // supported formats or some other contrived example!
        }
    }

    public static Texture GetTexture(string _path) 
    {
        if(m_textures.ContainsKey(_path))
            return m_textures[_path];

        // How do we know that ResourceLoader is valid at this point?
        var texture = ResourceLoader.LoadResource(_path);
        m_textures.Add(_path, texture);
        return texture; 
    }

    public static Quit() { ... cleanup code }       
}

Guardando TextureManager, non puoi dire quali passaggi di inizializzazione devono essere eseguiti guardando un costruttore. È necessario approfondire la classe per trovare le sue dipendenze e inizializzare le cose nell'ordine corretto. In questo caso, è necessario che ResourceLoader venga inizializzato prima di essere eseguito. Ora aumenta questo incubo di dipendenza e probabilmente puoi indovinare cosa succederà. Immagina di provare a mantenere il codice dove non esiste un ordine esplicito di inizializzazione. Confronta questo con l'inserimento di dipendenze con le istanze: in tal caso il codice non verrà nemmeno compilato se le dipendenze non vengono soddisfatte!

Inoltre, se usi le statistiche che modificano lo stato, è come un castello di carte. Non si sa mai chi ha accesso a cosa e il design tende ad assomigliare a un mostro di spaghetti.

Infine, e altrettanto importante, l'uso della statistica lega un programma a un'implementazione specifica. Il codice statico è l'antitesi della progettazione per la testabilità. Testare un codice pieno di statiche è un incubo. Una chiamata statica non può mai essere scambiata con una doppia di test (a meno che non si utilizzino framework di test progettati specificamente per simulare i tipi statici), quindi un sistema statico fa sì che tutto ciò che lo utilizza sia un test di integrazione istantaneo.

In breve, la statica va bene per alcune cose e per piccoli strumenti o codice usa e getta non ne scoraggerei l'uso. Tuttavia, oltre a ciò, sono un incubo sanguinario per la manutenibilità, il buon design e la facilità di test.

Ecco un buon articolo sui problemi: http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/


4

Il motivo per cui vieni messo in guardia dai metodi statici è che il loro utilizzo perde uno dei vantaggi degli oggetti. Gli oggetti sono destinati all'incapsulamento dei dati. Ciò impedisce che si verifichino effetti collaterali imprevisti che evitano i bug. I metodi statici non hanno dati incapsulati * e quindi non ottengono questo vantaggio.

Detto questo, se non si utilizzano dati interni, vanno bene da usare e leggermente più veloci da eseguire. Assicurati però di non toccare i dati globali in essi contenuti.

  • Alcuni linguaggi hanno anche variabili a livello di classe che consentirebbero l'incapsulamento di dati e metodi statici.

4

Sembra essere un approccio ragionevole. La ragione per cui non vuoi usare troppe classi / metodi statici è che finisci per allontanarti dalla programmazione orientata agli oggetti e più nel regno della programmazione strutturata.

Nel tuo caso in cui stai semplicemente trasformando A in B, dì che tutto ciò che stiamo facendo è trasformare il testo da cui partire

"hello" =>(transform)=> "<b>Hello!</b>"

Allora un metodo statico avrebbe senso.

Tuttavia, se si invocano frequentemente questi metodi statici su un oggetto e questo tende ad essere unico per molte chiamate (ad esempio, il modo in cui lo si utilizza dipende dall'input), o fa parte del comportamento intrinseco dell'oggetto, lo farebbe essere saggio renderlo parte dell'oggetto e mantenerlo in uno stato. Un modo per farlo sarebbe implementarlo come interfaccia.

class Interface{
    method toHtml(){
        return transformed string (e.g. "<b>Hello!</b>")
    }

    method toConsole(){
        return transformed string (e.g. "printf Hello!")
    }
}


class Object implements Interface {
    mystring = "hello"

    //the implementations of the interface would yield the necessary 
    //functionality, and it is reusable across the board since it 
    //is an interface so... you can make it specific to the object

   method toHtml()
   method toConsole()
}

Modifica: un buon esempio di ottimo utilizzo dei metodi statici sono i metodi helper html in Asp.Net MVC o Ruby. Creano elementi html che non sono legati al comportamento di un oggetto e sono quindi statici.

Modifica 2: programmazione funzionale modificata in programmazione strutturata (per qualche motivo mi sono confuso), puntelli a Torsten per averlo sottolineato.


2
Non credo che l'uso di metodi statici si qualifichi come programmazione funzionale, quindi immagino che tu intenda programmazione strutturata.
Torsten Marek

3

Recentemente ho refactoring un'applicazione per rimuovere / modificare alcune classi che erano state inizialmente implementate come classi statiche. Nel corso del tempo queste classi hanno acquisito così tanto e le persone hanno continuato a etichettare le nuove funzioni come statiche, poiché non c'era mai un'istanza in circolazione.

Quindi, la mia risposta è che le classi statiche non sono intrinsecamente cattive, ma potrebbe essere più facile iniziare a creare istanze ora, quindi dover eseguire il refactoring in seguito.


3

Lo considererei un odore di design. Se ti ritrovi a utilizzare metodi prevalentemente statici, probabilmente non hai un design OO molto buono. Non è necessariamente un male, ma come con tutti gli odori mi farebbe fermare e rivalutare. Indica che potresti essere in grado di realizzare un design OO migliore, o che forse dovresti andare nella direzione opposta ed evitare completamente OO per questo problema.


2

Andavo avanti e indietro tra una classe con un sacco di metodi statici e un singleton. Entrambi risolvono il problema, ma il singleton può essere sostituito molto più facilmente con più di uno. (I programmatori sembrano sempre così certi che ci sarà solo 1 di qualcosa e mi sono trovato abbastanza volte sbagliato per rinunciare completamente ai metodi statici tranne in alcuni casi molto limitati).

Ad ogni modo, il singleton ti dà la possibilità di passare successivamente qualcosa nella factory per ottenere un'istanza diversa e questo cambia il comportamento dell'intero programma senza refactoring. Cambiare una classe globale di metodi statici in qualcosa con dati di "supporto" diversi o un comportamento leggermente diverso (classe figlio) è un grosso problema.

E i metodi statici non hanno vantaggi simili.

Quindi sì, sono cattivi.


1

Finché non entra in gioco lo stato interno, va bene. Si noti che di solito i metodi statici dovrebbero essere thread-safe, quindi se si utilizzano strutture di dati di supporto, utilizzarli in modo thread-safe.


1

Se sai che non avrai mai bisogno di usare lo stato interno di C, va bene. Se ciò dovesse mai cambiare in futuro, però, dovresti rendere il metodo non statico. Se all'inizio non è statico, puoi semplicemente ignorare lo stato interno se non ne hai bisogno.


1

Se è un metodo di utilità, è bello renderlo statico. Guava e Apache Commons si basano su questo principio.

La mia opinione su questo è puramente pragmatica. Se è il codice della tua app, i metodi statici generalmente non sono la cosa migliore da avere. I metodi statici hanno gravi limitazioni di unit test: non possono essere facilmente derisi: non è possibile iniettare una funzionalità statica derisa in qualche altro test. Inoltre, di solito non è possibile inserire funzionalità in un metodo statico.

Quindi nella logica della mia app di solito ho piccole chiamate di metodi statici simili a utilità. Cioè

static cutNotNull(String s, int length){
  return s == null ? null : s.substring(0, length);
}

uno dei vantaggi è che non provo questi metodi :-)


1

Beh, ovviamente non esiste un proiettile d'argento. Le classi statiche vanno bene per piccole utilità / aiutanti. Ma l'utilizzo di metodi statici per la programmazione della logica aziendale è certamente un male. Considera il codice seguente

   public class BusinessService
   {

        public Guid CreateItem(Item newItem, Guid userID, Guid ownerID)
        {
            var newItemId = itemsRepository.Create(createItem, userID, ownerID);
            **var searchItem = ItemsProcessor.SplitItem(newItem);**
            searchRepository.Add(searchItem);
            return newItemId;
        }
    }

ItemsProcessor.SplitItem(newItem);Viene visualizzata una chiamata di metodo statico a It smells cause

  • Non hai una dipendenza esplicita dichiarata e se non approfondisci il codice potresti trascurare l'accoppiamento tra la tua classe e il contenitore del metodo statico
  • Non è possibile testare BusinessServiceisolandolo da ItemsProcessor(la maggior parte degli strumenti di test non deride le classi statiche) e rende impossibile il test di unità. Nessun test unitario == bassa qualità

0

I metodi statici sono generalmente una cattiva scelta anche per il codice senza stato. Crea invece una classe singleton con questi metodi che viene istanziata una volta e iniettata in quelle classi che desiderano utilizzare i metodi. Tali classi sono più facili da deridere e testare. Sono molto più orientati agli oggetti. Puoi avvolgerli con un proxy quando necessario. Le statistiche rendono OO molto più difficile e non vedo motivo per usarle in quasi tutti i casi. Non al 100% ma quasi tutti.

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.