Comment les globals sont-ils différents d'une base de données?

250

Je viens de croiser cette vieille question en demandant ce qui est si pervers à propos de l'état global, et la réponse acceptée avec le plus grand nombre de votes affirme que vous ne pouvez faire confiance à aucun code fonctionnant avec des variables globales, car un autre code situé ailleurs pourrait en modifier le contenu. valeur et vous ne savez pas quel sera le comportement de votre code car les données sont différentes! Mais quand je regarde cela, je ne peux pas m'empêcher de penser que c'est une explication vraiment faible, car en quoi est-ce différent de travailler avec des données stockées dans une base de données?

Lorsque votre programme utilise des données d'une base de données, vous ne vous souciez pas de savoir si un autre code de votre système le modifie, ou même si un programme complètement différent le modifie, d'ailleurs. Vous ne vous souciez pas de ce que les données sont; c'est le point entier. Tout ce qui compte, c'est que votre code traite correctement les données qu'il rencontre. (Évidemment, je passe sous silence la question souvent épineuse de la mise en cache ici, mais ignorons-le pour le moment.)

Mais si les données que vous utilisez proviennent d'une source externe sur laquelle votre code n'a aucun contrôle, tel qu'une base de données (ou une entrée utilisateur, ou une prise réseau, ou un fichier, etc.) et que rien ne se passe mal. Avec cela, comment les données globales contenues dans le code lui-même - sur lesquelles votre programme a un degré de contrôle beaucoup plus important - sont-elles un inconvénient quand elles sont évidemment bien moins mauvaises que des données parfaitement normales que personne ne considère comme un problème?

Maçon Wheeler
la source
117
Il est agréable de voir des membres vétérans défier un peu les dogmes ...
svidgen
10
Dans une application, vous fournissez généralement un moyen d'accéder à la base de données. Ce moyen est transmis aux fonctions qui souhaitent accéder à la base de données. Vous ne faites pas cela avec des variables globales, vous savez simplement qu'elles sont à portée de main. C'est une différence clé ici.
Andy
45
L'état global est comme avoir une base de données unique avec une table unique avec une seule ligne avec un nombre infini de colonnes accessibles simultanément par un nombre arbitraire d'applications.
BevynQ
42
Les bases de données sont aussi pervers.
Stig Hemmer
27
C'est amusant d'inverser l'argument que vous avancez et d'aller dans l'autre sens. Une structure qui a un pointeur sur une autre structure est logiquement juste une clé étrangère dans une ligne d'une table qui se connecte à une autre ligne d'une autre table. Comment fonctionne avec tout code, y compris la marche listes chaînées différente de la manipulation des données dans une base de données? Réponse: ce n'est pas. Question: pourquoi alors manipulons-nous les structures de données en mémoire et les structures de données en base de données en utilisant des outils aussi différents? Réponse: je ne sais vraiment pas! On dirait un accident de l'histoire plutôt qu'un bon design.
Eric Lippert

Réponses:

118

Premièrement, je dirais que la réponse que vous associez pour surestimer ce problème particulier et que le principal inconvénient de l'état global est qu'il introduit le couplage de manière imprévisible, ce qui peut rendre difficile le changement de comportement de votre système à l'avenir.

Mais pour approfondir davantage cette question, il existe des différences entre l’état global d’une application orientée objet typique et l’état conservé dans une base de données. En bref, les plus importants sont:

  • Les systèmes orientés objet permettent de remplacer un objet par une classe d'objet différente, à condition qu'il s'agisse d'un sous-type du type d'origine. Cela permet de modifier le comportement , pas seulement les données .

  • L'état global dans une application ne fournit généralement pas les garanties de cohérence élevées d'une base de données - il n'y a pas de transactions au cours desquelles vous voyez un état cohérent, pas de mises à jour atomiques, etc.

De plus, nous pouvons voir l'état de la base de données comme un mal nécessaire; il est impossible de l'éliminer de nos systèmes. L'état global, cependant, n'est pas nécessaire. Nous pouvons entièrement l'éliminer. Ainsi, même si les problèmes avec une base de données étaient aussi graves , nous pouvons toujours éliminer certains des problèmes potentiels et une solution partielle est préférable à l'absence de solution.

