Perché lo stato globale è così malvagio?


328

Prima di iniziare, lasciami dire che sono ben consapevole dei concetti di Iniezione di astrazione e dipendenza. Non ho bisogno di aprire gli occhi qui.

Bene, molti di noi dicono (anche) molte volte senza capire veramente, "Non usare le variabili globali", o "I Singleton sono cattivi perché sono globali". Ma cosa c'è di così brutto nel minaccioso stato globale?

Diciamo che ho bisogno di una configurazione globale per la mia applicazione, ad esempio i percorsi delle cartelle di sistema o le credenziali del database a livello di applicazione.

In tal caso, non vedo alcuna buona soluzione se non quella di fornire queste impostazioni in una sorta di spazio globale, che sarà comunemente disponibile per l'intera applicazione.

So che è male per abusarne , ma è lo spazio globale davvero CHE male? E se lo è, quali buone alternative ci sono?


5
puoi utilizzare una classe di configurazione che gestisce l'accesso a tali impostazioni. penso che sia buona norma avere un solo posto dove leggere le impostazioni di configurazione da file di configurazione e / o database mentre altri oggetti li recuperano da quella cosa di configurazione. rende il tuo design più pulito e prevedibile.
Hajo,

25
Dov'è la differenza con l'utilizzo di un globale? Il tuo "unico e unico posto" suona molto sospettosamente come un Singleton.
Madara Uchiha,

130
L'unico vero male sono i dogmi.
Pieter B,

34
Usare lo stato globale con i tuoi colleghi programmatori è come usare lo stesso spazzolino da denti con i tuoi amici - puoi ma non sai mai quando qualcuno deciderà di spingerlo nel culo.
ziGi,

5
Il diavolo è malvagio. Lo stato globale non è né male né buono. Può essere molto utile o dannoso a seconda di come lo usi. Non ascoltare gli altri e impara a programmare correttamente.
annoying_squid

Risposte:


282

Molto brevemente, rende imprevedibile lo stato del programma.

Per elaborare, immagina di avere un paio di oggetti che utilizzano entrambi la stessa variabile globale. Supponendo che non si stia utilizzando una fonte di casualità in nessun punto all'interno di uno dei moduli, è possibile prevedere (e quindi testare) l'output di un particolare metodo se lo stato del sistema è noto prima di eseguire il metodo.

Tuttavia, se un metodo in uno degli oggetti attiva un effetto collaterale che modifica il valore dello stato globale condiviso, allora non si sa più quale sia lo stato iniziale quando si esegue un metodo nell'altro oggetto. Ora non puoi più prevedere quale output otterrai quando esegui il metodo e quindi non puoi testarlo.

A livello accademico questo potrebbe non sembrare così grave, ma essere in grado di unitizzare il codice di test è un passo importante nel processo di dimostrazione della sua correttezza (o almeno idoneità allo scopo).

Nel mondo reale, ciò può avere conseguenze molto serie. Supponiamo di avere una classe che popola una struttura di dati globale e una classe diversa che consuma i dati in quella struttura di dati, modificandone lo stato o distruggendoli nel processo. Se la classe del processore esegue un metodo prima che venga eseguita la classe populator, il risultato è che probabilmente la classe del processore avrà dati incompleti da elaborare e la struttura dei dati su cui stava lavorando la classe populator potrebbe essere danneggiata o distrutta. Il comportamento del programma in queste circostanze diventa completamente imprevedibile e probabilmente porterà a una perdita epica.

Inoltre, lo stato globale danneggia la leggibilità del codice. Se il tuo codice ha una dipendenza esterna che non è esplicitamente introdotta nel codice, chiunque ottenga il compito di mantenere il tuo codice dovrà cercarlo per capire da dove proviene.

Per quanto riguarda le alternative esistenti, è impossibile non avere affatto uno stato globale, ma in pratica è di solito possibile limitare lo stato globale a un singolo oggetto che avvolge tutti gli altri e a cui non si deve mai fare riferimento facendo affidamento sulle regole di scoping della lingua che stai utilizzando. Se un oggetto particolare necessita di uno stato particolare, allora dovrebbe richiederlo esplicitamente facendolo passare come argomento al suo costruttore o con un metodo setter. Questo è noto come iniezione di dipendenza.

