In che modo i globali sono diversi da un database?


250

Mi sono appena imbattuto in questa vecchia domanda chiedendomi cosa c'è di così male nello stato globale e la risposta più votata e accettata afferma che non puoi fidarti di alcun codice che funzioni con variabili globali, perché qualche altro codice da qualche altra parte potrebbe venire e modificarne la valore e quindi non sai quale sarà il comportamento del tuo codice perché i dati sono diversi! Ma quando lo guardo, non posso fare a meno di pensare che sia una spiegazione molto debole, perché in che modo differisce dal lavorare con i dati archiviati in un database?

Quando il tuo programma funziona con i dati di un database, non ti importa se un altro codice nel tuo sistema lo sta cambiando, o anche se un programma completamente diverso lo sta cambiando, del resto. Non ti importa quali siano i dati; questo è l'intero punto. Tutto ciò che conta è che il tuo codice gestisca correttamente i dati che incontra. (Ovviamente sto sorvolando il problema spesso spinoso della cache qui, ma ignoriamolo per il momento.)

Ma se i dati con cui stai lavorando provengono da una fonte esterna su cui il tuo codice non ha alcun controllo, come un database (o input dell'utente, un socket di rete, o un file, ecc ...) e non c'è niente di sbagliato con quello, allora come sono i dati globali all'interno del codice stesso - su cui il tuo programma ha un grado molto maggiore di controllo - in qualche modo una cosa cattiva quando è ovviamente molto meno cattiva di cose perfettamente normali che nessuno vede come un problema?


117
È bello vedere membri veterani sfidare un po 'i dogmi ...
svidgen,

10
In un'applicazione, di solito si fornisce un mezzo per accedere al database, questo mezzo viene passato alle funzioni che vogliono accedere al database. Non lo fai con le variabili globali, semplicemente sai che sono a portata di mano. Questa è una differenza chiave proprio lì.
Andy,

45
Lo stato globale è come avere un singolo database con una singola tabella con una singola riga con infinitamente molte colonne a cui si accede contemporaneamente da un numero arbitrario di applicazioni.
BevynQ,

42
Anche i database sono malvagi.
Stig Hemmer,

27
È divertente "invertire" l'argomento che fai qui e andare nella direzione opposta. Una struttura che ha un puntatore a un'altra struttura è logicamente solo una chiave esterna in una riga di una tabella che digita in un'altra riga di un'altra tabella. In che modo lavorare con qualsiasi codice, inclusi gli elenchi collegati a piedi, è diverso dalla manipolazione dei dati in un database? Risposta: non lo è. Domanda: perché allora manipoliamo le strutture di dati in memoria e le strutture di dati in database utilizzando strumenti così diversi? Risposta: davvero non lo so! Sembra un incidente della storia piuttosto che un buon design.
Eric Lippert,

Risposte:


118

In primo luogo, direi che la risposta che colleghi a sopravvaluta quel particolare problema e che il male principale dello stato globale è che introduce l'accoppiamento in modi imprevedibili che possono rendere difficile cambiare il comportamento del tuo sistema in futuro.

Ma approfondendo ulteriormente questo problema, ci sono differenze tra lo stato globale in una tipica applicazione orientata agli oggetti e lo stato contenuto in un database. In breve, i più importanti di questi sono:

  • I sistemi orientati agli oggetti consentono di sostituire un oggetto con una diversa classe di oggetti, purché si tratti di un sottotipo del tipo originale. Ciò consente di modificare il comportamento , non solo i dati .

  • Lo stato globale in un'applicazione non fornisce in genere la forte coerenza garantita da un database: non vi sono transazioni durante le quali viene visualizzato uno stato coerente, né aggiornamenti atomici, ecc.

Inoltre, possiamo vedere lo stato del database come un male necessario; è impossibile eliminarlo dai nostri sistemi. Lo stato globale, tuttavia, non è necessario. Possiamo eliminarlo del tutto. Quindi, anche se i problemi con un database sono altrettanto gravi , possiamo ancora eliminare alcuni dei potenziali problemi e una soluzione parziale è meglio di nessuna soluzione.


44
Penso che il punto della coerenza sia in realtà il motivo principale: quando nel codice vengono utilizzate variabili globali, di solito non è possibile stabilire quando siano effettivamente inizializzate. Le dipendenze tra i moduli sono profondamente nascoste all'interno della sequenza delle chiamate e cose semplici come lo scambio di due chiamate possono produrre bug davvero cattivi perché improvvisamente alcune variabili globali non vengono più inizializzate correttamente quando viene utilizzato per la prima volta. Almeno questo è il problema che ho con il codice legacy con cui devo lavorare e che rende il refactoring un incubo.
cmaster

24
@DavidHammen In realtà ho lavorato sulla simulazione dello stato mondiale per un gioco online, che è chiaramente nella categoria di applicazione di cui stai parlando, e anche lì non avrei (e non) usato lo stato globale per questo. Anche se è possibile ottenere alcuni miglioramenti dell'efficienza utilizzando lo stato globale, il problema è che lo stato globale non è scalabile . Diventa difficile da usare quando si passa da un'architettura a thread singolo a architettura multi-thread. Diventa inefficiente quando si passa a un'architettura NUMA. Diventa impossibile quando ti sposti in un'architettura distribuita. Il documento che citi risale al ...
Jules

24
1993. Questi problemi erano quindi meno importanti. Gli autori stavano lavorando su un singolo sistema di processore, simulando le interazioni di 1.000 oggetti. In un sistema moderno avresti probabilmente eseguito una simulazione di quel tipo almeno su un sistema dual-core, ma molto probabilmente potrebbe essere almeno 6 core in un singolo sistema. Per problemi ancora più grandi, lo avresti eseguito su un cluster. Per questo tipo di cambiamento, è necessario evitare lo stato globale perché lo stato globale non può essere effettivamente condiviso.
Jules,

19
Penso che chiamare lo stato del database un "male necessario" sia un po 'complicato. Voglio dire, da quando lo stato è diventato malvagio? Lo stato è l'intero scopo di un database. Lo stato è informazione. Senza stato, tutto ciò che hai sono operatori. A che cosa servono gli operatori senza qualcosa su cui operare? Quello stato deve andare da qualche parte. Alla fine della giornata, la programmazione funzionale è solo un mezzo per raggiungere un fine e senza uno stato di mutazione non avrebbe senso fare nulla. È un po 'come un fornaio che definisce la torta un male necessario, non è un male. È l'intero punto della cosa.
J ...

5
@DavidHammen "c'è ancora qualche oggetto che conosce almeno un po 'ogni oggetto del gioco" Non necessariamente vero. Una tecnica importante nella moderna simulazione distribuita sta sfruttando la località e facendo approssimazioni in modo tale che gli oggetti distanti non debbano conoscere tutto ciò che è lontano, solo i dati forniti loro dai proprietari di quegli oggetti distanti.
JAB

75

Innanzitutto, quali sono i problemi con le variabili globali, in base alla risposta accettata alla domanda che hai collegato?

Molto brevemente, rende imprevedibile lo stato del programma.

I database sono, nella maggior parte dei casi, conformi ACID. ACID affronta in modo specifico i problemi sottostanti che renderebbero un archivio dati imprevedibile o inaffidabile.

Inoltre, lo stato globale danneggia la leggibilità del codice.

Questo perché le variabili globali esistono in un ambito molto lontano dal loro utilizzo, forse anche in un file diverso. Quando si utilizza un database, si utilizza un set di record o un oggetto ORM che è locale al codice che si sta leggendo (o che dovrebbe essere).

I driver di database in genere forniscono un'interfaccia coerente e comprensibile per accedere ai dati uguali indipendentemente dal dominio problematico. Quando si ottengono dati da un database, il programma dispone di una copia dei dati. Gli aggiornamenti sono atomici. Contrasto con variabili globali, in cui più thread o metodi potrebbero operare sullo stesso pezzo di dati senza atomicità a meno che non si aggiunga la sincronizzazione. Gli aggiornamenti dei dati sono imprevedibili e difficili da rintracciare. Gli aggiornamenti possono essere intercalati, causando esempi di libri di testo standard bog di corruzione dei dati multithread (ad es. Incrementi interlacciati).

I database in genere modellano inizialmente dati diversi rispetto alle variabili globali, ma a parte questo per un momento, i database sono progettati da zero per essere un archivio di dati conforme ACID che mitiga molte delle preoccupazioni con le variabili globali.


4
+1 Quello che stai dicendo è che i database hanno transazioni, rendendo possibile leggere e scrivere atomicamente più parti dello stato globale. Un buon punto, che può essere aggirato solo usando variabili globali per ogni informazione completamente indipendente.
l0b0,

1
Le transazioni @ l0b0 sono il meccanismo che raggiunge la maggior parte degli obiettivi ACID, corretto. Ma l'interfaccia DB stessa rende più chiaro il codice portando i dati in un ambito più locale. Pensa di utilizzare un RecordSet JDBC con un blocco try-with-resources o una funzione ORM che ottiene una porzione di dati utilizzando una singola chiamata di funzione. Confronta questo con la gestione dei dati molto lontani dal codice che stai leggendo in un globale da qualche parte.

1
Quindi sarebbe bene usare le variabili globali se copio il valore in una variabile locale (con un mutex) all'inizio della funzione, modifico la variabile locale e quindi copio il valore nella variabile globale alla fine di la funzione? (... chiese retoricamente.)
RM,

1
@RM Ha menzionato due punti. Ciò che hai eliminato potrebbe riguardare il primo (stato del programma imprevedibile), ma non risolve il secondo (la leggibilità del tuo codice). In effetti, potrebbe peggiorare la leggibilità del programma: P.
riwalk

1
@RM La tua funzione funzionerà in modo coerente, sì. Ma allora ti chiederesti se qualcos'altro avesse modificato la variabile globale nel frattempo, e quella modifica era più importante di ciò che stai scrivendo. Anche i database possono avere lo stesso problema, ovviamente.
Graham,

45

Vorrei offrire alcune osservazioni:

Sì, un database è stato globale.

In realtà, è uno stato super-globale, come hai sottolineato. È universale! Il suo ambito implica qualcosa o chiunque si connetta al database. E sospetto che molte persone con anni di esperienza possano raccontarvi storie horror su come "cose ​​strane" nei dati abbiano portato a "comportamenti imprevisti" in una o più applicazioni pertinenti ...

Una delle potenziali conseguenze dell'uso di una variabile globale è che due "moduli" distinti useranno quella variabile per i propri scopi distinti. E fino a questo punto, una tabella di database non è diversa. Può cadere vittima dello stesso problema.

Hmm ... Ecco la cosa:

Se un modulo non funziona in modo estrinseco in qualche modo, non fa nulla.

Un modulo utile può essere dato i dati o può trovare esso. E può restituire dati o può modificare lo stato. Ma se in qualche modo non interagisce con il mondo esterno, potrebbe anche non fare nulla.

Ora, la nostra preferenza è quella di ricevere dati e restituire dati. La maggior parte dei moduli sono semplicemente più facili da scrivere se possono essere scritti con totale disprezzo per ciò che il mondo esterno sta facendo. Ma alla fine, qualcosa deve trovare i dati e modificare quello stato globale esterno.

Inoltre, nelle applicazioni del mondo reale, i dati esistono in modo che possano essere letti e aggiornati da varie operazioni. Alcuni problemi sono evitati da blocchi e transazioni. Ma, in linea di principio , impedire che queste operazioni siano in conflitto tra loro , alla fine della giornata, implica semplicemente un'attenta riflessione. (E fare errori ...)

Inoltre, generalmente non stiamo lavorando direttamente con lo stato globale.

A meno che l'applicazione non risieda nel livello dati (in SQL o altro), gli oggetti con cui lavorano i nostri moduli sono in realtà copie dello stato globale condiviso. Possiamo fare quello che vogliamo senza alcun impatto sullo stato reale e condiviso.

E, nei casi in cui abbiamo bisogno di mutare quello stato globale, supponendo che i dati che ci sono stati dati non siano cambiati, possiamo generalmente eseguire lo stesso tipo di blocco che vorremmo sui nostri globali locali.

E infine, di solito facciamo cose diverse con i database di quanto non potremmo fare con i globuli cattivi.

Un globale cattivo, rotto si presenta così:

Int32 counter = 0;

public someMethod() {
  for (counter = 0; counter < whatever; counter++) {
    // do other stuff.
  }
}

public otherMethod() {
  for (counter = 100; counter < whatever; counter--) {
    // do other stuff.
  }
}

Semplicemente non usiamo database per cose in-process / operative del genere. E potrebbe essere la natura lenta del database e la relativa convenienza di una semplice variabile che ci scoraggia: la nostra interazione lenta e goffa con i database semplicemente li rende cattivi candidati per molti degli errori che abbiamo storicamente commesso con le variabili.


3
Il modo per garantire (dal momento che non possiamo supporre) "che i dati che ci sono stati dati non è cambiato" in un database sarebbe una transazione.
l0b0,

Sì ... ciò avrebbe dovuto essere implicato con "lo stesso tipo di blocco".
svidgen,

Ma può essere difficile pensare attentamente alla fine della giornata.

Sì, i database sono davvero lo stato globale - motivo per cui è così allettante condividere dati usando qualcosa come git o ipfs.
William Payne,

21

Non sono d'accordo con l'affermazione fondamentale secondo cui:

Quando il tuo programma funziona con i dati di un database, non ti importa se un altro codice nel tuo sistema lo sta cambiando, o anche se un programma completamente diverso lo sta cambiando, del resto.

Il mio pensiero iniziale era "Wow. Solo Wow". Si impiegano così tanto tempo e sforzi nel cercare di evitare esattamente questo - e capire quali compromessi e compromessi funzionano per ogni applicazione. Ignorarlo è una ricetta per il disastro.

Ma diasgo anche a livello architettonico. Una variabile globale non è solo stato globale. È lo stato globale che è accessibile da qualsiasi luogo in modo trasparente. Al contrario, per utilizzare un database è necessario disporre di un handle per esso (a meno che non si memorizzi piuttosto che gestire in una variabile globale ....)

Ad esempio l'uso di una variabile globale potrebbe apparire così

int looks_ok_but_isnt() {
  return global_int++;
}

int somewhere_else() {
  ...
  int v = looks_ok_but_isnt();
  ...
}

Ma fare la stessa cosa con un database dovrebbe essere più esplicito su ciò che sta facendo

int looks_like_its_using_a_database( MyDB * db ) {
   return db->get_and_increment("v");
}

int somewhere_else( MyBD * db ) { 
   ...
   v = looks_like_its_using_a_database(db);
   ...
}

Quello del database è ovviamente confuso con un database. Se non si desidera utilizzare un database, è possibile utilizzare lo stato esplicito e sembra quasi uguale al caso del database.

int looks_like_it_uses_explicit_state( MyState * state ) {
   return state->v++;
}


int somewhere_else( MyState * state ) { 
   ...
   v = looks_like_it_uses_explicit_state(state);
   ...
}

Quindi direi che usare un database è molto più simile all'uso dello stato esplicito che all'utilizzo di variabili globali.


2
Sì, ho pensato che fosse interessante quando l'OP ha detto: " Non ti importa quali siano i dati; questo è l'intero punto " - se non ci interessa, perché conservarli? Ecco un pensiero: diciamo solo smettere di usare le variabili e dati a tutti . Ciò dovrebbe rendere le cose molto più semplici. "Ferma il mondo, voglio scendere!"

1
+1 Discussioni o app diverse che scrivono e leggono dallo stesso database sono una potenziale fonte di un gran numero di problemi noti, motivo per cui dovrebbe sempre esserci una strategia per gestirlo, a livello di database o di app, oppure entrambi. Quindi NON è assolutamente vero che tu (lo sviluppatore dell'app) non ti interessi di chi altri sta leggendo o scrivendo dal database.
Andres F.

1
+1 In una nota a margine, questa risposta spiega praticamente cosa odio di più dell'iniezione di dipendenza. Nasconde questo tipo di dipendenze.
jpmc26,

@ jpmc26 Potrei contrassegnare le parole, ma il precedente non è un buon esempio di come l'iniezione di dipendenza (al contrario della ricerca globale) aiuti a rendere esplicite le dipendenze? Mi sembra che tu abbia piuttosto messo in discussione alcune API, come forse la magia delle annotazioni usata da JAX-RS e Spring.
Emil Lundberg,

2
@EmilLundberg No, il problema è quando hai una gerarchia. L'iniezione di dipendenza nasconde le dipendenze dei livelli inferiori dal codice nei livelli superiori, rendendo difficile tenere traccia delle cose che interagiscono. Ad esempio, se MakeNewThingdipende MakeNewThingInDbe utilizzato dalla mia classe di controller MakeNewThing, dal codice nel mio controller non è chiaro che sto modificando il database. Quindi cosa succede se uso un'altra classe che effettivamente trasferisce la mia transazione corrente al DB? DI rende molto difficile controllare l'ambito di un oggetto.
jpmc26

18

Il fatto che l'unica ragione per cui non ci si può fidare delle variabili globali poiché lo stato può essere cambiato da qualche altra parte, di per sé, non è una ragione sufficiente per non usarle, è d'accordo (è comunque una buona ragione!). È probabile che la risposta stia principalmente descrivendo l'utilizzo in cui limitare l'accesso di una variabile alle sole aree di codice che interesserebbe avrebbe più senso.

I database sono una questione diversa, tuttavia, poiché sono progettati allo scopo di accedervi "a livello globale" per così dire.

Per esempio:

  • I database in genere hanno una convalida del tipo e della struttura integrata che va oltre la lingua che li accede
  • I database si aggiornano quasi all'unanimità in base alle transazioni, il che impedisce stati incoerenti, dove non esiste alcuna garanzia su come sarà lo stato finale in un oggetto globale (a meno che non sia nascosto dietro un singleton)
  • La struttura del database è almeno implicitamente documentata in base alla struttura della tabella o dell'oggetto, più che dall'applicazione che lo utilizza

Ma soprattutto, i database hanno uno scopo diverso rispetto a una variabile globale. I database servono per l'archiviazione e la ricerca di grandi quantità di dati organizzati, in cui le variabili globali servono nicchie specifiche (se giustificabili).


1
Huh. Mi hai battuto mentre stavo scrivendo una risposta quasi identica. :)
Jules,

@Jules la tua risposta fornisce ulteriori dettagli dal lato dell'applicazione; tienilo.
Jeffrey Sweeney,

Tuttavia, a meno che non si dipenda interamente da procedure memorizzate per l'accesso ai dati, tutta quella struttura non riuscirà comunque a imporre che le tabelle vengano utilizzate come previsto. O che le operazioni vengano eseguite nell'ordine appropriato. O che i blocchi (transazioni) vengano creati in base alle esigenze.
svidgen,

Salve, i punti 1 e 3 sono ancora applicabili se si utilizza un linguaggio di tipo statico come Java?
Jesvin Jose,

@aitchnyu Non necessariamente. Il punto è che i database sono creati allo scopo di condividere in modo affidabile i dati, dove le variabili globali in genere non lo sono. Un oggetto che implementa un'interfaccia auto-documentante in un linguaggio rigoroso ha uno scopo diverso rispetto a un database NoSQL tipizzato.
Jeffrey Sweeney,

10

Ma quando lo guardo, non posso fare a meno di pensare che sia una spiegazione molto debole, perché in che modo differisce dal lavorare con i dati archiviati in un database?

O diverso dal lavorare con un dispositivo interattivo, con un file, con memoria condivisa, ecc. Un programma che fa esattamente la stessa cosa ogni volta che viene eseguito è un programma molto noioso e piuttosto inutile. Quindi sì, è un argomento debole.

Per me, la differenza che fa la differenza rispetto alle variabili globali è che formano linee di comunicazione nascoste e non protette. Leggere da una tastiera è molto ovvio e protetto. Devo effettuare una determinata chiamata di funzione e non riesco ad accedere al driver della tastiera. Lo stesso vale per l'accesso ai file, la memoria condivisa e, ad esempio, i database. È ovvio per il lettore del codice che questa funzione legge dalla tastiera, che la funzione accede a un file, alcune altre funzioni accedono alla memoria condivisa (e ci dovrebbero essere protezioni attorno a ciò), eppure alcune altre funzioni accedono a un database.

Con le variabili globali, d'altra parte, non è affatto ovvio. L'API dice di chiamare foo(this_argument, that_argument). Non c'è nulla nella sequenza chiamante che dice che la variabile globale g_DangerWillRobinsondovrebbe essere impostata su un valore ma prima di chiamare foo(o esaminata dopo la chiamata foo).


Google ha vietato l'uso di argomenti di riferimento non costanti in C ++ principalmente perché non è ovvio per il lettore del codice che foo(x)cambierà xperché fooutilizza un riferimento non costante come argomento. (Confronta con C #, che impone che sia la definizione della funzione sia il sito della chiamata debbano qualificare un parametro di riferimento con la refparola chiave.) Anche se non sono d'accordo con lo standard di Google su questo, capisco il loro punto.

Il codice viene scritto una volta e modificato alcune volte, ma se è del tutto buono, viene letto molte, molte volte. Le linee nascoste di comunicazione sono un karma molto brutto. Il riferimento non const del C ++ rappresenta una linea di comunicazione nascosta minore. Una buona API o un buon IDE mi mostreranno che "Oh! Questa è chiamata per riferimento". Le variabili globali sono un'enorme linea nascosta di comunicazione.


La tua risposta ha più senso.
Billal Begueradj,

8

Penso che la spiegazione citata semplifichi eccessivamente la questione al punto in cui il ragionamento diventa ridicolo. Naturalmente, lo stato di un database esterno contribuisce allo stato globale. La domanda importante è comeil tuo programma dipende dallo stato (mutabile) globale. Se una funzione di libreria per dividere le stringhe su uno spazio bianco dipendesse dai risultati intermedi memorizzati in un database, mi opporterei a questo progetto almeno tanto quanto obietterei a un array di caratteri globale utilizzato per lo stesso scopo. D'altra parte, se decidi che la tua applicazione non ha bisogno di un DBMS completo per archiviare i dati aziendali a questo punto e una struttura globale di valori-chiave in memoria lo farà, questo non è necessariamente un segno di cattiva progettazione. Ciò che è importante è che, indipendentemente dalla soluzione scelta per archiviare i dati, questa scelta è isolata in una porzione molto piccola del sistema in modo che la maggior parte dei componenti possa essere agnostica rispetto alla soluzione scelta per l'implementazione e testata in unità isolatamente e distribuita la soluzione può essere modificata in un secondo momento con un piccolo sforzo.


8

Come ingegnere del software che lavora prevalentemente con firmware incorporato, utilizzo quasi sempre variabili globali per qualsiasi cosa tra i moduli. In effetti, è la migliore pratica per embedded. Sono assegnati staticamente, quindi non c'è rischio di saltare l'heap / stack e non c'è tempo aggiuntivo per l'allocazione / pulizia dello stack all'entrata / uscita della funzione.

Il rovescio della medaglia è che noi facciamo considerare come vengono utilizzate le variabili, e un sacco di che si riduce allo stesso tipo di pensiero che va nel database-dispute. Qualsiasi lettura / scrittura asincrona delle variabili DEVE essere atomica. Se più di un posto è in grado di scrivere una variabile, è necessario pensare che scriveranno sempre dati validi, quindi la scrittura precedente non viene sostituita arbitrariamente (o quella sostituzione arbitraria è una cosa sicura da fare). Se la stessa variabile viene letta più di una volta, è necessario prendere in considerazione cosa succede se la variabile cambia valore tra le letture o una copia della variabile deve essere presa all'inizio in modo che l'elaborazione venga eseguita utilizzando un valore coerente, anche se quel valore diventa stantio durante l'elaborazione.

(Per l'ultimo, il mio primissimo giorno di un contratto di lavoro su un sistema di contromisure aeronautiche, così altamente legato alla sicurezza, il team del software stava esaminando una segnalazione di bug che stavano cercando di capire da circa una settimana. Avevo avuto appena il tempo di scaricare gli strumenti di sviluppo e una copia del codice. Ho chiesto "non è possibile aggiornare quella variabile tra letture e causarla?", Ma non ho davvero ottenuto una risposta. Ehi, che cosa fa il nuovo ragazzo lo sai, dopo tutto? Quindi, mentre ne stavano ancora discutendo, ho aggiunto un codice di protezione per leggere la variabile atomicamente, ho fatto una build locale e in pratica ho detto "hey ragazzi, provate questo". Modo per dimostrare che valgo il mio tasso contrattuale :)

Quindi le variabili globali non sono inequivocabilmente cattive, ma ti lasciano aperto a una vasta gamma di problemi se non ci pensi attentamente.


7

A seconda dell'aspetto che stai valutando, le variabili globali e l'accesso al database possono essere mondi a parte, ma finché li giudichiamo come dipendenze, sono le stesse.

Consideriamo che la definizione di programmazione funzionale di una funzione pura afferma che deve dipendere esclusivamente dai parametri che assume come input, producendo un output deterministico. Cioè, dato lo stesso insieme di argomenti due volte, deve produrre lo stesso risultato.

Quando una funzione dipende da una variabile globale, non può più essere considerata pura, poiché, per lo stesso set o argomenti, può produrre output diversi perché il valore della variabile globale potrebbe essere cambiato tra le chiamate.

Tuttavia, la funzione può ancora essere considerata deterministica se consideriamo la variabile globale tanto una parte dell'interfaccia della funzione quanto gli altri suoi argomenti, quindi non è il problema. Il problema è solo che questo è nascosto fino al momento in cui siamo sorpresi da comportamenti imprevisti da funzioni apparentemente ovvie, quindi vai a leggere le loro implementazioni per scoprire le dipendenze nascoste .

Questa parte, il momento in cui una variabile globale diventa una dipendenza nascosta è ciò che è considerato malvagio da noi programmatori. Rende il codice più difficile da ragionare, difficile prevedere come si comporterà, difficile da riutilizzare, difficile da testare e soprattutto, aumenta il tempo di debug e di correzione quando si verifica un problema.

La stessa cosa accade quando nascondiamo la dipendenza dal database. Possiamo avere funzioni o oggetti che fanno chiamate dirette a query e comandi del database, nascondendo queste dipendenze e causandoci lo stesso identico problema che causano le variabili globali; oppure possiamo renderli espliciti, che, a quanto pare, è considerato una best practice che va sotto molti nomi, come modello di repository, archivio dati, gateway, ecc.

PS: ci sono altri aspetti che sono importanti per questo confronto, come ad esempio se è coinvolta la concorrenza, ma questo punto è coperto da altre risposte qui.


Mi piace che tu l'abbia preso dal punto di vista delle dipendenze.
cbojar,

6

Va bene, partiamo dal punto storico.

Siamo in una vecchia applicazione, scritta nel tipico mix di assembly e C. Non ci sono funzioni, solo procedure . Quando si desidera passare un argomento o restituire un valore da una procedura, si utilizza una variabile globale. Inutile dire che è abbastanza difficile tenere traccia di, e in generale, ogni procedura può fare quello che vuole con ogni variabile globale. Non sorprende che le persone si siano rivolte al passaggio degli argomenti e alla restituzione dei valori in modo diverso non appena fosse possibile (a meno che non fosse fondamentale per le prestazioni non farlo - ad esempio, guardare il codice sorgente di Build Engine (Duke 3D)). L'odio per le variabili globali è nato qui: avevi ben poca idea di quale parte dello stato globale ogni procedura potesse leggere e cambiare e non potevi davvero nidificare le chiamate di procedura in modo sicuro.

Questo significa che l'odio variabile globale è un ricordo del passato? Non proprio.

In primo luogo, devo dire che ho visto lo stesso approccio esatto per passare argomenti nel progetto a cui sto lavorando in questo momento. Per passare due istanze del tipo di riferimento in C #, in un progetto che ha circa 10 anni. Non c'è letteralmente una buona ragione per farlo in questo modo, e molto probabilmente è nato dal culto del carico o da un completo fraintendimento di come funziona C #.

Il punto più grande è che aggiungendo variabili globali, stai espandendo l'ambito di ogni singolo pezzo di codice che ha accesso a quella variabile globale. Ricordi tutti quei consigli come "mantieni i tuoi metodi brevi"? Se hai 600 variabili globali (di nuovo, esempio nel mondo reale: /), tutti gli ambiti del tuo metodo vengono espansi implicitamente da quelle 600 variabili globali e non esiste un modo semplice per tenere traccia di chi ha accesso a cosa.

Se fatto in modo sbagliato (nel solito modo :)), le variabili globali potrebbero avere un accoppiamento tra loro. Ma non hai idea di come siano accoppiati e non esiste alcun meccanismo per garantire che lo stato globale sia sempre coerente. Anche se introduci sezioni critiche per cercare di mantenere le cose coerenti, scoprirai che si confronta male con un database ACID adeguato:

  • Non è possibile ripristinare un aggiornamento parziale, a meno che non si conservino i vecchi valori prima della "transazione". Inutile dire che, a questo punto, passare un valore come argomento è già una vittoria :)
  • Tutti gli utenti che accedono allo stesso stato devono aderire allo stesso processo di sincronizzazione. Ma non c'è modo di imporlo: se ti dimentichi di impostare la sezione critica, sei fregato.
  • Anche se si sincronizza correttamente tutti gli accessi, potrebbero esserci chiamate nidificate che accedono allo stato parzialmente modificato. Ciò significa che si esegue un deadlock (se le sezioni critiche non sono reëntrant) o si tratta di dati incoerenti (se sono reëntrant).