Jules
la source
44
Je pense que le point essentiel de la cohérence est en fait la raison principale: lorsque des variables globales sont utilisées dans le code, il n’est généralement pas possible de savoir quand elles sont réellement initialisées. Les dépendances entre les modules sont profondément cachées à l'intérieur de la séquence d'appels, et des choses simples comme échanger deux appels peuvent produire de très vilains bugs car soudainement, une variable globale n'est plus correctement initialisée lors de sa première utilisation. Du moins, c’est le problème que j’ai avec le code hérité avec lequel je dois travailler et qui fait du refactoring un cauchemar.
cmaster
24
@ DavidHammen J'ai en fait travaillé sur la simulation de l'état du monde pour un jeu en ligne, qui est clairement dans la catégorie d'applications que vous parlez, et même dans ce cas, je n'utiliserais pas (et n'aurais pas) utilisé l'état global pour cela. Même s'il est possible de gagner en efficacité en utilisant l'état global, le problème est que l'état global n'est pas évolutif . Il devient difficile à utiliser une fois que vous passez d'une architecture à un seul thread à une architecture à plusieurs threads. Cela devient inefficace lorsque vous passez à une architecture NUMA. Cela devient impossible lorsque vous passez à une architecture distribuée. Le papier que vous citez date de ...
Jules
24
1993. Ces problèmes étaient alors moins problématiques. Les auteurs travaillaient sur un système à un seul processeur simulant les interactions de 1 000 objets. Dans un système moderne, vous feriez probablement une simulation de ce type sur au moins un système à double cœur, mais il est tout à fait probable qu’il y ait au moins 6 cœurs dans un seul système. Pour des problèmes plus importants encore, vous l’exécuteriez sur un cluster. Pour ce type de changement, vous devez éviter les états globaux car ils ne peuvent pas être partagés efficacement.
Jules
19
Je pense qu'appeler l'état de base de données un "mal nécessaire" est un peu exagéré. Je veux dire, depuis quand l'état est-il devenu mauvais? State est le but d'une base de données. L'état est une information. Sans Etat, tout ce que vous avez sont des opérateurs. A quoi servent les opérateurs sans quelque chose à exploiter? Cet état doit aller quelque part. En fin de compte, la programmation fonctionnelle n’est qu’un moyen de parvenir à une fin. Sans état de mutation, il serait inutile de faire quoi que ce soit. C'est un peu comme un boulanger qui considère le gâteau comme un mal nécessaire - ce n'est pas mal. C'est le but de la chose.
J ...
5
@DavidHammen "Il y a encore un objet qui en sait au moins un peu sur chaque objet du jeu" Ce n'est pas nécessairement vrai. Une technique majeure de la simulation distribuée moderne consiste à tirer parti de la localité et à effectuer des approximations de sorte que les objets distants n'aient pas besoin de tout savoir au loin, mais seulement des données qui leur sont fournies par les propriétaires de ces objets distants.
JAB
75

Premièrement, quels sont les problèmes avec les variables globales, en fonction de la réponse acceptée à la question que vous avez liée?

Très brièvement, cela rend l’état du programme imprévisible.

Les bases de données sont, dans la grande majorité du temps, compatibles ACID. ACID traite spécifiquement des problèmes sous-jacents qui rendraient un magasin de données imprévisible ou peu fiable.

En outre, l'état global nuit à la lisibilité de votre code.

En effet, les variables globales existent dans une étendue très éloignée de leur utilisation, peut-être même dans un fichier différent. Lorsque vous utilisez une base de données, vous utilisez un jeu d'enregistrements ou un objet ORM local par rapport au code que vous lisez (ou devriez utiliser).

Les pilotes de base de données fournissent généralement une interface cohérente et compréhensible pour accéder à des données identiques quel que soit le domaine problématique. Lorsque vous obtenez des données d'une base de données, votre programme en possède une copie . Les mises à jour sont atomiques. Contraste avec les variables globales, où plusieurs threads ou méthodes peuvent fonctionner sur le même élément de données sans aucune atomicité, sauf si vous ajoutez vous-même la synchronisation. Les mises à jour des données sont imprévisibles et difficiles à repérer. Les mises à jour peuvent être entrelacées, ce qui entraîne la corruption des exemples de manuels multilingues (par exemple, des incréments entrelacés).

Les bases de données modélisent au départ des données différentes de celles des variables globales, mais, en laissant cela de côté, les bases de données sont conçues dès le départ pour être un magasin de données conforme à ACID qui atténue nombre des problèmes liés aux variables globales.


la source
4
+1 Ce que vous dites, c'est que les bases de données ont des transactions, ce qui permet de lire et d'écrire plusieurs éléments d'état global de manière atomique. Bon point, qui ne peut être contourné qu'en utilisant des variables globales pour chaque information complètement indépendante.
l0b0
1
@ 10b0 transactions sont le mécanisme qui permet d'atteindre la plupart des objectifs ACID, correct. Mais l'interface de la base de données elle-même rend le code plus clair en plaçant les données dans une portée plus locale. Pensez à utiliser un ensemble JDBC RecordSet avec un bloc try-with-resources ou une fonction ORM qui récupère une donnée à l'aide d'un seul appel de fonction. Comparez cela à la gestion de données éloignées du code que vous lisez dans un contexte global.
1
Donc, il serait bon d’utiliser des variables globales si je copie la valeur dans une variable locale (avec un mutex) au début de la fonction, modifie la variable locale, puis recopie la valeur dans la variable globale à la fin de la fonction? (... demanda-t-il avec rhétorique.)
RM
1
@RM Il a mentionné deux points. Ce que vous avez jeté pourrait concerner le premier (état du programme imprévisible), mais pas le second (la lisibilité de votre code). En fait, cela pourrait rendre la lisibilité de votre programme encore pire: P.
Riwalk
1
@RM Votre fonction serait exécutée de manière cohérente, oui. Mais vous auriez alors la question de savoir si quelque chose d'autre avait modifié la variable globale entre-temps et cette modification était plus importante que ce que vous lui écrivez. Les bases de données peuvent aussi avoir le même problème, bien sûr.
Graham
45

Je ferais quelques observations:

Oui, une base de données est un état global.

En fait, comme vous l'avez souligné, il s'agit d'un état super mondial. C'est universel! Son champ d' action implique quelque chose ou quelqu'un qui se connecte à la base de données. Et je soupçonne que de nombreuses personnes avec des années d’expérience peuvent vous raconter des histoires horribles sur la façon dont des "choses étranges" dans les données ont conduit à un "comportement inattendu" dans une ou plusieurs des applications pertinentes ...

L'une des conséquences potentielles de l'utilisation d'une variable globale est que deux "modules" distincts utiliseront cette variable à leurs propres fins. Et dans cette mesure, une table de base de données n'est pas différente. Il peut être victime du même problème.

Hmm ... Voici la chose:

Si un module ne fonctionne pas de manière extrinsèque, il ne fait rien.

Un module utile peut recevoir des données ou le trouver . Et, il peut renvoyer des données ou modifier l’ état. Mais, si elle n'interagit pas avec le monde extérieur d'une manière ou d'une autre, elle pourrait également ne rien faire.

Nous préférons maintenant recevoir des données et les renvoyer . La plupart des modules sont simplement plus faciles à écrire s’ils peuvent être écrits avec un mépris total pour ce que fait le monde extérieur. Mais en fin de compte, il faut que les données soient trouvées et que cet état global et externe soit modifié .

De plus, dans les applications du monde réel, les données existent pour pouvoir être lues et mises à jour par diverses opérations. Certains problèmes sont évités par des verrous et des transactions. Toutefois, pour éviter que ces opérations ne se contredisent en principe , au bout du compte, il suffit simplement de réfléchir. (Et faire des erreurs ...)

Mais aussi, nous ne travaillons généralement pas directement avec l’état global.

Sauf si l'application réside dans la couche de données (en SQL ou autre), les objets avec lesquels travaillent nos modules sont en réalité des copies de l'état global partagé. Nous pouvons faire ce que nous voulons sans aucun impact sur l’état actuel partagé.

Et, dans les cas où nous devons muter cet état global, en supposant que les données qui nous ont été fournies n’ont pas changé, nous pouvons généralement effectuer le même verrouillage que celui que nous appliquerions à nos globaux locaux.

Et finalement, nous faisons habituellement avec les bases de données des choses différentes de celles que nous pourrions avoir avec des globals vilains.

Un vilain global brisé ressemble à ceci:

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.
  }
}