Può sembrare sciocco passare in uno stato in cui è già possibile accedere a causa delle regole di scoping di qualunque linguaggio si stia utilizzando, ma i vantaggi sono enormi. Ora, se qualcuno guarda il codice da solo, è chiaro quale stato ha bisogno e da dove proviene. Ha anche enormi vantaggi per quanto riguarda la flessibilità del modulo di codice e quindi le opportunità per riutilizzarlo in contesti diversi. Se lo stato viene passato e le modifiche allo stato sono locali per il blocco di codice, è possibile passare in qualsiasi stato desiderato (se si tratta del tipo di dati corretto) e far elaborare il codice. Il codice scritto in questo stile tende ad avere l'aspetto di una raccolta di componenti vagamente associati che possono essere facilmente scambiati. Il codice di un modulo non dovrebbe importare da dove provenga lo stato, ma solo come elaborarlo.

Ci sono molte altre ragioni per cui passare lo stato in giro è di gran lunga superiore al fare affidamento sullo stato globale. Questa risposta non è affatto completa. Probabilmente potresti scrivere un intero libro sul perché lo stato globale è cattivo.


61
Fondamentalmente, poiché chiunque può cambiare lo stato, non puoi fare affidamento su di esso.
Oded,

21
@Truth L'alternativa? Iniezione delle dipendenze .
rdlowrey,

12
Il tuo argomento principale non si applica realmente a qualcosa che è effettivamente di sola lettura, come un oggetto che rappresenta una configurazione.
Frank

53
Niente di tutto ciò spiega perché le variabili globali di sola lettura siano cattive ... di cui l'OP ha esplicitamente chiesto. Altrimenti è una buona risposta, sono solo sorpreso che l'OP lo abbia contrassegnato come "accettato" quando ha affrontato esplicitamente un punto diverso.
Konrad Rudolph,

20
being able to unit test code is a major step in the process of proving its correctness (or at least fitness for purpose). No non lo è. "Sono passati due decenni da quando è stato sottolineato che i test del programma possono dimostrare in modo convincente la presenza di bug, ma non possono mai dimostrare la loro assenza. Dopo aver citato devotamente questa osservazione ben pubblicizzata, l'ingegnere del software ritorna all'ordine del giorno e continua per perfezionare le sue strategie di test, proprio come l'alchimista di un tempo, che ha continuato a perfezionare le sue purificazioni crisocosmiche ". - Djikstra, 1988. (Questo fa 4,5 decenni ormai ...)
Mason Wheeler,

135

Lo stato globale mutevole è malvagio per molte ragioni:

  • Bug da uno stato globale mutevole - molti bug insidiosi sono causati dalla mutabilità. I bug che possono essere causati da una mutazione da qualsiasi parte del programma sono ancora più complicati, poiché spesso è difficile rintracciare la causa esatta
  • Scarsa testabilità : se si dispone di uno stato globale mutabile, sarà necessario configurarlo per tutti i test scritti. Questo rende i test più difficili (e quindi le persone essendo le persone hanno meno probabilità di farlo!). ad esempio nel caso di credenziali di database a livello di applicazione, cosa succede se un test deve accedere a un database di test specifico diverso da tutto il resto?
  • Inflessibilità : cosa succede se una parte del codice richiede un valore nello stato globale, ma un'altra parte richiede un altro valore (ad esempio un valore temporaneo durante una transazione)? Improvvisamente hai un brutto pezzo di refactoring tra le mani
  • Impurità delle funzioni - le funzioni "pure" (cioè quelle in cui il risultato dipende solo dai parametri di input e non hanno effetti collaterali) sono molto più facili da ragionare e comporre per costruire programmi più grandi. Le funzioni che leggono o manipolano lo stato globale mutevole sono intrinsecamente impure.
  • Comprensione del codice - il comportamento del codice che dipende da molte variabili globali mutabili è molto più complicato da capire - è necessario comprendere l'intervallo di possibili interazioni con la variabile globale prima di poter ragionare sul comportamento del codice. In alcune situazioni, questo problema può diventare intrattabile.
  • Problemi di concorrenza : lo stato globale mutabile in genere richiede una qualche forma di blocco quando utilizzato in una situazione concorrente. Questo è molto difficile da correggere (è una causa di bug) e aggiunge molta più complessità al tuo codice (difficile / costoso da mantenere).
  • Prestazioni : più thread che basano continuamente sullo stesso stato globale causano contese nella cache e rallenteranno il sistema in generale.