È possibile risolvere questi problemi? Non proprio. Hai bisogno di incapsulamento per gestire questo, o disciplina molto severa. È difficile fare le cose bene, e in genere questa non è un'ottima ricetta per il successo nello sviluppo del software :)

Un ambito più piccolo tende a rendere il codice più facile da ragionare. Le variabili globali rendono anche i pezzi di codice più semplici includono enormi aree di portata.

Naturalmente, ciò non significa che l'ambito globale sia malvagio. Non dovrebbe essere la prima soluzione per te: è un tipico esempio di "semplice da implementare, difficile da mantenere".


Sembra molto simile al mondo fisico: è molto difficile ripristinare le cose.

Questa è una buona risposta, ma all'inizio potrebbe contenere una tesi (TL; sezione DR).
jpmc26

6

Una variabile globale è uno strumento, può essere usata per il bene e per il male.

Un database è uno strumento, può essere usato per il bene e per il male.

Come osserva il poster originale, la differenza non è poi così grande.

Gli studenti inesperti spesso pensano che i bug siano qualcosa che accade agli altri. Gli insegnanti usano "Le variabili globali sono cattive" come motivo semplificato per penalizzare la cattiva progettazione. Gli studenti generalmente non capiscono che solo perché il loro programma da 100 linee è privo di bug non significa che gli stessi metodi possano essere usati per i programmi da 10000 linee.