Nous n'utilisons tout simplement pas de bases de données pour des tâches en cours de traitement / opérationnelles de ce type. Et c’est peut-être la nature lente de la base de données et la commodité relative d’une simple variable qui nous découragent: notre interaction lente et délicate avec les bases de données en fait tout simplement de mauvais candidats pour de nombreuses erreurs que nous avons commises avec des variables.

svidgen
la source
3
Le moyen de garantir (puisque nous ne pouvons pas supposer) "que les données qui nous ont été fournies n’ont pas changé" dans une base de données serait une transaction.
l0b0
Oui ... cela était supposé impliquer "une sorte de verrouillage identique".
svidgen
Mais, il peut être difficile de bien réfléchir à la fin de la journée.
Oui, les bases de données ont effectivement un état global - c'est pourquoi il est si tentant de partager des données en utilisant quelque chose comme git ou ipfs.
William Payne
21

Je ne suis pas d'accord avec l'affirmation fondamentale selon laquelle:

Lorsque votre programme utilise des données d'une base de données, vous ne vous souciez pas de savoir si un autre code de votre système le modifie, ou même si un programme complètement différent le modifie, d'ailleurs.

Ma pensée initiale était "Wow. Just Wow". Tant de temps et d'efforts sont consacrés à éviter exactement cela - et à déterminer quels compromis et compromis fonctionnent pour chaque application. Ignorer cela est une recette pour un désastre.

Mais je fais aussi de la diasgree sur le plan architectural. Une variable globale n'est pas simplement un état global. C'est un état global qui est accessible de n'importe où de manière transparente. Contrairement à l'utilisation d'une base de données, vous devez disposer d'un descripteur - (à moins que vous ne stockiez qu'un descripteur dans une variable globale ...)

Par exemple, utiliser une variable globale pourrait ressembler à ceci

int looks_ok_but_isnt() {
  return global_int++;
}

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

Mais faire la même chose avec une base de données devrait être plus explicite sur ce qu’il fait.

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);
   ...
}

La base de données on est évidemment en train de mettre la main dessus. Si vous ne souhaitez pas utiliser de base de données, vous pouvez utiliser un état explicite qui ressemble à peu près au cas de la base de données.

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);
   ...
}

Donc, je dirais que l'utilisation d'une base de données revient beaucoup plus à utiliser un état explicite qu'à utiliser des variables globales.

Michael Anderson
la source
2
Ouais, je pensais que c'était intéressant quand le PO a déclaré: " Peu importe les données, c'est tout le problème " - si on s'en fiche, alors pourquoi les stocker? Voici une pensée: Arrêtons simplement en utilisant des variables et des données du tout . Cela devrait rendre les choses beaucoup plus simples. "Arrête le monde, je veux descendre!"
1
+1 Différents threads ou applications écrivant et lisant à partir de la même base de données sont une source potentielle d'un grand nombre de problèmes connus. C'est pourquoi il devrait toujours y avoir une stratégie pour y remédier, au niveau de la base de données ou de l'application, ou tous les deux. Donc, il est certainement pas vrai que vous (le développeur de l'application) ne vous souciez pas de savoir qui lit ou écrit dans la base de données.
Andres F.
1
+1 Sur une note de côté, cette réponse explique à peu près ce que je déteste le plus à propos de l'injection de dépendance. Il cache ces types de dépendances.
JPMc26
@ jpmc26 Je marque peut-être des mots, mais ce qui précède n'est-il pas un bon exemple de la manière dont l'injection de dépendance (par opposition à la recherche globale) permet de rendre explicites les dépendances? Il me semble que vous contestez plutôt certaines API, comme peut-être la magie des annotations utilisée par JAX-RS et Spring.
Emil Lundberg
2
@EmilLundberg Non, le problème vient d'une hiérarchie. L'injection de dépendance masque les dépendances des niveaux inférieurs du code des niveaux supérieurs, ce qui rend difficile le suivi des interactions. Par exemple, si MakeNewThingdépend de MakeNewThingInDbet que ma classe de contrôleur utilise MakeNewThing, le code de mon contrôleur n'indique pas clairement que je modifie la base de données. Alors, que se passe-t-il si j'utilise une autre classe qui valide réellement ma transaction en cours dans la base de données? DI rend très difficile le contrôle de la portée d'un objet.
JPMc26
18

Le fait que la seule raison pour laquelle les variables globales ne puissent pas être approuvées étant donné que l'état peut être modifié ailleurs n'est pas, en soi, une raison suffisante pour ne pas les utiliser, a convenu (c'est une très bonne raison cependant!). Il est probable que la réponse consistait principalement en une utilisation où il serait plus logique de limiter l'accès d'une variable à des zones de code concernées.

Les bases de données sont une autre affaire, cependant, car elles sont conçues pour être accessibles «globalement» pour ainsi dire.

Par exemple:

  • Les bases de données ont généralement une validation de type et de structure intégrée qui va plus loin que le langage qui y accède
  • Les bases de données mettent à jour presque à l'unanimité les transactions hors tension, ce qui évite les états incohérents, sans garantie quant à l'apparence de l'état final dans un objet global (à moins qu'il ne soit caché derrière un singleton).
  • La structure de la base de données est au moins implicitement documentée sur la base d'une structure de table ou d'objet, plus que l'application qui l'utilise