Alternative allo stato globale mutevole:

  • Parametri delle funzioni : spesso trascurati, ma parametrizzare meglio le funzioni è spesso il modo migliore per evitare lo stato globale. Ti costringe a risolvere l'importante domanda concettuale: quali informazioni richiede questa funzione per fare il suo lavoro? A volte ha senso avere una struttura di dati chiamata "Contesto" che può essere trasmessa attraverso una catena di funzioni che racchiude tutte le informazioni rilevanti.
  • Iniezione delle dipendenze - come per i parametri delle funzioni, appena eseguita un po 'prima (alla costruzione dell'oggetto piuttosto che all'invocazione della funzione). Fai attenzione se le tue dipendenze sono oggetti mutabili, questo può causare rapidamente gli stessi problemi dello stato globale mutabile .....
  • Lo stato globale immutabile è per lo più innocuo - è effettivamente una costante. Ma assicurati che sia davvero una costante e che non sarai tentato di trasformarlo in uno stato globale mutevole in un secondo momento!
  • Singleton immutabili - praticamente lo stesso di uno stato globale immutabile, tranne per il fatto che è possibile rinviare l'istanza fino a quando non sono necessari. Utile ad es. Per grandi strutture dati fisse che richiedono costosi pre-calcoli una tantum. I singoletti mutevoli sono ovviamente equivalenti allo stato globale mutabile e sono quindi malvagi :-)
  • Rilegatura dinamica : disponibile solo in alcuni linguaggi come Common Lisp / Clojure, ma ciò consente effettivamente di associare un valore all'interno di un ambito controllato (in genere su base thread-local) che non influisce su altri thread. In una certa misura si tratta di un modo "sicuro" per ottenere lo stesso effetto di una variabile globale, poiché si sa che sarà interessato solo l'attuale thread di esecuzione. Ciò è particolarmente utile nel caso in cui si disponga di più thread ciascuno per gestire transazioni indipendenti, ad esempio.

11
Penso che il passaggio di un oggetto di contesto tramite parametro di funzione o iniezione di dipendenza causerebbe problemi se il contesto è mutabile, lo stesso problema dell'utilizzo dello stato globale mutabile.
Alfredo Osorio,

1
Tutta roba buona e amen! Ma la domanda riguarda l'immutabile stato globale
MarkJ,

@Alfredo - molto vero. Anche se non è così male poiché almeno l'ambito può essere in qualche modo controllato. Ma in generale, è più semplice risolvere il problema rendendo immutabili contesti e dipendenze.
Mikera,

2
+1 per mutevole / immutabile. I globi immutabili sono ok. Anche quelli che sono pigri caricati ma non cambiano mai. Naturalmente, non esporre le variabili globali, ma un'interfaccia o API globale.
Jess

1
@giorgio La domanda chiarisce che le variabili in questione ottengono i loro valori all'avvio e non cambiano mai successivamente durante l'esecuzione del programma (cartelle di sistema, credenziali del database). Cioè immutabile, non cambia una volta che gli è stato dato un valore. Personalmente uso anche la parola "stato" perché può essere diversa da un'esecuzione all'altra o su una macchina diversa. Potrebbero esserci parole migliori.
MarkJ,

62
  1. Dal momento che tutta la tua dannata app può essere utilizzata, è sempre incredibilmente difficile rimodificarli di nuovo. Se cambi qualcosa a che fare con il tuo globale, tutto il tuo codice deve cambiare. Questo è un mal di testa di manutenzione, molto più che la semplice capacità grepdel nome del tipo di scoprire quali funzioni lo utilizzano.
  2. Sono cattivi perché introducono dipendenze nascoste, che interrompono il multithreading, che è sempre più vitale per sempre più applicazioni.
  3. Lo stato della variabile globale è sempre completamente inaffidabile, perché tutto il codice potrebbe farci qualcosa.
  4. Sono davvero difficili da testare.
  5. Rendono difficile chiamare l'API. "Devi ricordare di chiamare SET_MAGIC_VARIABLE () prima di chiamare l'API" sta solo chiedendo a qualcuno di dimenticarlo. Rende l'utilizzo dell'API soggetto a errori, causando bug difficili da trovare. Usandolo come parametro regolare, si forza il chiamante a fornire correttamente un valore.

Basta passare un riferimento a funzioni che ne hanno bisogno. Non è così difficile.


3
Bene, puoi avere una classe di configurazione globale che incapsula il blocco e È progettata per cambiare stato in qualsiasi momento possibile. Sceglierei questo approccio piuttosto che istanziare i lettori di configurazione da 1000x posizioni nel codice. Ma sì, l'imprevedibilità è la cosa peggiore di loro.
Coder,

6
@Coder: Notare che l'alternativa sana a un globale non è "configurare i lettori da 1000x posizioni nel codice", ma un lettore di configurazione, che crea un oggetto di configurazione, che i metodi possono accettare come parametro (-> Iniezione delle dipendenze).
sleske,

2
Nitpicking: perché è più facile richiedere un tipo che un globale? E la domanda riguarda i globi di sola lettura, quindi i punti 2 e 3 non sono rilevanti
MarkJ,

2
@MarkJ: spero che nessuno, per esempio, abbia mai oscurato il nome.
DeadMG