Quando lavori con i database, non puoi semplicemente vietare lo stato globale poiché è questo il programma. Invece ottieni maggiori linee guida dettagliate come ACID e Normal Forms e così via.

Se le persone usassero l'approccio ACID alle variabili globali, non sarebbero così male.

D'altra parte, se si progettano male i database, possono essere incubi.


3
Richiesta di studenti tipica su StackOverflow: aiutami! Il mio codice è perfetto, ma non funziona bene!
David Hammen,

"Approccio ACID alle variabili globali" - vedi riferimenti in Clojure.
Charles Duffy,

@DavidHammen e pensi che i professionisti abbiano un cervello diverso dagli studenti?
Billal Begueradj,

@BillalBEGUERADJ - Questa è la differenza tra professionisti e studenti. Sappiamo che nonostante anni di esperienza e nonostante i migliori sforzi di revisione del codice, test, ecc., Il nostro codice non è perfetto.
David Hammen,


5

Per me, il male principale è che Globals non ha protezione contro i problemi di concorrenza. Puoi aggiungere meccanismi per gestire tali problemi con Globals, ma scoprirai che più problemi di concorrenza risolvi, più i tuoi Globals iniziano a imitare un database. Il male secondario non è un contratto sull'uso.


3
Ad esempio, errnoin C.
David Hammen,