Mais surtout, les bases de données ont un objectif différent de celui d’une variable globale. Les bases de données permettent de stocker et de rechercher de grandes quantités de données organisées, où les variables globales servent des niches spécifiques (lorsque cela est justifiable).

Jeffrey Sweeney
la source
1
Huh. Vous m'y avez battu alors que j'étais à mi-chemin en train d'écrire une réponse presque identique. :)
Jules
@Jules, votre réponse fournit plus de détails du côté des applications; garde le.
Jeffrey Sweeney
Toutefois, à moins que vous dépendiez entièrement de procédures stockées pour accéder aux données, toute cette structure ne parviendra toujours pas à faire en sorte que les tables soient utilisées comme prévu. Ou que les opérations sont effectuées dans l'ordre approprié. Ou que des verrous (transactions) sont créés au besoin.
svidgen
Bonjour, les points 1 et 3 sont-ils toujours applicables si vous utilisez un langage de type statique comme Java?
Jesvin Jose
@aitchnyu Pas nécessairement. Nous soulignons que les bases de données sont construites dans le but de partager des données de manière fiable, contrairement aux variables globales. Un objet implémentant une interface auto-documentante dans un langage strict a un objectif différent de celui d'une base de données NoSQL en vrac.
Jeffrey Sweeney
10

Mais quand je regarde cela, je ne peux pas m'empêcher de penser que c'est une explication vraiment faible, car en quoi est-ce différent de travailler avec des données stockées dans une base de données?

Ou différent du travail avec un périphérique interactif, avec un fichier, avec une mémoire partagée, etc. Un programme qui fait exactement la même chose à chaque fois qu'il s'exécute est un programme très ennuyeux et plutôt inutile. Alors oui, c'est un argument faible.

Pour moi, la différence qui fait la différence en ce qui concerne les variables globales est qu’elles forment des lignes de communication cachées et non protégées. La lecture à partir d'un clavier est très évidente et protégée. Je dois effectuer un certain appel de fonction et je ne peux pas accéder au pilote du clavier. Il en va de même pour l'accès aux fichiers, la mémoire partagée et votre exemple, les bases de données. Il est évident pour le lecteur du code que cette fonction lit à partir du clavier, que cette fonction accède à un fichier, qu'une autre fonction accède à la mémoire partagée (et qu'il serait préférable de la protéger), et qu'une autre fonction accède à une base de données.

Avec les variables globales, en revanche, ce n’est pas évident du tout. L'API dit d'appeler foo(this_argument, that_argument). Rien dans la séquence d'appel n'indique que la variable globale g_DangerWillRobinsondoit avoir une valeur quelconque avant d'appeler foo(ou d'être examinée après l'appel foo).


Google a interdit l'utilisation d'arguments de référence non const en C ++, principalement parce que cela n'est pas évident pour le lecteur du code qui foo(x)va changer, xcar cela fooprend comme argument une référence non constante. (Comparez avec C #, qui indique que la définition de la fonction et le site d'appel doivent qualifier un paramètre de référence avec le refmot clé.) Bien que je ne sois pas d'accord avec la norme Google à ce sujet, je comprends leur argument.

Le code est écrit une fois et modifié plusieurs fois, mais s'il est bon, il est lu beaucoup de fois. Les lignes de communication cachées sont un très mauvais karma. La référence non constante de C ++ représente une ligne de communication cachée mineure. Une bonne API ou un bon IDE me montrera que "Oh! C'est un appel par référence." Les variables globales constituent une énorme voie de communication cachée.

David Hammen
la source
Votre réponse a plus de sens.
Billal Begueradj
8

Je pense que l'explication citée simplifie à l'extrême le problème au point que le raisonnement devient ridicule. Bien entendu, l'état d'une base de données externe contribue à l'état global. La question importante est de savoir commentvotre programme dépend de l'état global (mutable). Si une fonction de bibliothèque permettant de scinder des chaînes sur un espace blanc dépend des résultats intermédiaires stockés dans une base de données, je m'opposerais à cette conception au moins autant que je le ferais à un tableau de caractères global utilisé dans le même but. D'autre part, si vous décidez que votre application n'a pas besoin d'un SGBD complet pour stocker les données de l'entreprise à ce stade et qu'une structure clé-valeur globale en mémoire suffira, ce n'est pas nécessairement le signe d'une conception médiocre. Ce qui est important, c’est que, quelle que soit la solution choisie pour stocker vos données, ce choix s’applique à une très petite partie du système, de sorte que la plupart des composants peuvent rester indépendants de la solution choisie pour le déploiement, et être testés dans des unités isolées et déployées. La solution peut être changée ultérieurement avec peu d'effort.

5gon12eder
la source
8

En tant qu’ingénieur logiciel travaillant principalement avec des microprogrammes intégrés, j’utilise presque toujours des variables globales pour tout ce qui se passe entre les modules. En fait, c'est la meilleure pratique pour les systèmes intégrés. Ils sont attribués de manière statique, il n'y a donc aucun risque d'explosion du tas / de la pile et aucun temps supplémentaire n'est alloué pour l'allocation / le nettoyage de la pile à l'entrée / à la sortie de la fonction.

L'inconvénient est que nous ne devons considérer la façon dont ces variables sont utilisées, et beaucoup de ce qui revient au même genre de pensée qui va dans la base de données-tiraillements. Toute lecture / écriture asynchrone de variables DOIT être atomique. Si plusieurs emplacements peuvent écrire une variable, vous devez vous assurer qu'ils écrivent toujours des données valides, de sorte que l'écriture précédente ne soit pas remplacée de manière arbitraire (ou que le remplacement arbitraire soit une chose sûre à faire). Si la même variable est lue plus d'une fois, il faut réfléchir à ce qui se passe si la variable change de valeur entre les lectures, ou une copie de la variable doit être prise au début de sorte que le traitement soit effectué en utilisant une valeur cohérente, même si cette valeur devient périmée pendant le traitement.