2
@DeadMG, Re "..è sempre completamente inaffidabile ..", lo stesso vale per l'iniezione di dipendenza. Solo perché lo hai reso un parametro non significa che all'oggetto è garantito uno stato fisso. Da qualche parte in fondo alla funzione potresti aver fatto una chiamata a un'altra funzione che modifica lo stato della variabile iniettata in alto.
Pacerier,

34

Se dici "stato", ciò significa di solito "stato mutabile". E lo stato mutabile globale è totalmente malvagio, perché significa che qualsiasi parte del programma può influenzare qualsiasi altra parte (cambiando lo stato globale).

Immagina di eseguire il debug di un programma sconosciuto: scopri che la funzione A si comporta in un certo modo per determinati parametri di input, ma a volte funziona in modo diverso per gli stessi parametri. Scoprite che utilizza la variabile globale x .

Cerchi i luoghi che modificano xe scopri che ci sono cinque posti che lo modificano. Ora buona fortuna scoprire in quali casi la funzione A fa cosa ...


Lo stato globale così immutabile non è così malvagio?
FrustratedWithFormsDesigner,

50
Direi che lo stato globale immutabile è la ben nota buona pratica nota come "costanti".
Telastyn,

6
Lo stato globale immutabile non è male, è solo cattivo :-). È ancora problematico a causa dell'accoppiamento che introduce (rende più difficili le modifiche, il riutilizzo e il test dell'unità), ma crea molti meno problemi, quindi in casi semplici è generalmente accettabile.
sleske,

2
IFF uno utilizza le variabili globali quindi solo un pezzo di codice dovrebbe modificarlo. Il resto è libero di leggerlo. I problemi degli altri che lo cambiano non scompaiono con le funzioni di incapsulamento e accesso. Non è a questo che servono quei costrutti.
Phkahler,

1
@Pacerier: Sì, cambiare un'interfaccia ampiamente utilizzata è difficile, indipendentemente dal fatto che venga utilizzata come variabile globale o locale. Tuttavia, questo è indipendente dal mio punto, che è che l' interazione di diversi pezzi di codice è difficile da capire con varchi globali.
sleske,

9