1
Questo spiega esattamente perché i globi e i database non sono gli stessi. Potrebbero esserci altre differenze, ma il tuo post specifico distrugge completamente il concetto. Se hai dato un rapido esempio di codice, sono sicuro che otterrai molti voti. ad es. MyFunc () {x = globalVar * 5; // .... Qualche altra elaborazione; y = globalVar * 34; // Ooops, alcuni altri thread potrebbero aver cambiato globalVar durante alcuni altri processi e xey stanno usando valori diversi per globalVar nei loro calcoli, che quasi sicuramente non darebbero risultati desiderabili.
Dunk,

5

Alcune delle altre risposte provano a spiegare perché usare un database sia buono. Si sbagliano! Un database è uno stato globale e come tale è altrettanto malvagio di un singolo o di una variabile globale. È assolutamente sbagliato usare un database quando invece puoi semplicemente usare una mappa locale o un array!

Le variabili globali consentono l'accesso globale, che comporta rischi di abuso. Le variabili globali hanno anche degli aspetti positivi. Le variabili globali sono generalmente considerate qualcosa che dovresti evitare, non qualcosa che non dovresti mai usare. Se riesci facilmente ad evitarli, dovresti evitarli. Ma se i benefici superano gli svantaggi, ovviamente dovresti usarli! *

La stessa identica cosa ** si applica ai database, che sono stati globali, proprio come le variabili globali. Se riesci ad accontentarti senza accedere a un database e la logica risultante fa tutto ciò di cui hai bisogno ed è ugualmente complessa, l'utilizzo di un database aumenta il rischio per il tuo progetto, senza alcun vantaggio corrispondente.

Nella vita reale, molte applicazioni richiedono uno stato globale in base alla progettazione, a volte anche uno stato globale persistente - ecco perché abbiamo file, database, ecc.


* L'eccezione qui sono gli studenti. Ha senso vietare agli studenti di utilizzare le variabili globali in modo che debbano imparare quali sono le alternative.

** Alcune risposte affermano erroneamente che i database sono in qualche modo meglio protetti rispetto ad altre forme di stato globale (la domanda riguarda esplicitamente lo stato globale , non solo le variabili globali). Questi sono bollocks. La protezione primaria offerta nello scenario del database è per convenzione, che è esattamente la stessa per qualsiasi altro stato globale. La maggior parte delle lingue offre anche molta protezione aggiuntiva per lo stato globale, sotto forma di constclassi che semplicemente non consentono di cambiare il loro stato dopo che è stato impostato nel costruttore, o getter e setter che possono tenere conto delle informazioni sui thread o dello stato del programma.


2

In un certo senso, la distinzione tra variabili globali e un database è simile alla distinzione tra membri privati ​​e pubblici di un oggetto (supponendo che qualcuno usi ancora campi pubblici). Se si considera l'intero programma come un oggetto, i globali sono le variabili private e il database è i campi pubblici.

La distinzione chiave qui è una delle responsabilità assunte.

Quando si scrive un oggetto, si presume che chiunque mantenga i metodi membro assicurerà che i campi privati ​​rimangano ben educati. Ma hai già rinunciato a qualsiasi ipotesi sullo stato dei campi pubblici e li tratti con particolare attenzione.

La stessa ipotesi si applica a un livello più ampio al database globale v / s. Inoltre, il linguaggio di programmazione / ecosistema garantisce restrizioni di accesso su v / s pubblico nello stesso modo in cui le applica sul database v / s globale (memoria non condivisa).

Quando il multithreading entra in gioco, il concetto di database privato v / s pubblico v / s globale v / s è semplicemente distinzioni lungo uno spettro.

static int global; // within process memory space
static int dbvar; // mirrors/caches data outside process memory space

class Cls {
    public: static int class_public; // essentially the same as global
    private: static int class_private; // but public to all methods in class

    private: static void method() {
        static int method_private; // but public to all scopes in method
        // ...
        {
            static int scope1_private; // mutex guarded
            int the_only_truly_private_data;
        }
        // ...
        {
            static int scope2_private; // mutex guarded
        }
    }
}

1

Un database può essere uno stato globale, ma non deve essere sempre. Non sono d'accordo con il presupposto che tu non abbia il controllo. Un modo per gestirlo è il blocco e la sicurezza. Questo può essere fatto sul record, sulla tabella o sull'intero database. Un altro approccio consiste nell'avere una sorta di campo versione che impedirebbe la modifica di un record se i dati sono obsoleti.

Come una variabile globale, i valori in un database possono essere modificati una volta sbloccati, ma ci sono molti modi per controllare l'accesso (non dare a tutti gli sviluppatori la password all'account a cui è consentito modificare i dati). Se hai una variabile con accesso limitato, non è molto globale.