(Pour ce dernier, le tout premier jour de mon contrat de travail sur un système de contre-mesures pour aéronefs, qui était tellement lié à la sécurité, l'équipe des logiciels examinait un rapport de bogue qu'ils essayaient de résoudre depuis environ une semaine. J'avais eu juste le temps de télécharger les outils de développement et une copie du code. Je lui ai demandé "Cette variable ne peut-elle pas être mise à jour entre les lectures et ce qui le cause?", Mais je n'ai pas vraiment eu de réponse. Alors, pendant qu'ils en discutaient encore, j'ai ajouté un code de protection pour lire la variable de manière atomique, une génération locale et en gros je me suis dit "hé les gars, essayez ceci". :)

Les variables globales ne sont donc pas une mauvaise chose sans équivoque, mais elles vous laissent faire face à un large éventail de problèmes si vous n'y réfléchissez pas attentivement.

Graham
la source
7

En fonction de l'aspect que vous jugez, les variables globales et l'accès à la base de données peuvent être des mondes à part, mais tant que nous les jugeons comme des dépendances, ils sont identiques.

Considérons la définition par la programmation fonctionnelle d'une fonction pure, selon laquelle elle dépend uniquement des paramètres qu'elle prend en entrée, produisant une sortie déterministe. En d'autres termes, si le même jeu d'arguments est répété deux fois, il doit produire le même résultat.

Lorsqu'une fonction dépend d'une variable globale, elle ne peut plus être considérée comme pure, car, pour le même ensemble ou les mêmes arguments, elle peut générer des résultats différents, car la valeur de la variable globale peut avoir changé entre les appels.

Cependant, la fonction peut toujours être considérée comme déterministe si nous considérons la variable globale comme une partie de l'interface de la fonction au même titre que ses autres arguments. Ce n'est donc pas le problème. Le problème est seulement que cela est caché jusqu'au moment où nous sommes surpris par un comportement inattendu de fonctions apparemment évidentes, puis allez lire leurs implémentations pour découvrir les dépendances cachées .

Cette partie, le moment où une variable globale devient une dépendance cachée est ce qui est considéré comme un mal par nos programmeurs. Cela rend le code plus difficile à raisonner, à prédire comment il va se comporter, difficile à réutiliser, difficile à tester et surtout, cela augmente le temps de débogage et de résolution du problème lorsqu'un problème survient.

La même chose se produit lorsque nous masquons la dépendance à la base de données. Nous pouvons avoir des fonctions ou des objets appelant directement des requêtes et des commandes de base de données, masquant ces dépendances et nous causant exactement le même problème que les variables globales; ou nous pouvons les rendre explicites, ce qui, en fin de compte, est considéré comme une meilleure pratique qui porte plusieurs noms, tels que modèle de référentiel, magasin de données, passerelle, etc.

PS: Il existe d’autres aspects importants pour cette comparaison, par exemple le fait qu’il s’agisse d’une concurrence ou non, mais ce point est couvert par d’autres réponses ici.

MichelHenrich
la source
J'aime que vous preniez cela sous l'angle des dépendances.
cbojar
6

Bon, commençons par le point historique.

Nous sommes dans une ancienne application, écrite dans votre mélange typique d'assemblage et de C. Il n'y a pas de fonctions, juste des procédures . Lorsque vous souhaitez passer un argument ou une valeur de retour d'une procédure, vous utilisez une variable globale. Inutile de dire que c’est assez difficile à suivre, et en général, chaque procédure peut faire ce qu’elle veut avec chaque variable globale. Sans surprise, les utilisateurs ont eu tendance à passer des arguments et à renvoyer des valeurs d'une manière différente dès que c'était faisable (à moins que cela ne soit pas critique en termes de performances - consultez par exemple le code source de Build Engine (Duke 3D)). La haine des variables globales est née ici: vous aviez très peu idée de la portion d'état global que chaque procédure lirait et modifierait, et vous ne pouviez pas vraiment imbriquer les appels de procédure en toute sécurité.

Cela signifie-t-il que la haine de variable globale fait partie du passé? Pas assez.

Tout d'abord, je dois mentionner que j'ai vu exactement la même approche pour passer des arguments dans le projet sur lequel je travaille actuellement. Pour passer deux instances de type de référence en C #, dans un projet vieux d'environ 10 ans. Il n'y a littéralement aucune bonne raison de le faire ainsi, et est probablement né de la culture de la cargaison ou d'un malentendu complet sur le fonctionnement de C #.

Le point le plus important est qu'en ajoutant des variables globales, vous élargissez la portée de chaque morceau de code ayant accès à cette variable globale. Vous vous souvenez de toutes ces recommandations telles que "gardez vos méthodes courtes"? Si vous avez 600 variables globales (encore une fois, exemple concret: /), toutes les étendues de vos méthodes sont implicitement développées par ces 600 variables globales. Il n'existe pas de moyen simple de savoir qui a accès à quoi.

Si cela est mal fait (comme d'habitude :)), les variables globales peuvent avoir un couplage entre elles. Mais vous ne savez pas comment ils sont couplés et il n'existe aucun mécanisme pour garantir la cohérence de l'état global. Même si vous introduisez des sections critiques pour essayer de garder les choses cohérentes, vous constaterez que cela se compare mal à une base de données ACID appropriée:

  • Il n'y a aucun moyen d'annuler une mise à jour partielle, sauf si vous conservez les anciennes valeurs avant la "transaction". Inutile de dire qu'à ce stade, passer une valeur en tant qu'argument est déjà une victoire :)
  • Toute personne accédant au même état doit adhérer au même processus de synchronisation. Mais il n'y a aucun moyen de faire respecter cela - si vous oubliez de configurer la section critique, vous êtes foutu.
  • Même si vous synchronisez correctement tous les accès, il se peut que des appels imbriqués accèdent à l'état partiellement modifié. Cela signifie que vous pouvez soit bloquer (si vos sections critiques ne sont pas réétendantes), soit gérer des données incohérentes (si elles sont rééternantes).