Hai risposto alla tua domanda. Sono difficili da gestire se "abusati", ma possono essere utili e [in qualche modo prevedibili se usati correttamente, da qualcuno che sa come contenerli. La manutenzione e le modifiche a / sui globali sono di solito un incubo, aggravate dall'aumentare delle dimensioni dell'applicazione.

I programmatori esperti che sanno distinguere i globali come unica opzione e che sono la soluzione più semplice, possono avere problemi minimi nell'utilizzarli. Ma gli infiniti possibili problemi che possono sorgere con il loro uso richiedono il consiglio di non usarli.

modifica: per chiarire cosa intendo, i globuli sono imprevedibili per natura. Come per qualsiasi cosa imprevedibile, puoi prendere provvedimenti per contenere l'imprevedibilità, ma ci sono sempre limiti a ciò che può essere fatto. Aggiungete a ciò la seccatura dei nuovi sviluppatori che si uniscono al progetto di dover affrontare variabili relativamente sconosciute, le raccomandazioni contro l'uso dei globuli dovrebbero essere comprensibili.


9

Ci sono molti problemi con Singletons: ecco i due maggiori problemi nella mia mente.

  • Ciò rende problematici i test unitari. Lo stato globale può essere contaminato da un test all'altro

  • Fa valere la dura regola "L'unico e unico", che, anche se non può cambiare, all'improvviso lo fa. Un intero gruppo di codice di utilità che utilizzava l'oggetto accessibile a livello globale deve quindi essere modificato.

Detto questo, la maggior parte dei sistemi ha bisogno di Big Global Objects. Si tratta di elementi di grandi dimensioni e costosi (ad esempio, Database Connection Manager) o che contengono informazioni sullo stato pervasivo (ad esempio, informazioni di blocco).

L'alternativa a un Singleton è quella di creare questi Big Global Objects all'avvio e trasmessi come parametri a tutte le classi o metodi che richiedono l'accesso a questo oggetto.

Il problema qui è che si finisce con un grande gioco di "pass-the-pacco". Hai un grafico dei componenti e delle loro dipendenze, e alcune classi creano altre classi e ognuna deve contenere un mucchio di componenti di dipendenza solo perché i loro componenti generati (o i componenti generati) ne hanno bisogno.

Si verificano nuovi problemi di manutenzione. Un esempio: improvvisamente il tuo "WidgetFactory", il componente in profondità nel grafico ha bisogno di un oggetto timer che desideri deridere. Tuttavia, "WidgetFactory" è creato da "WidgetBuilder" che fa parte di "WidgetCreationManager" e devi conoscere tre classi su questo oggetto timer anche se solo uno lo utilizza effettivamente. Ti ritrovi a voler rinunciare e tornare a Singletons e rendere questo oggetto timer accessibile a livello globale.

Fortunatamente, questo è esattamente il problema risolto da un framework di Iniezione delle dipendenze. Puoi semplicemente dire al framework quali classi deve creare e usa la riflessione per capire il grafico delle dipendenze per te e costruisce automaticamente ogni oggetto quando è necessario.

Quindi, in sintesi, i Singleton sono cattivi e l'alternativa è usare un framework di Iniezione delle dipendenze.

Mi capita di usare Castle Windsor, ma hai l'imbarazzo della scelta. Vedi questa pagina dal 2008 per un elenco dei framework disponibili.


8

Prima di tutto, affinché l'iniezione di dipendenza sia "stateful", dovresti usare i singoli, quindi le persone che dicono che questa è in qualche modo un'alternativa si sbagliano. Le persone usano oggetti di contesto globale in ogni momento ... Anche lo stato della sessione, ad esempio, è essenzialmente una variabile globale. Passare tutto in giro sia per iniezione di dipendenza o meno non è sempre la soluzione migliore. Attualmente lavoro su un'applicazione molto grande che utilizza molti oggetti di contesto globale (singoli punti iniettati tramite un contenitore IoC) e non è mai stato un problema il debug. Soprattutto con un'architettura guidata dagli eventi, si può preferire usare oggetti di contesto globali piuttosto che passare qualsiasi cosa sia cambiata. Dipende da chi chiedi.

Tutto può essere abusato e dipende anche dal tipo di applicazione. L'uso di variabili statiche ad esempio in un'app Web è completamente diverso da un'app desktop. Se riesci ad evitare le variabili globali, allora fallo, ma a volte hanno i loro usi. Per lo meno assicurati che i tuoi dati globali siano in un chiaro oggetto contestuale. Per quanto riguarda il debug, non è possibile risolvere nulla con uno stack di chiamate e alcuni punti di interruzione.

Voglio sottolineare che l'uso cieco di variabili globali è una cattiva idea. Le funzioni dovrebbero essere riutilizzabili e non dovrebbero importare da dove provengono i dati - il riferimento a variabili globali abbina la funzione a un input di dati specifico. Questo è il motivo per cui dovrebbe essere passato e perché l'iniezione di dipendenza può essere utile, anche se hai ancora a che fare con un archivio di contesto centralizzato (via singletons).

A proposito ... Alcune persone pensano che l'iniezione di dipendenza sia sbagliata, incluso il creatore di Linq, ma ciò non impedirà alle persone di usarla, incluso me stesso. Alla fine l'esperienza sarà il tuo miglior insegnante. Ci sono momenti per seguire le regole e volte per infrangerle.


4

Dato che alcune altre risposte qui fanno la distinzione tra stato globale mutevole e immutabile , vorrei affermare che anche le variabili / impostazioni globali immutabili sono spesso un fastidio .

Considera la domanda:

... Diciamo che ho bisogno di una configurazione globale per la mia applicazione, ad esempio i percorsi delle cartelle di sistema o le credenziali del database a livello di applicazione. ...

Bene, per un piccolo programma, questo probabilmente non è un problema, ma non appena lo fai con componenti in un sistema leggermente più grande, scrivere improvvisamente i test automatici diventa più difficile perché tutti i test (~ eseguiti nello stesso processo) devono funzionare con lo stesso valore di configurazione globale.

Se tutti i dati di configurazione vengono passati in modo esplicito, i componenti diventano molto più facili da testare e non dovrai mai preoccuparti di come avviare un valore di configurazione globale per più test, magari anche in parallelo.


3

Bene, per esempio, puoi imbatterti esattamente nello stesso problema che puoi fare con i singoli. Ciò che oggi sembra una "cosa globale di cui ho solo bisogno di uno" si trasformerà improvvisamente in qualcosa di cui hai bisogno di più lungo la strada.

Ad esempio, oggi crei questo sistema di configurazione globale perché desideri una configurazione globale per l'intero sistema. Pochi anni dopo, porti a un altro sistema e qualcuno dice "ehi, sai, questo potrebbe funzionare meglio se ci fosse una configurazione globale generale e una configurazione specifica della piattaforma". All'improvviso hai tutto questo lavoro per rendere le tue strutture globali non globali, quindi puoi averne di più.

(Questo non è un esempio casuale ... questo è successo con il nostro sistema di configurazione nel progetto in cui mi trovo attualmente.)

Considerando che il costo per creare qualcosa di non globale è di solito banale, è sciocco farlo. Stai solo creando problemi futuri.


Il tuo esempio non è ciò che significava OP. Stai chiaramente parlando di un'istanza di configurazioni astratte (solo una struttura di dati del gestore della configurazione, senza chiavi di preferenza specifiche dell'applicazione) più volte perché vuoi dividere tutte le chiavi in ​​un'istanza in esecuzione del programma su più istanze della classe del gestore della configurazione. (Ad esempio, ciascuna di queste istanze potrebbe gestire un file di configurazione, ad esempio).
Jo So,