0

Ci sono diverse differenze:

  • Un valore di database può essere modificato al volo. D'altro canto, il valore di un globale impostato nel codice non può essere modificato se non si ridistribuisce l'applicazione e si modifica il codice. In realtà, questo è intenzionale. Un database è per valori che potrebbero cambiare nel tempo, ma le variabili globali dovrebbero essere solo per cose che non cambieranno mai e quando non contengono dati reali.

  • Un valore di database (riga, colonna) ha un contesto e una mappatura relazionale nel database. Questa relazione può essere facilmente estratta e analizzata usando strumenti come Jailer (per esempio). Una variabile globale, d'altra parte, è leggermente diversa. Puoi trovare tutti gli usi, ma sarebbe impossibile per me dirmi tutti i modi in cui la variabile interagisce con il resto del tuo mondo.

  • Le variabili globali sono più veloci . Per ottenere qualcosa da un database è necessario effettuare una connessione al database, eseguire una selezione per me e quindi chiudere la connessione al database. Tutte le conversioni di tipo che potresti aver bisogno ti vengono in cima. Confrontalo con un accesso globale nel tuo codice.

Questi sono gli unici a cui riesco a pensare adesso, ma sono sicuro che ce ne sono altri. In poche parole, sono due cose diverse e dovrebbero essere utilizzate per obiettivi diversi .