Est-il possible de résoudre ces problèmes? Pas vraiment. Vous avez besoin d'encapsulation pour gérer cela, ou d'une discipline très stricte. Il est difficile de bien faire les choses et ce n’est généralement pas une très bonne recette pour réussir en développement logiciel :)

Une portée plus petite a tendance à rendre le code plus facile à raisonner. Les variables globales font que même les éléments de code les plus simples incluent d’énormes pans de portée.

Bien sûr, cela ne signifie pas que la portée mondiale est un mal. Cela ne devrait tout simplement pas être la première solution que vous choisissez: c’est un exemple typique de «simple à mettre en œuvre, difficile à maintenir».

Luaan
la source
Cela ressemble beaucoup au monde physique: il est très difficile de revenir en arrière.
C'est une bonne réponse, mais cela pourrait résister à une déclaration de thèse (section TL; DR) au début.
JPMc26
6

Une variable globale est un outil, il peut être utilisé pour le bien et pour le mal.

Une base de données est un outil, il peut être utilisé pour le bien et pour le mal.

Comme le note l’affiche originale, la différence n’est pas si grande.

Les étudiants inexpérimentés pensent souvent que les insectes sont quelque chose qui arrive à d'autres personnes. Les enseignants utilisent "Les variables globales sont pervers" comme une raison simplifiée pour pénaliser les mauvais designs. Généralement, les étudiants ne comprennent pas que le fait que leur programme de 100 lignes soit exempt de bogues ne signifie pas que les mêmes méthodes peuvent être utilisées pour des programmes de 10 000 lignes.

Lorsque vous travaillez avec des bases de données, vous ne pouvez pas simplement interdire l’état global car c’est la raison d’être du programme. Au lieu de cela, vous obtenez des instructions plus détaillées telles que ACID et les formulaires normaux, etc.

Si les personnes utilisaient l'approche ACID pour les variables globales, elles ne seraient pas si mauvaises.

D'autre part, si vous concevez mal les bases de données, elles peuvent être cauchemardesques.

Stig Hemmer
la source
3
Déclaration typique d'un étudiant sur stackoverflow: Aidez-moi! Mon code est parfait, mais il ne fonctionne pas correctement!
David Hammen
"Approche ACID des variables globales" - voir les références dans Clojure.
Charles Duffy
@ Davidhammen et vous pensez que les professionnels ont un cerveau différent de celui des étudiants?
Billal Begueradj
@BillalBEGUERADJ - C'est la différence entre les professionnels et les étudiants. Nous savons que malgré des années d’expérience et malgré tous les efforts déployés en matière de révision de code, de test, etc., notre code n’est pas parfait.
David Hammen
5

Pour moi, le principal mal est que Globals n’ait aucune protection contre les problèmes de concurrence. Vous pouvez ajouter des mécanismes pour traiter de tels problèmes avec Globals, mais vous constaterez que plus vous résolvez de problèmes de simultanéité, plus vos globes commencent à imiter une base de données. Le mal secondaire n'est pas un contrat d'utilisation.

G DeMasters
la source
3
Par exemple, errnodans C.
David Hammen,
1
Cela explique exactement pourquoi les globales et les bases de données ne sont pas identiques. Il peut y avoir d'autres différences, mais votre message spécifique détruit entièrement le concept. Si vous donniez un exemple de code rapide, je suis sûr que vous obtiendriez de nombreux votes positifs. Par exemple, MyFunc () {x = globalVar * 5; // .... Un autre traitement; y = globalVar * 34; // Ooops, un autre thread aurait pu changer globalVar au cours d'un autre traitement et x et y utilisent des valeurs différentes pour globalVar dans leurs calculs, ce qui ne donnerait presque certainement pas de résultats souhaitables.
Dunk
5

Certaines des autres réponses tentent d'expliquer pourquoi l'utilisation d'une base de données est bonne. Ils ont tort! Une base de données est un état global et, en tant que telle, est aussi diabolique qu'un singleton ou une variable globale. Utiliser une base de données est une mauvaise chose quand vous pouvez facilement utiliser une carte locale ou un tableau à la place!

Les variables globales permettent un accès global, ce qui comporte un risque d'abus. Les variables globales ont également des avantages. On dit généralement que les variables globales sont quelque chose que vous devriez éviter, pas quelque chose que vous ne devriez jamais utiliser. Si vous pouvez facilement les éviter, vous devriez les éviter. Mais si les avantages l'emportent sur les inconvénients, vous devez bien sûr les utiliser! *

La même chose ** s'applique aux bases de données, qui sont à l'état global - tout comme les variables globales. Si vous pouvez vous débrouiller sans accès à une base de données et que la logique qui en résulte fait tout ce dont vous avez besoin et est tout aussi complexe, l'utilisation d'une base de données augmente les risques pour votre projet, sans aucun avantage correspondant.

Dans la réalité, de nombreuses applications nécessitent un état global de conception, parfois même un état global persistant - c'est pourquoi nous avons des fichiers, des bases de données, etc.


* L'exception ici sont les étudiants. Il est logique d’interdire aux étudiants d’utiliser des variables globales afin d’apprendre quelles sont les alternatives.

** Certaines réponses prétendent à tort que les bases de données sont mieux protégées que d'autres formes d'état global (la question concerne explicitement l'état global , pas seulement les variables globales). C'est des conneries. La protection principale offerte dans le scénario de base de données est par convention, ce qui est identique pour tout autre état global. La plupart des langages offrent également une protection supplémentaire pour l’état global, sous la forme de constclasses qui ne permettent tout simplement pas de modifier leur état après qu’il a été défini dans le constructeur, ou d’objecteurs et de paramètres qui peuvent prendre en compte les informations de thread ou l’état du programme.

Peter
la source
2

En un sens, la distinction entre les variables globales et une base de données est similaire à la distinction entre les membres privés et publics d'un objet (en supposant que quiconque utilise encore des champs publics). Si vous considérez l'ensemble du programme comme un objet, les variables globales sont les variables privées et la base de données, les champs publics.

La distinction essentielle en est une de responsabilité assumée.

Lorsque vous écrivez un objet, il est supposé que toute personne qui gère les méthodes de membre veillera à ce que les champs privés restent bien gérés. Mais vous renoncez déjà à toute hypothèse sur l'état des champs publics et les traitez avec un soin particulier.

La même hypothèse s’applique à un niveau plus large à la base de données globals v / s. En outre, le langage / écosystème de programmation garantit les restrictions d’accès aux v / s privés publics de la même manière qu’il les applique à la base de données v / s de globals (mémoire non partagée).

Lorsque le multithreading entre en jeu, le concept de base de données v / s global v / s privé v / s public n'est que des distinctions le long d'un spectre.

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
        }
    }
}
Benito Ciaro
la source
1