3

L'altro problema, curiosamente, è che rendono difficile ridimensionare un'applicazione perché non sono abbastanza "globali". Lo scopo di una variabile globale è il processo.

Se si desidera ridimensionare l'applicazione utilizzando più processi o eseguendo su più server non è possibile. Almeno non fino a quando non si eliminano tutti i globi e li si sostituisce con qualche altro meccanismo.


3

Perché lo stato globale è così malvagio?

Lo stato globale mutevole è malvagio perché è molto difficile per il nostro cervello prendere in considerazione più di alcuni parametri alla volta e capire come combinano sia una prospettiva temporale che una prospettiva di valore per influenzare qualcosa.

Pertanto, siamo molto cattivi nel debug o nel test di un oggetto il cui comportamento ha più di alcune ragioni esterne da modificare durante l'esecuzione di un programma. Per non parlare del fatto che dobbiamo ragionare su dozzine di questi oggetti presi insieme.


3

Le lingue progettate per la progettazione di sistemi sicuri e robusti spesso eliminano del tutto lo stato mutabile globale . (Probabilmente questo non significa globali, poiché gli oggetti immutabili sono in un certo senso non davvero statali poiché non hanno mai una transizione di stato.)

Joe-E è un esempio e David Wagner spiega la decisione in questo modo:

L'analisi di chi ha accesso a un oggetto e il principio del privilegio minimo sono entrambi sovvertiti quando le capacità sono archiviate in variabili globali e quindi sono potenzialmente leggibili da qualsiasi parte del programma. Una volta che un oggetto è disponibile a livello globale, non è più possibile limitare l'ambito di analisi: l'accesso all'oggetto è un privilegio che non può essere trattenuto da alcun codice nel programma. Joe-E evita questi problemi verificando che l'ambito globale non contenga capacità, solo dati immutabili.

Quindi un modo di pensarci è

  1. La programmazione è un problema di ragionamento distribuito. I programmatori di grandi progetti devono dividere il programma in pezzi che possono essere ragionati da singoli individui.
  2. Più piccolo è l'ambito, più facile è ragionarlo. Ciò vale sia per gli individui che per gli strumenti di analisi statica che provano a dimostrare le proprietà di un sistema e per i test che devono testare le proprietà di un sistema.
  3. Fonti di autorità significative che sono disponibili a livello globale rendono difficile ragionare sulle proprietà del sistema.

Pertanto, lo stato mutabile a livello globale rende più difficile

  1. progettare sistemi robusti,
  2. più difficile dimostrare le proprietà di un sistema e
  3. più difficile essere certi che i test siano testati in un ambito simile al proprio ambiente di produzione.

Lo stato mutabile globale è simile all'inferno DLL . Nel tempo, parti diverse di un sistema di grandi dimensioni richiedono comportamenti leggermente diversi da parti condivise di stato mutabile. Risolvere l'inferno delle DLL e le incoerenze condivise dello stato mutabile richiede un coordinamento su larga scala tra team diversi. Questi problemi non si verificherebbero se lo stato globale fosse stato studiato correttamente per cominciare.


2

I globuli non sono poi così male. Come affermato in molte altre risposte, il vero problema è che oggi il percorso della cartella globale potrebbe essere uno o più centinaia. Se stai scrivendo un programma rapido e unico, usa i globi se è più facile. In generale, tuttavia, consentire i multipli anche quando pensi solo di averne bisogno è la strada da percorrere. Non è piacevole dover ristrutturare un programma ampio e complesso che improvvisamente deve parlare con due database.

Ma non danneggiano l'affidabilità. Qualsiasi dato a cui si fa riferimento da molte parti del programma può causare problemi se cambia in modo imprevisto. Gli enumeratori si bloccano quando la raccolta che stanno enumerando viene modificata a metà dell'enumerazione. Gli eventi in coda agli eventi possono giocare brutti scherzi. Le discussioni possono sempre provocare scompiglio. Tutto ciò che non è una variabile locale o un campo immutabile è un problema. I globi sono questo tipo di problema, ma non lo risolverai rendendoli non globali.