0

Ovviamente i globi non sono sempre inappropriati. Esistono perché hanno un uso legittimo. Il problema principale con i globi, e la fonte primaria dell'ammonizione per evitarli, è che il codice che utilizza un globale è associato a quell'unico e unico globale.

Ad esempio, si consideri un server HTTP che memorizza il nome del server.

Se si memorizza il nome del server in un globale, il processo non può eseguire contemporaneamente la logica per due nomi di server diversi. Forse il design originale non prevedeva mai l'esecuzione di più di un'istanza del server alla volta, ma se in seguito decidi di volerlo fare, semplicemente non puoi se il nome del server è in un globale.

Al contrario, se il nome del server si trova in un database, non ci sono problemi. Puoi semplicemente creare un'istanza di quel database per ogni istanza del server HTTP. Poiché ogni istanza del server ha la propria istanza del database, può avere il proprio nome del server.

Quindi, l'obiezione principale ai globali, può esserci un solo valore per tutto il codice che accede a quel globale, non si applica alle voci del database. Lo stesso codice può accedere facilmente a distinte istanze di database con valori diversi per una voce specifica.


0

Penso che questa sia una domanda interessante, ma è un po 'difficile rispondere perché ci sono due questioni principali che si confondono con il termine "stato globale". Il primo è il concetto di "accoppiamento globale". La prova di ciò è che l'alternativa data per lo stato globale è l'iniezione di dipendenza. Il fatto è che DI non elimina necessariamente lo stato globale. Cioè, è assolutamente possibile e comune iniettare dipendenze dallo stato globale. Quello che fa DI è rimuovere l'accoppiamento che viene fornito con le variabili globali e il modello Singleton comunemente usato. Oltre a un design leggermente meno ovvio, c'è molto poco svantaggio nell'eliminare questo tipo di accoppiamento e i vantaggi dell'eliminazione dell'accoppiamento aumentano in modo esponenziale con il numero di dipendenze su quei globi.