Une base de données peut être un état global, mais il n'est pas nécessaire qu'elle le soit tout le temps. Je suis en désaccord avec l'hypothèse que vous n'avez pas le contrôle. Un moyen de gérer cela est le verrouillage et la sécurité. Cela peut être fait à l'enregistrement, la table ou la base de données entière. Une autre approche consiste à créer une sorte de champ de version qui empêcherait la modification d’un enregistrement si les données sont périmées.

Comme une variable globale, les valeurs d'une base de données peuvent être modifiées une fois qu'elles sont déverrouillées, mais il existe de nombreuses façons de contrôler l'accès (Ne donnez pas le mot de passe à tous les développeurs pour le compte autorisé à modifier les données.). Si vous avez une variable dont l'accès est limité, ce n'est pas très global.

JeffO
la source
0

Il y a plusieurs différences:

  • Une valeur de base de données peut être modifiée à la volée. En revanche, la valeur d'un global définie dans le code ne peut être modifiée que si vous redéployez votre application et modifiez votre code. En fait, c'est intentionnel. Une base de données est pour les valeurs qui pourraient changer au fil du temps, mais les variables globales devraient seulement être pour des choses qui ne changeront jamais et quand ils ne contiennent pas de données réelles.

  • Une valeur de base de données (ligne, colonne) a un contexte et un mappage relationnel dans la base de données. Cette relation peut être facilement extraite et analysée à l'aide d'outils comme Jailer (par exemple). Une variable globale, en revanche, est légèrement différente. Vous pouvez trouver tous les usages, mais il vous serait impossible de me dire toutes les manières dont la variable interagit avec le reste de votre monde.

  • Les variables globales sont plus rapides . Obtenir quelque chose d'une base de données nécessite une connexion à la base de données, une sélection à mon exécution, puis la connexion à la base de données doit être fermée. Toutes les conversions dont vous pourriez avoir besoin s’ajoutent à cela. Comparez cela à un accès global dans votre code.

Ce sont les seuls auxquels je puisse penser pour le moment, mais je suis sûr qu'il y en a plus. En termes simples, il s’agit de deux choses différentes qui doivent être utilisées pour des objectifs différents .

Arnab Datta
la source
0

Bien sûr, les globals ne sont pas toujours inappropriés. Ils existent parce qu'ils ont un usage légitime. Le principal problème des globals, et la principale source d'avertissement pour les éviter, est qu'un code utilisant un global est associé à celui-ci et à un seul.

Par exemple, considérons un serveur HTTP stockant le nom du serveur.

Si vous stockez le nom du serveur dans un fichier global, le processus ne peut pas simultanément exécuter la logique pour deux noms de serveur différents. Peut-être que la conception d'origine n'avait jamais envisagé d'exécuter plus d'une instance de serveur à la fois, mais si vous décidez plus tard de le faire, vous ne pourrez tout simplement pas si le nom du serveur est global.

En revanche, si le nom du serveur est dans une base de données, il n'y a pas de problème. Vous pouvez simplement créer une instance de cette base de données pour chaque instance du serveur HTTP. Étant donné que chaque instance du serveur possède sa propre instance de la base de données, elle peut avoir son propre nom de serveur.

Donc, l'objection principale aux globales, il ne peut y avoir qu'une seule valeur pour tout le code qui accède à ce global, ne s'applique pas aux entrées de base de données. Le même code peut facilement accéder à des instances de base de données distinctes ayant des valeurs différentes pour une entrée particulière.

David Schwartz
la source
0

Je pense que c’est une question intéressante, mais il est un peu difficile de répondre à cette question car deux problèmes principaux sont regroupés sous le terme «État mondial». Le premier est le concept de «couplage global». La preuve en est que l'alternative donnée pour l'état global est l'injection de dépendance. Le fait est que DI n'élimine pas nécessairement l'état global. En d'autres termes, il est tout à fait possible et courant d'injecter des dépendances sur un état global. Ce que fait DI, c'est enlever le couplage qui vient avec les variables globales et le motif Singleton couramment utilisé. Hormis une conception légèrement moins évidente, l'élimination de ce type de couplage présente très peu d'inconvénients et les avantages de cette suppression augmentent de manière exponentielle avec le nombre de dépendances sur ces globaux.

L'autre aspect de cet état est l'état partagé. Je ne sais pas s'il existe une distinction très nette entre un état partagé globalement et un état partagé en général, mais les coûts et les avantages sont beaucoup plus nuancés. En termes simples, il existe d'innombrables systèmes logiciels qui nécessitent un état partagé pour être utiles. Le bitcoin, par exemple, est un moyen très intelligent de partager un État de manière globale (littéralement) de manière décentralisée. Partager correctement un état mutable sans créer d’énormes goulets d’étranglement est difficile mais utile. Donc, si vous n'avez pas vraiment besoin de le faire, vous pouvez simplifier votre application en minimisant l'état mutable partagé.