Se stai per scrivere su un file e il percorso della cartella cambia, la modifica e la scrittura devono essere sincronizzate. (Come una delle mille cose che potrebbero andare storte, supponiamo di afferrare il percorso, quindi quella directory viene eliminata, quindi il percorso della cartella viene modificato in una buona directory, quindi si tenta di scrivere nella directory eliminata.) Il problema esiste se il percorso della cartella è globale o è uno dei mille che il programma sta attualmente utilizzando.

Esiste un vero problema con i campi a cui è possibile accedere da diversi eventi su una coda, diversi livelli di ricorsione o thread diversi. Per renderlo semplice (e semplicistico): le variabili locali sono buone e i campi sono cattivi. Ma gli ex globuli continueranno ad essere campi, quindi questo problema (per quanto di fondamentale importanza) non si applica allo stato Bene o Male dei campi globali.

Aggiunta: problemi di multithreading:

(Nota che puoi avere problemi simili con una coda di eventi o chiamate ricorsive, ma il multithreading è di gran lunga il peggiore.) Considera il seguente codice:

if (filePath != null)  text = filePath.getName();

Se filePathè una variabile locale o un qualche tipo di costante, il programma non fallirà durante l'esecuzione perché filePathè nullo. Il controllo funziona sempre. Nessun altro thread può modificarne il valore. Altrimenti , non ci sono garanzie. Quando ho iniziato a scrivere programmi multithreading in Java, ho sempre ricevuto NullPointerExceptions su righe come questa. Qualunquealtri thread possono modificare il valore in qualsiasi momento e spesso lo fanno. Come diverse altre risposte sottolineano, questo crea seri problemi per i test. La dichiarazione di cui sopra può funzionare un miliardo di volte, superando test approfonditi e completi, quindi esplodere una volta in produzione. Gli utenti non saranno in grado di riprodurre il problema e non accadrà più fino a quando non si saranno convinti di vedere le cose e di averlo dimenticato.

I globali hanno sicuramente questo problema, e se riesci a eliminarli completamente o sostituirli con costanti o variabili locali, questa è un'ottima cosa. Se hai un codice stateless in esecuzione su un server web, probabilmente puoi farlo. In genere, tutti i problemi di multithreading possono essere rilevati dal database.

Ma se il tuo programma deve ricordare cose da un'azione dell'utente alla successiva, avrai campi accessibili da tutti i thread in esecuzione. Passare da un campo globale a un campo così non globale non aiuterà l'affidabilità.


1
Puoi chiarire cosa intendi con questo ?: "Qualunque cosa che non sia una variabile locale o un campo immutabile è un problema. I globali sono questo tipo di problema, ma non lo risolverai rendendoli non globali."
Andres F.,

1
@AndresF .: ho esteso la mia risposta. Penso che sto adottando un approccio desktop in cui la maggior parte delle persone in questa pagina è più un codice server con un database. "Globale" può significare cose diverse in questi casi.
RalphChapin,

2

Lo stato è inevitabile in qualsiasi vera applicazione. Puoi avvolgerlo come preferisci, ma un foglio di calcolo deve contenere dati nelle celle. Puoi creare oggetti cella con solo funzioni come interfaccia, ma ciò non limita il numero di posizioni che possono chiamare un metodo sulla cella e modificare i dati. Si creano intere gerarchie di oggetti per provare a nascondere le interfacce in modo che altre parti del codice non possanomodificare i dati per impostazione predefinita. Ciò non impedisce che un riferimento all'oggetto contenente venga passato arbitrariamente. Nulla di tutto ciò elimina da solo i problemi di concorrenza. Rende più difficile la proliferazione dell'accesso ai dati, ma in realtà non elimina i problemi percepiti con i globali. Se qualcuno vuole modificare un pezzo di stato, lo faranno se è globale o attraverso un'API complessa (il successivo scoraggerà solo, non impedirà).

Il vero motivo per non utilizzare l'archiviazione globale è evitare collisioni di nomi. Se carichi più moduli che dichiarano lo stesso nome globale, o hai un comportamento indefinito (molto difficile da eseguire il debug perché i test unitari passeranno) o un errore del linker (sto pensando C - Il tuo linker avvisa o fallisce?).

Se vuoi riutilizzare il codice, devi essere in grado di prendere un modulo da un altro posto e non farlo accidentalmente passare sul tuo globale perché ne hanno usato uno con lo stesso nome. Oppure, se sei fortunato e ricevi un errore, non devi cambiare tutti i riferimenti in una sezione di codice per evitare la collisione.


1