L'altro aspetto di questo è lo stato condiviso. Non sono sicuro che ci sia una chiara distinzione tra stato condiviso a livello globale e stato condiviso in generale, ma i costi e i benefici sono molto più sfumati. In poche parole, ci sono innumerevoli sistemi software che richiedono lo stato condiviso per essere utili. Il bitcoin, ad esempio, è un modo molto intelligente di condividere lo stato a livello globale (letteralmente) in modo decentralizzato. Condividere correttamente lo stato mutevole senza creare enormi colli di bottiglia è difficile ma utile. Quindi, se non è necessario farlo, è possibile semplificare l'applicazione riducendo al minimo lo stato mutabile condiviso.

Quindi la domanda su come i database differiscono dai globali è anche biforcata attraverso questi due aspetti. Introducono l'accoppiamento? Sì, possono, ma dipende molto dalla progettazione dell'applicazione e dalla progettazione del database. Ci sono troppi fattori per avere un'unica risposta al fatto che i database introducano un accoppiamento globale senza dettagli del progetto. Sul fatto che introducano la condivisione dello stato, beh, questo è il punto principale di un database. La domanda è se lo fanno bene. Ancora una volta, penso che questo sia troppo complicato per rispondere senza molte altre informazioni come le alternative e molti altri compromessi.