Ainsi, la question de savoir comment les bases de données diffèrent des bases globales est également abordée sous ces deux aspects. Est-ce qu'ils introduisent le couplage? Oui, ils le peuvent, mais cela dépend beaucoup de la conception de l'application et de la conception de la base de données. Il y a trop de facteurs pour qu'une réponse unique puisse déterminer si les bases de données introduisent un couplage global sans détails de la conception. Quant à savoir s'ils introduisent le partage de l'état, eh bien, c'est en quelque sorte le point essentiel d'une base de données. La question est de savoir s'ils le font bien. Encore une fois, j'estime qu'il est trop compliqué de répondre sans de nombreuses autres informations telles que les alternatives et de nombreux autres compromis.

JimmyJames
la source
0

J'y penserais un peu différemment: une "variable globale" comme un comportement est un prix payé par les administrateurs de base de données (DBA), car c'est un mal nécessaire pour faire leur travail.

Comme plusieurs autres personnes l'ont souligné, le problème des variables globales n'est pas arbitraire. Le problème est que leur utilisation rend le comportement de votre programme de moins en moins prévisible, car il devient plus difficile de déterminer qui utilise la variable et de quelle manière. C'est un gros problème pour les logiciels modernes, car on leur demande généralement de faire beaucoup de choses souples. Il peut faire des milliards, voire des trillions de manipulations d’états complexes au cours d’une course. La capacité de prouver de vraies déclarations sur ce que le logiciel fera dans ces milliards ou ces milliards d'opérations est extrêmement utile.

Dans le cas des logiciels modernes, toutes nos langues fournissent des outils d'aide, tels que l'encapsulation. Le choix de ne pas l'utiliser est inutile, ce qui conduit à la mentalité "les globals sont mauvais". Dans de nombreuses régions du domaine du développement logiciel, les seules personnes qui les utilisent sont des personnes qui ne savent pas mieux coder. Cela signifie qu'ils ne sont pas seulement des problèmes directement, mais ils suggèrent indirectement que le développeur ne savait pas ce qu'il faisait. Dans d’autres régions, vous constaterez que les globaux sont tout à fait normaux (les logiciels embarqués, en particulier, aiment les globals, en partie parce qu’ils fonctionnent bien avec les ISR). Cependant, parmi les nombreux développeurs de logiciels, ils sont la voix d'une minorité, de sorte que la seule voix que vous entendez est "les globals sont diaboliques".

Le développement de bases de données est l'une de ces situations de voix minoritaires. Les outils nécessaires pour faire un travail de DBA sont très puissants, et leur théorie est pas enracinée dans l' encapsulation. Pour exploiter chaque performance de leurs bases de données, ils ont besoin d'un accès complet et sans entrave à tout, à l'instar des globals. Utilisez l'une de leurs énormes bases de données de 100 millions de lignes (ou plus!), Et vous comprendrez pourquoi ils ne laissent pas leur moteur de base de données tenir le coup.

Ils paient un prix pour cela, un prix cher. Les DBA sont obligés d'être presque pathologiques par leur souci du détail, car leurs outils ne les protègent pas. Le meilleur moyen de protection est ACID ou peut-être des clés étrangères. Ceux qui ne sont pas pathologiques se retrouvent avec un fouillis de tables complètement inutilisable, voire corrompu.

Il n’est pas rare d’avoir des progiciels de 100 000 lignes. En théorie, n'importe quelle ligne du logiciel peut affecter n'importe quel type global à tout moment. Dans les administrateurs de base de données, vous ne trouvez jamais 100 000 requêtes différentes pouvant modifier la base de données. Il serait déraisonnable de maintenir l’attention portée aux détails pour vous protéger de vous-même. Si un administrateur de base de données a quelque chose d'important dans ce sens, il encapsulera intentionnellement sa base de données à l'aide d'accesseurs, en contournant les problèmes de type "global", puis effectuera le maximum de travail possible grâce à ce mécanisme "plus sûr". Ainsi, même lorsque la base de données est utilisée, les utilisateurs évitent les globals. Ils viennent tout simplement avec beaucoup de danger, et il existe des alternatives qui sont tout aussi fortes, mais pas aussi dangereuses.

Préférez-vous vous promener sur du verre brisé ou sur des trottoirs bien balayés, si toutes les autres choses sont égales? Oui, vous pouvez marcher sur du verre brisé. Oui, certaines personnes gagnent même leur vie en le faisant. Mais quand même, laissez-les simplement balayer le trottoir et passer à autre chose!

Cort Ammon
la source
0

Je pense que la prémisse est fausse. Il n'y a aucune raison qu'une base de données doive être un "état global" plutôt qu'un objet de contexte (très grand). Si vous vous liez à la base de données que votre code utilise via des variables globales ou des paramètres de connexion de base de données globaux fixes, il n’est pas différent et n’est pas moins dommageable que tout autre état global. D'autre part, si vous transmettez correctement un objet de contexte pour la connexion à la base de données, il s'agit simplement d'un état contextuel volumineux (et largement utilisé), et non d'un état global.

Mesurer la différence est simple: pouvez-vous exécuter deux instances de la logique de votre programme, chacune utilisant sa propre base de données, dans un seul programme / processus sans apporter de modifications invasives au code? Si tel est le cas, votre base de données n'est pas vraiment "globale".

R ..
la source
-2

Les globaux ne sont pas mauvais. ils sont simplement un outil. La mauvaise utilisation des globaux est problématique, de même que l'utilisation abusive de toute autre fonction de programmation.

Ma recommandation générale est que les globals ne devraient être utilisés que dans des situations bien comprises et réfléchies, où d’autres solutions sont moins optimales. Plus important encore, vous voulez vous assurer que vous avez bien documenté les endroits où cette valeur globale peut être modifiée et que, si vous utilisez plusieurs méthodes d'exécution multithread, assurez-vous que l'accès aux transactions globales globales et à toutes les dépendances co-dépendantes est transactionnel.

Byron Jones
la source
Est-ce que certains des électeurs défavorisés pourraient expliquer vos votes négatifs? Il semble impoli de voter sans explication.
Byron Jones
-2

Motif en lecture seule et supposez que vos données ne sont pas à jour lorsque vous les imprimez. La file d'attente écrit ou gère les conflits d'une autre manière. Bienvenue au diable, vous utilisez la base de données globale.

Vince
la source