Quando è facile vedere e accedere a tutto lo stato globale, i programmatori finiscono inevitabilmente per farlo. Ciò che ottieni è inespresso e difficile da tracciare le dipendenze (int blahblah significa che array foo è valido in qualsiasi cosa). In sostanza, è quasi impossibile mantenere gli invarianti del programma poiché tutto può essere modificato in modo indipendente. someInt ha una relazione tra altri, difficile da gestire e più difficile da dimostrare se è possibile cambiare direttamente in qualsiasi momento.

Detto questo, può essere fatto (molto tempo fa quando era l'unico modo in alcuni sistemi), ma quelle abilità sono perse. Riguardano principalmente le convenzioni di codifica e denominazione: il campo è andato avanti per una buona ragione. Il compilatore e il linker fanno un lavoro migliore nel controllare gli invarianti nei dati protetti / privati ​​di classi / moduli piuttosto che affidarsi agli umani per seguire un piano generale e leggere la fonte.


1
"ma quelle abilità si perdono" ... non ancora del tutto. Di recente ho lavorato in una software house che giura su "Clarion", uno strumento per la generazione di codice che ha un proprio linguaggio di base che manca di funzionalità come passare argomenti alle routine secondarie ... Gli sviluppatori non erano contenti di alcun suggerimento su "cambiamento" o "modernizzazione", alla fine mi sono stufato delle mie osservazioni e mi hanno descritto come carente e incompetente. Ho dovuto partire ...
Louis Somers,

1

Non dirò se le variabili globali sono buone o cattive, ma ciò che aggiungerò alla discussione è dire che se non si utilizza lo stato globale, probabilmente si sta sprecando molta memoria, specialmente quando usi classess per memorizzare le loro dipendenze nei campi.

Per lo stato globale, non esiste tale problema, tutto è globale.

Ad esempio: immagina uno scenario seguente: hai una griglia 10x10 che è composta da "Board" e "Tile" di classe.

Se vuoi farlo nel modo OOP, probabilmente passerai l'oggetto "Board" a ogni "Tile". Diciamo ora che "Tile" ha 2 campi di tipo "byte" che ne memorizzano le coordinate. La memoria totale che occorrerebbe su una macchina a 32 bit per una tessera sarebbe (1 + 1 + 4 = 6) byte: 1 per x coord, 1 per y coord e 4 per un puntatore alla scheda. Ciò fornisce un totale di 600 byte per la configurazione di tessere 10x10

Ora, nel caso in cui la Board sia nell'ambito globale, un singolo oggetto accessibile da ogni riquadro dovresti ottenere solo 2 byte di memoria per ogni riquadro, ovvero i byte delle coordinate xey. Ciò darebbe solo 200 byte.

Quindi in questo caso si ottiene 1/3 dell'utilizzo della memoria se si utilizza solo lo stato globale.

Questo, oltre ad altre cose, credo sia una ragione per cui l'ambito globale rimane ancora in linguaggi (relativamente) di basso livello come il C ++


1
Questo è fortemente dipendente dalla lingua. Nei linguaggi in cui i programmi vengono eseguiti in modo persistente, può essere vero che sei in grado di risparmiare memoria utilizzando lo spazio globale e i singoli punti invece di creare un'istanza di molte classi, ma anche quell'argomento è traballante. Si tratta di priorità. In linguaggi come PHP (che viene eseguito una volta per richiesta e gli oggetti non persistono), anche quell'argomento è controverso.
Madara Uchiha,

1
@MadaraUchiha No, questo argomento non è traballante in alcun modo. Questi sono fatti oggettivi, a meno che alcune VM o altri linguaggi compilati non eseguano l'ottimizzazione del codice hardcore con questo tipo di problema. Altrimenti è ancora rilevante. Anche con i programmi lato server che erano "uno scatto", la memoria è riservata durante il tempo di esecuzione. Con server a carico elevato questo può essere un punto critico.
luke1985,

0

Esistono diversi fattori da considerare con lo stato globale:

  1. Spazio di memoria del programmatore
  2. Globali immutabili / scrivere una volta globali.
  3. Globali mutevoli
  4. Dipendenza dai globi.

Più globuli hai, maggiore è la possibilità di introdurre duplicati e quindi di rompere le cose quando i duplicati non sono sincronizzati. Mantenere tutti i globi nella tua memoria umana incapace è sia necessario che doloroso.

Immutabili / scrivi una volta sono generalmente OK, ma fai attenzione agli errori della sequenza di inizializzazione.

I globi mutevoli sono spesso scambiati per globi immutabili ...

Una funzione che utilizza globalmente ha effettivamente parametri "nascosti" extra, rendendo più difficile il refactoring.

Lo stato globale non è malvagio, ma ha un costo definito: usalo quando il beneficio supera il costo.

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.