0

Ci penserei in modo leggermente diverso: il comportamento "variabile globale" è un prezzo pagato dagli amministratori di database (DBA) perché è un male necessario fare il loro lavoro.

Il problema con le variabili globali, come molti altri hanno sottolineato, non è arbitrario. Il problema è che il loro uso rende il comportamento del programma sempre meno prevedibile perché diventa più difficile determinare chi sta usando la variabile e in che modo. Questo è un grosso problema per il software moderno, poiché al software moderno viene generalmente chiesto di fare molte cose flessibili. Può fare miliardi o addirittura trilioni di complesse manipolazioni statali nel corso di una corsa. La capacità di dimostrare affermazioni vere su ciò che quel software farà in quei miliardi o trilioni di operazioni è estremamente preziosa.

Nel caso del software moderno, tutte le nostre lingue forniscono strumenti per aiutare in questo, come l'incapsulamento. La scelta di non usarla è inutile, il che porta alla mentalità "i globuli sono cattivi". In molte regioni del campo dello sviluppo software, le uniche persone che le usano sono persone che non sanno come programmare meglio. Ciò significa che non solo sono direttamente problemi, ma suggeriscono indirettamente che lo sviluppatore non sapeva cosa stavano facendo. In altre regioni, scoprirai che i globi sono del tutto normali (il software incorporato, in particolare, ama i globi, in parte perché funzionano bene con gli ISR). Tuttavia, tra i molti sviluppatori di software là fuori, sono la voce di minoranza, quindi l'unica voce che senti sono "i globi sono cattivi".

Lo sviluppo del database è una di quelle situazioni vocali di minoranza. Gli strumenti necessari per eseguire il lavoro DBA sono molto potenti e la loro teoria non è radicata nell'incapsulamento. Per eliminare ogni singolo jiffy di prestazioni dai loro database, hanno bisogno di un accesso illimitato a tutto, simile ai globi. Impugna uno dei loro mostruosi database da 100 milioni di righe (o più!) E capirai perché non permettono ai loro motori DB di trattenere i pugni.

Pagano un prezzo per quello, un caro prezzo. I DBA sono costretti a essere quasi patologici con la loro attenzione ai dettagli, perché i loro strumenti non li proteggono. Il migliore che hanno in termini di protezione è ACID o forse chiavi esterne. Quelli che non sono patologici si trovano con un casino totale di tavoli che è completamente inutilizzabile o addirittura corrotto.

Non è raro avere pacchetti software da 100k line. In teoria, qualsiasi linea del software può influenzare qualsiasi globale in qualsiasi momento. Nei DBA non trovi mai 100.000 query diverse che possono modificare il database. Sarebbe irragionevole mantenere con l'attenzione ai dettagli necessari per proteggerti da te stesso. Se un DBA ha qualcosa di così grande, incapsulerà intenzionalmente il proprio database utilizzando gli accessor, evitando i problemi di "global like" e quindi svolgendo il maggior lavoro possibile attraverso quel meccanismo "più sicuro". Pertanto, quando arriva il push, anche le persone del database evitano i globi. Vengono semplicemente con un sacco di pericolo e ci sono alternative altrettanto forti, ma non altrettanto pericolose.

Preferiresti camminare su vetri rotti o su marciapiedi ben spazzati, se tutte le altre cose sono uguali? Sì, puoi camminare su vetri rotti. Sì, alcune persone si guadagnano da vivere anche facendo. Tuttavia, lascia che spazzino il marciapiede e proseguano!


0

Penso che la premessa sia falsa. Non c'è motivo per cui un database debba essere "stato globale" piuttosto che un oggetto di contesto (molto grande). Se stai vincolando a un determinato database che il tuo codice utilizza tramite variabili globali o parametri fissi di connessione al database globale, non è diverso e non meno malvagio di qualsiasi altro stato globale. D'altra parte, se si passa correttamente un oggetto di contesto per la connessione al database, è solo uno stato contestuale (e ampiamente utilizzato), non globale.

Misurare la differenza è semplice: potresti eseguire due istanze della logica del tuo programma, ognuna usando il proprio database, in un singolo programma / processo senza apportare modifiche invasive al codice? In tal caso, il database non è realmente "stato globale".


-2

I globali non sono cattivi; sono semplicemente uno strumento. IL MISUSO di globuli è problematico, così come l'uso improprio di qualsiasi altra funzione di programmazione.

La mia raccomandazione generale è che i globi dovrebbero essere usati solo in situazioni ben comprese e ponderate, dove altre soluzioni sono meno ottimali. Ancora più importante, vuoi assicurarti di aver ben documentato dove potrebbe essere modificato quel valore globale e, se stai eseguendo il multithreading, di assicurarti che i globali globali e tutti i co-dipendenti abbiano accesso in modo transazionale.


Ad alcuni dei downvoter dispiacerebbe spiegare i tuoi downvotes? Sembra maleducato sottovalutare senza una spiegazione.
Byron Jones,

-2

Modello di sola lettura e si presuppone che i dati non siano aggiornati al momento della stampa. La coda scrive o gestisce i conflitti in un altro modo. Benvenuto all'inferno diavolo, stai usando il db globale.

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.