Pourquoi l'état global est-il si mauvais?

328

Avant de commencer, permettez-moi de dire que je connais bien les concepts d'abstraction et d'injection de dépendance. Je n'ai pas besoin que mes yeux soient ouverts ici.

Eh bien, la plupart d’entre nous disent (trop) souvent, sans vraiment comprendre «Ne pas utiliser de variables globales» ou «Les singletons sont diaboliques parce qu’ils sont globaux». Mais quel est vraiment le problème de cet état mondial menaçant?

Supposons que j'ai besoin d'une configuration globale pour mon application, par exemple des chemins de dossiers système ou des informations d'identification de base de données à l'échelle de l'application.

Dans ce cas, je ne vois pas de bonne solution autre que de fournir ces paramètres dans une sorte d'espace global, qui sera généralement disponible pour l'ensemble de l'application.

Je sais que c'est mauvais d'en abuser , mais l'espace mondial est-il vraiment SI mal? Et si c'est le cas, quelles sont les bonnes alternatives?

Madara Uchiha
la source
5
vous pouvez utiliser une classe de configuration qui gère l'accès à ces paramètres. Je pense que sa bonne pratique consiste à avoir un seul et unique endroit où les paramètres de configuration sont lus à partir de fichiers de configuration et / ou de bases de données, tandis que d'autres objets les récupèrent à partir de cette configuration. cela rend votre conception plus propre et prévisible.
Hajo
25
Où est la différence avec l'utilisation d'un global? Votre "seul et unique endroit" ressemble étrangement à un Singleton.
Madara Uchiha
130
Le seul vrai mal sont les dogmes.
Pieter B
34
Utiliser l'état global avec vos collègues programmeurs, c'est comme utiliser la même brosse à dents avec vos amis - vous pouvez, mais vous ne savez jamais quand quelqu'un décidera de l'enfler.
ZiGi
5
Le diable est le mal. L'état global n'est ni mauvais ni bon. Cela peut être très utile ou dangereux en fonction de votre utilisation. Ne pas écouter les autres et juste apprendre à programmer correctement.
annoying_squid

Réponses:

282

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

Pour élaborer, imaginez que vous avez deux objets qui utilisent tous les deux la même variable globale. En supposant que vous n'utilisez pas de source aléatoire dans l'un ou l'autre des modules, la sortie d'une méthode particulière peut être prédite (et donc testée) si l'état du système est connu avant l'exécution de la méthode.

Toutefois, si une méthode dans l'un des objets déclenche un effet secondaire modifiant la valeur de l'état global partagé, vous ne savez plus quel est l'état de départ lorsque vous exécutez une méthode dans l'autre objet. Vous ne pouvez plus prédire quelle sortie vous obtiendrez lorsque vous exécuterez la méthode et vous ne pourrez donc pas la tester.

Au niveau académique, cela ne semble pas si sérieux, mais être capable de coder des tests unitaires est une étape majeure dans le processus de preuve de sa correction (ou du moins de son adéquation à son objectif).

Dans le monde réel, cela peut avoir des conséquences très graves. Supposons que vous ayez une classe qui renseigne une structure de données globale et une classe différente qui utilise les données de cette structure de données, en modifiant son état ou en le détruisant au cours du processus. Si la classe de processeur exécute une méthode avant que la classe de populator ne soit terminée, le résultat est que la classe de processeur aura probablement des données incomplètes à traiter et que la structure de données sur laquelle la classe de populator travaillait pourrait être corrompue ou détruite. Le comportement du programme dans ces circonstances devient complètement imprévisible et conduira probablement à une perte épique.

En outre, l'état global nuit à la lisibilité de votre code. Si votre code a une dépendance externe qui n'est pas explicitement introduite dans celui-ci, le responsable de la maintenance de votre code devra le rechercher pour déterminer d'où il provient.

En ce qui concerne les alternatives existantes, il est impossible de ne pas avoir d'état global, mais dans la pratique, il est généralement possible de limiter l'état global à un seul objet qui englobe tous les autres, et qui ne doit jamais être référencé en s'appuyant sur les règles de cadrage. de la langue que vous utilisez. Si un objet particulier a besoin d'un état particulier, il doit le demander explicitement en le faisant passer comme argument à son constructeur ou par une méthode de définition. Ceci est connu sous le nom d'injection de dépendance.

Il peut sembler stupide de passer dans un état auquel vous pouvez déjà accéder en raison des règles de portée de la langue que vous utilisez, mais les avantages sont énormes. Maintenant, si quelqu'un regarde le code de manière isolée, il est clair de quel état il a besoin et d'où il vient. Il présente également d’énormes avantages en ce qui concerne la flexibilité de votre module de code et, par conséquent, les possibilités de le réutiliser dans différents contextes. Si l'état est passé et que les modifications apportées à l'état sont locales au bloc de code, vous pouvez alors transmettre l'état de votre choix (s'il s'agit du type de données correct) et laisser votre code le traiter. Le code écrit dans ce style a généralement l’apparence d’une collection de composants faiblement associés qui peuvent facilement être interchangés. Le code d'un module ne devrait pas se soucier d'où vient l'état, mais comment le traiter.

Il y a beaucoup d'autres raisons pour lesquelles le fait de passer d'un état à un autre est nettement supérieur au fait de s'appuyer sur un état global. Cette réponse n’est nullement exhaustive. Vous pourriez probablement écrire un livre entier sur la raison pour laquelle l'état global est mauvais.

GordonM
la source
61
Fondamentalement, comme tout le monde peut changer d’État, vous ne pouvez pas compter sur lui.
Oded
21
@Truth L'alternative? Injection de dépendance .
Rédempteur
12
Votre argument principal ne s'applique pas vraiment à quelque chose qui est effectivement en lecture seule, tel qu'un objet représentant une configuration.
Frankc
53
Rien de tout cela n'explique pourquoi les variables globales en lecture seule sont mauvaises… ce à quoi l'OP a explicitement demandé. C'est une bonne réponse, je suis juste surpris que le PO l'ait marqué comme «accepté» lorsqu'il a explicitement abordé un point différent.
Konrad Rudolph
20
being able to unit test code is a major step in the process of proving its correctness (or at least fitness for purpose). Non ce n'est pas. "Cela fait maintenant deux décennies qu'il a été souligné que les tests de programmes peuvent démontrer de manière convaincante la présence de bogues, mais ne peuvent jamais démontrer leur absence. Après avoir cité cette remarque bien médiatisée, l'ingénieur en logiciel revient à l'ordre du jour et continue pour affiner ses stratégies de tests, tout comme l’alchimiste d’autrefois, qui a continué à affiner ses purifications chrysocosmiques. " - Djikstra, 1988. (Cela fait maintenant 4,5 décennies ...)
Mason Wheeler
135

L'état global modifiable est diabolique pour plusieurs raisons:

  • Bugs de l'état global mutable - beaucoup de bugs difficiles sont causés par la mutabilité. Les bugs pouvant être causés par une mutation à partir de n’importe où dans le programme sont encore plus complexes, car il est souvent difficile de déterminer la cause exacte.
  • Mauvaise testabilité - si vous avez un état global mutable, vous devrez le configurer pour tous les tests que vous écrivez. Cela rend les tests plus difficiles (et les gens sont donc moins susceptibles de le faire!). Par exemple, dans le cas des informations d'identification de la base de données à l'échelle de l'application, que se passe-t-il si un test doit accéder à une base de données de test spécifique différente de tout le reste?
  • Inflexibilité - que se passe-t-il si une partie du code nécessite une valeur dans l'état global, alors qu'une autre partie nécessite une autre valeur (par exemple, une valeur temporaire lors d'une transaction)? Vous avez soudainement un peu de refactoring sur vos mains
  • Fonction impureté - les fonctions "pures" (c.-à-d. Celles dont le résultat dépend uniquement des paramètres d'entrée et qui n'ont pas d'effets secondaires) sont beaucoup plus faciles à raisonner et à composer pour construire des programmes plus volumineux. Les fonctions qui lisent ou manipulent un état global mutable sont intrinsèquement impures.
  • Compréhension du code - le comportement du code qui dépend de nombreuses variables globales mutables est beaucoup plus compliqué à comprendre - vous devez comprendre la gamme des interactions possibles avec la variable globale avant de pouvoir raisonner sur le comportement du code. Dans certaines situations, ce problème peut devenir insoluble.
  • Problèmes de simultanéité - un état global mutable nécessite généralement une certaine forme de verrouillage lorsqu'il est utilisé dans une situation concurrente. C'est très difficile à résoudre (cause de bogues) et ajoute beaucoup plus de complexité à votre code (difficile / coûteux à maintenir).
  • Performances : plusieurs threads qui basent continuellement sur le même état global provoquent des conflits de cache et ralentissent le système dans son ensemble.

Alternatives à l'état global mutable:

  • Paramètres de fonction - souvent négligés, mais mieux paramétrer vos fonctions est souvent le meilleur moyen d'éviter un état global. Cela vous oblige à résoudre la question conceptuelle importante: de quelle information cette fonction a-t-elle besoin pour faire son travail? Parfois, il est logique de disposer d'une structure de données appelée "Contexte" pouvant être transmise à une chaîne de fonctions qui englobe toutes les informations pertinentes.
  • Injection de dépendance - comme pour les paramètres de fonction, juste un peu plus tôt (à la construction de l'objet plutôt qu'à l'appel de la fonction). Faites attention si vos dépendances sont des objets mutables, cela peut rapidement causer les mêmes problèmes que l'état global mutable .....
  • L’état global immuable est en grande partie inoffensif - c’est effectivement une constante. Mais assurez-vous qu'il s'agit bien d'une constante et que vous ne serez pas tenté de le transformer ultérieurement en un état mondial mutable!
  • Singletons immuables - à peu près les mêmes que l'état global immuable, sauf que vous pouvez différer l'instanciation jusqu'à ce que vous en ayez besoin. Utile pour, par exemple, les grandes structures de données fixes qui nécessitent un pré-calcul unique coûteux. Les singletons mutables sont bien sûr équivalents à l'état global mutable et sont donc pervers :-)
  • Liaison dynamique - disponible uniquement dans certaines langues telles que Common Lisp / Clojure, mais cela vous permet effectivement de lier une valeur dans une étendue contrôlée (généralement sur une base de threads locale) sans affecter les autres threads. Dans une certaine mesure, il s'agit d'un moyen "sûr" d'obtenir le même effet qu'une variable globale, car vous savez que seul le thread d'exécution en cours sera affecté. Ceci est particulièrement utile dans le cas où vous avez plusieurs threads, chacun gérant des transactions indépendantes, par exemple.
Mikera
la source
11
Je pense que le fait de passer un objet de contexte par paramètre de fonction ou par injection de dépendance poserait des problèmes si le contexte est modifiable, problème identique à celui de l’utilisation de l’état global mutable.
Alfredo Osorio
1
Toutes les bonnes choses, et amen! Mais la question concerne l’état global immuable
MarkJ
@ Alfredo - très vrai. Bien que ce ne soit pas aussi grave car au moins la portée peut être contrôlée quelque peu. Mais en général, il est plus facile de résoudre le problème en rendant les contextes et les dépendances immuables.
Mikera
2
+1 pour mutable / immuable. Les globaux immuables sont ok. Même ceux qui sont paresseux chargés mais ne changent jamais. Bien sûr, n'exposez pas les variables globales, mais une interface globale ou une API.
Jess
1
@giorgio La question indique clairement que les variables en question obtiennent leurs valeurs au démarrage et ne changent jamais par la suite lors de l'exécution du programme (dossiers système, informations d'identification de la base de données). C'est-à-dire immuable, cela ne change pas une fois qu'on lui a donné une valeur. Personnellement, j'utilise aussi le mot "état" car il peut être différent d'une exécution à l'autre ou sur une autre machine. Il peut y avoir de meilleurs mots.
MarkJ
62
  1. Puisque toute votre fichue application peut l'utiliser, il est toujours incroyablement difficile de les factoriser à nouveau. Si vous changez quelque chose ayant un rapport avec votre global, tout votre code doit changer. C’est un casse-tête de maintenance, bien plus que le simple fait grepde savoir quelles fonctions l’utilisent.
  2. Ils sont mauvais parce qu’ils introduisent des dépendances cachées, qui cassent le multithreading, ce qui est de plus en plus vital pour de plus en plus d’applications.
  3. L'état de la variable globale est toujours complètement peu fiable, car tout votre code pourrait y faire quelque chose.
  4. Ils sont vraiment difficiles à tester.
  5. Ils rendent difficile l'appel de l'API. « Vous devez vous rappeler d'appeler SET_MAGIC_VARIABLE () avant d' appeler l' API » est juste la mendicité pour quelqu'un oublier de l' appeler. Cela rend l’utilisation de l’API sujette aux erreurs, ce qui provoque des bogues difficiles à trouver. En l’utilisant comme paramètre normal, vous forcez l’appelant à fournir correctement une valeur.

Il suffit de passer une référence aux fonctions qui en ont besoin. Ce n'est pas si dur.

DeadMG
la source
3
Eh bien, vous pouvez avoir une classe de configuration globale qui encapsule le verrouillage, et IS est conçu pour changer d'état à tout moment. Je choisirais cette approche plutôt que d’instancier des lecteurs de configuration à partir de 1000 emplacements dans le code. Mais oui, l'imprévisibilité est la pire des choses à leur sujet.
Coder
6
@Coder: Notez que l'alternative saine au global n'est pas "des lecteurs de configuration de 1 000 places dans le code", mais un lecteur de configuration, qui crée un objet de configuration, que les méthodes peuvent accepter en tant que paramètre (-> injection de dépendance).
Sleske
2
Nitpicking: Pourquoi est-il plus facile de grep pour un type que pour un type? Et la question concerne les globaux en lecture seule, les points 2 et 3 ne sont donc pas pertinents
MarkJ
2
@MarkJ: J'espère que personne n'a jamais dit le nom à l'ombre.
DeadMG
2
@DeadMG, Re "..est toujours complètement fiable ..", il en va de même pour l'injection de dépendance. Ce n'est pas parce que vous en avez fait un paramètre que l'état d'obj est garanti. Quelque part en bas de la fonction, vous pourriez avoir à appeler une autre fonction qui modifie l’état de la variable injectée en haut.
Pacerier
34

Si vous dites "état", cela signifie généralement "état mutable". Et l'état mondial mutable est totalement pervers, car cela signifie que toute partie du programme peut influencer toute autre partie (en modifiant l'état global).

Imaginez déboguer un programme inconnu: vous trouvez que la fonction A se comporte d’une certaine manière pour certains paramètres d’entrée, mais parfois elle fonctionne différemment pour les mêmes paramètres. Vous constatez qu'il utilise la variable globale x .

Vous recherchez des endroits qui modifient x et constatez qu'il y a cinq endroits qui le modifient. Maintenant, bonne chance pour savoir dans quels cas la fonction A fait quoi ...

sleske
la source
État mondial si immuable n'est pas si mauvais?
FrustratedWithFormsDesigner
50
Je dirais que cet état global immuable est la bonne pratique bien connue appelée «constantes».
Telastyn
6
L'état global immuable n'est pas mauvais, c'est juste mauvais :-). C'est toujours problématique en raison du couplage qu'il introduit (rend plus difficiles les modifications, la réutilisation et les tests unitaires), mais crée beaucoup moins de problèmes, donc dans des cas simples, il est généralement acceptable.
Sleske
2
IFF on utilise des variables globales, alors un seul élément de code devrait le modifier. Le reste est libre de le lire. Les problèmes des autres personnes qui le modifient ne disparaissent pas avec les fonctions d’encapsulation et d’accès. Ce n'est pas à quoi servent ces constructions.
Phkahler
1
@ Pacerier: Oui, il est difficile de changer une interface largement utilisée, qu'elle soit utilisée comme variable globale ou locale. Toutefois, cela est indépendant de mon propos, à savoir que l’ interaction de différents éléments de code est difficile à comprendre avec les variables globales.
Sleske
9

Vous avez en quelque sorte répondu à votre propre question. Ils sont difficiles à gérer lorsqu'ils sont «maltraités», mais ils peuvent être utiles et [assez] prévisibles lorsqu'ils sont utilisés correctement par quelqu'un qui sait les contenir. La maintenance et les modifications apportées aux globaux sont généralement un cauchemar, aggravé par l’augmentation de la taille de l’application.

Les programmeurs expérimentés qui peuvent faire la différence entre les options globales étant la seule option et les solutions faciles, peuvent avoir un minimum de problèmes pour les utiliser. Mais les problèmes sans fin qui peuvent survenir avec leur utilisation nécessitent de ne pas les utiliser.

edit: Pour clarifier ce que je veux dire, les globals sont imprévisibles par nature. Comme pour tout ce qui est imprévisible, vous pouvez prendre des mesures pour limiter l'imprévisibilité, mais il y a toujours des limites à ce qui peut être fait. Ajoutez à cela les soucis des nouveaux développeurs rejoignant le projet devant traiter des variables relativement inconnues, les recommandations contre l'utilisation de globals doivent être compréhensibles.

Orourkek
la source
9

Il y a beaucoup de problèmes avec Singletons - voici les deux plus gros problèmes dans mon esprit.

  • Cela rend les tests unitaires problématiques. L'état global peut être contaminé d'un test à l'autre

  • Il applique la règle stricte "Un-et-un-un" qui, même si elle ne peut pas changer, le fait soudainement. Toute une série de codes d'utilitaires qui utilisaient l'objet accessible de manière globale doivent ensuite être modifiés.

Cela dit, la plupart des systèmes ont besoin de Big Global Objects. Il s’agit d’éléments volumineux et coûteux (par exemple, gestionnaires de connexion de base de données) ou contenant des informations d’état omniprésentes (par exemple, des informations de verrouillage).

L'alternative à un Singleton est de créer ces grands objets globaux au démarrage et de les transmettre en tant que paramètres à toutes les classes ou méthodes ayant besoin d'accéder à cet objet.

Le problème ici est que vous vous retrouvez avec un gros jeu de "passe-le-colis". Vous avez un graphique des composants et de leurs dépendances, et certaines classes créent d'autres classes, et chacune doit contenir un ensemble de composants de dépendance simplement parce que leurs composants générés (ou les composants des composants générés) en ont besoin.

Vous rencontrez de nouveaux problèmes de maintenance. Un exemple: Soudainement, votre composant "WidgetFactory" situé au cœur du graphique nécessite un objet de minuterie que vous souhaitez simuler. Toutefois, "WidgetFactory" est créé par "WidgetBuilder", qui fait partie de "WidgetCreationManager". Trois classes doivent être informées de cet objet de minuterie, même si une seule l'utilise réellement. Vous vous trouvez vouloir abandonner et revenir à Singletons, et rendre simplement cet objet timer globalement accessible.

Heureusement, c’est exactement le problème qui est résolu par un framework d’injection de dépendance. Vous pouvez simplement dire au framework quelles classes il doit créer, et il utilise la réflexion pour déterminer le graphe de dépendances à votre place et construit automatiquement chaque objet quand il le souhaite.

En résumé, les singletons sont mauvais et l’alternative consiste à utiliser un framework d’injection de dépendance.

J'utilise par hasard Castle Windsor, mais vous avez l'embarras du choix. Voir cette page depuis 2008 pour une liste des frameworks disponibles.

Andrew Shepherd
la source
8

Tout d'abord, pour que l'injection de dépendance soit "avec état", vous devez utiliser des singletons, de sorte que les gens qui disent que c'est en quelque sorte une alternative se trompent. Les personnes utilisent des objets de contexte globaux tout le temps ... Même l'état de session, par exemple, est essentiellement une variable globale. Tout transférer, que ce soit par injection de dépendance ou non, n'est pas toujours la meilleure solution. Je travaille actuellement sur une très grosse application qui utilise beaucoup d'objets de contexte global (singletons injectés via un conteneur IoC) et le débogage n'a jamais posé de problème. Surtout avec une architecture pilotée par les événements, il peut être préférable d'utiliser des objets de contexte global plutôt que de transmettre ce qui a changé. Cela dépend de qui vous demandez.

Tout peut être abusé et cela dépend aussi du type d'application. L'utilisation de variables statiques dans une application Web, par exemple, est complètement différente d'une application de bureau. Si vous pouvez éviter les variables globales, faites-le, mais elles ont parfois leur utilité. Au minimum, assurez-vous que vos données globales se trouvent dans un objet contextuel clair. En ce qui concerne le débogage, rien ne peut être résolu par une pile d'appels et par certains points d'arrêt.

Je tiens à souligner que l'utilisation aveugle de variables globales est une mauvaise idée. Les fonctions doivent être réutilisables et ne pas se soucier de l'origine des données - la référence à des variables globales associe la fonction à une entrée de données spécifique. C'est pourquoi il devrait être passé et pourquoi l'injection de dépendance peut être utile, bien que vous ayez toujours affaire à un magasin de contexte centralisé (via singletons).

Btw ... Certaines personnes pensent que l'injection de dépendance est mauvaise, y compris le créateur de Linq, mais cela n'empêchera pas les gens de l'utiliser, y compris moi-même. En fin de compte, l'expérience sera votre meilleur professeur. Il y a des moments pour suivre les règles et des temps pour les enfreindre.

Roi des hypocrites
la source
4

Étant donné que certaines autres réponses font la distinction entre un état global mutable et immuable , je voudrais dire que même les variables / paramètres globaux immuables sont souvent gênants .

Considérez la question:

... Disons que j'ai besoin d'une configuration globale pour mon application, par exemple les chemins des dossiers système ou les informations d'identification de la base de données à l'échelle de l'application. ...

D'accord, pour un petit programme, ce n'est probablement pas un problème, mais dès que vous faites cela avec des composants dans un système légèrement plus grand, l'écriture de tests automatisés devient soudainement plus difficile car tous les tests (exécutés dans le même processus) doivent fonctionner avec la même valeur de configuration globale.

Si toutes les données de configuration sont transmises explicitement, les composants deviennent beaucoup plus faciles à tester et vous n'avez jamais à vous soucier de la procédure d'amorce d'une valeur de configuration globale pour plusieurs tests, peut-être même en parallèle.

Martin Ba
la source
3

Tout d’abord, vous pouvez rencontrer exactement le même problème que vous pouvez utiliser avec des singletons. Ce qui ressemble aujourd’hui à une "chose globale dont je n’ai besoin que d’une" se transformera soudainement en une chose dont vous avez besoin de plus en plus.

Par exemple, vous créez aujourd'hui ce système de configuration global car vous souhaitez une configuration globale pour l'ensemble du système. Quelques années plus tard, vous passez à un autre système et quelqu'un dit: "Hé, vous savez, cela fonctionnerait mieux s'il y avait une configuration globale générale et une configuration spécifique à la plate-forme." Tout à coup, vous avez tout ce travail pour rendre vos structures globales non globales, vous pouvez donc en avoir plusieurs.

(Ce n'est pas un exemple aléatoire ... cela est arrivé avec notre système de configuration dans le projet dans lequel je suis actuellement.)

Considérant que le coût de la création de quelque chose de non global est généralement insignifiant, il est ridicule de le faire. Vous ne faites que créer des problèmes futurs.

Steven Burnap
la source
Votre exemple n’est pas ce que l’OP voulait dire. Vous parlez clairement d'instanciation de configurations abstraites (juste une structure de données de gestionnaire de configuration, sans aucune clé de préférence spécifique à une application) parce que vous souhaitez fractionner toutes les clés d'une instance en cours de votre programme sur plusieurs instances de votre classe de gestionnaire de configuration. (Par exemple, chaque instance de ce type pourrait gérer un fichier de configuration, par exemple).
Jo So
3

L’autre problème, curieusement, est qu’elles rendent l’application difficile à l’échelle, car elles ne sont pas assez «globales». La portée d'une variable globale est le processus.

Si vous souhaitez faire évoluer votre application en utilisant plusieurs processus ou en s'exécutant sur plusieurs serveurs, vous ne pouvez pas. Du moins pas avant de factoriser tous les globaux et de les remplacer par un autre mécanisme.

James Anderson
la source
3

Pourquoi l'état global est-il si mauvais?

Un état mondial modifiable est mauvais car il est très difficile pour notre cerveau de prendre en compte plus de quelques paramètres à la fois et de déterminer comment ils se combinent à la fois du point de vue du timing et du point de vue de la valeur pour influer sur quelque chose.

Par conséquent, nous sommes très mal à déboguer ou à tester un objet dont le comportement a plus que quelques raisons externes à modifier lors de l'exécution d'un programme. Laissons seul lorsque nous devons raisonner à propos de dizaines d'objets pris ensemble.

guillaume31
la source
3

Les langues conçues pour la conception de systèmes sécurisés et robustes éliminent souvent complètement l'état mutable global . (On peut dire que cela ne signifie pas de globaux, car les objets immuables ne sont pas vraiment statiques, car ils n'ont jamais de transition d'état.)

Joe-E est un exemple, et David Wagner explique la décision ainsi:

L'analyse de qui a accès à un objet et le principe du moindre privilège sont tous deux subvertis lorsque les fonctionnalités sont stockées dans des variables globales et sont donc potentiellement lisibles par toute partie du programme. Une fois qu'un objet est globalement disponible, il n'est plus possible de limiter le champ d'analyse: l'accès à l'objet est un privilège qui ne peut être refusé à aucun code du programme. Joe-E évite ces problèmes en vérifiant que la portée globale ne contient aucune fonctionnalité, mais uniquement des données immuables.

Donc, une façon de penser est

  1. La programmation est un problème de raisonnement distribué. Les programmeurs de grands projets doivent diviser le programme en éléments sur lesquels les individus peuvent raisonner.
  2. Plus la portée est petite, plus il est facile de raisonner. Cela vaut tant pour les individus que pour les outils d'analyse statique qui tentent de prouver les propriétés d'un système, ainsi que pour les tests qui doivent tester les propriétés d'un système.
  3. Des sources d'autorité importantes disponibles dans le monde entier rendent les propriétés du système difficiles à raisonner.

Par conséquent, l'état mutable à l'échelle mondiale rend plus difficile la

  1. concevoir des systèmes robustes,
  2. plus difficile de prouver les propriétés d'un système, et
  3. Il est plus difficile d’être sûr que vos tests sont en cours de test dans une étendue similaire à celle de votre environnement de production.

L'état mutable global est similaire à l' enfer de la DLL . Au fil du temps, différentes parties d'un grand système nécessiteront un comportement légèrement différent de celui des parties partagées de l'état mutable. La résolution des incohérences entre l'enfer de la DLL et l'état mutable partagé nécessite une coordination à grande échelle entre des équipes disparates. Ces problèmes ne se produiraient pas si l’état global avait été correctement défini pour commencer.

Mike Samuel
la source
2

Les globales ne sont pas si mauvaises. Comme indiqué dans plusieurs autres réponses, le vrai problème avec ces problèmes est que votre chemin de dossier global peut être, demain, l'un des plusieurs, voire des centaines. Si vous écrivez un programme rapide et ponctuel, utilisez globals si c'est plus facile. En règle générale, toutefois, il est préférable d’envisager des multiples, même lorsque vous pensez en avoir besoin. Il n'est pas agréable de devoir restructurer un programme complexe de grande taille qui doit soudainement se connecter à deux bases de données.

Mais ils ne nuisent pas à la fiabilité. Toute donnée référencée à de nombreux endroits dans votre programme peut causer des problèmes si elle change de manière inattendue. Les énumérateurs s'étranglent lorsque la collection qu'ils énumèrent est modifiée en cours d'énumération. Les événements en file d'attente peuvent se jouer des tours. Les fils peuvent toujours faire des ravages. Tout ce qui n'est pas une variable locale ou un champ non modifiable pose un problème. Les problèmes globaux sont ce genre de problème, mais vous ne pourrez pas résoudre ce problème en les rendant non globaux.

Si vous êtes sur le point d'écrire dans un fichier et que le chemin du dossier est modifié, la modification et l'écriture doivent être synchronisées. (Comme l’un des milliers de problèmes, vous pouvez saisir le chemin, puis ce répertoire est supprimé, puis le chemin du dossier est remplacé par un bon répertoire, puis vous essayez d’écrire dans le répertoire supprimé.) Le problème existe si le chemin du dossier est global ou est l’un des milliers que le programme utilise actuellement.

Il existe un réel problème avec les champs auxquels différents événements d'une file d'attente, différents niveaux de récursivité ou différents threads peuvent accéder. Pour faire simple (et simpliste): les variables locales sont bonnes et les champs sont mauvais. Mais les anciens globaux seront toujours des champs, de sorte que cette question (si importante soit-elle) ne s’applique pas au statut Bon ou Mauvais des champs Global.

Ajout: Problèmes de multithreading:

(Notez que vous pouvez avoir des problèmes similaires avec une file d'attente d'événements ou des appels récursifs, mais le multithreading est de loin le pire.) Considérez le code suivant:

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

Si filePathest une variable locale ou une sorte de constante, votre programme ne va pas échouer lors de son exécution car sa valeur filePathest null. Le chèque fonctionne toujours. Aucun autre thread ne peut changer sa valeur. Sinon , il n'y a aucune garantie. Quand j'ai commencé à écrire des programmes multithreads en Java, j'ai toujours eu des exceptions NullPointerExceptions. Toutles autres threads peuvent changer la valeur à tout moment, et ils le font souvent. Comme plusieurs autres réponses le soulignent, cela crée de sérieux problèmes de test. La déclaration ci-dessus peut fonctionner un milliard de fois, en faisant l’objet de tests approfondis et complets, puis en explosant une fois en production. Les utilisateurs ne pourront pas reproduire le problème et cela ne se reproduira plus tant qu'ils ne se seront pas convaincus qu'ils voyaient des choses et les ont oubliés.

Les problèmes globaux ont définitivement ce problème, et si vous pouvez les éliminer complètement ou les remplacer par des constantes ou des variables locales, c'est une très bonne chose. Si du code sans état est exécuté sur un serveur Web, vous le pouvez probablement. En règle générale, tous vos problèmes de multithreading peuvent être résolus par la base de données.

Mais si votre programme doit se souvenir d'éléments d'une action utilisateur à l'autre, vous aurez des champs accessibles par tous les threads en cours d'exécution. Basculer un champ global vers un champ aussi non global n’améliorera pas la fiabilité.

RalphChapin
la source
1
Pouvez-vous clarifier ce que vous entendez par ceci ?: "Tout ce qui n'est pas une variable locale ou un champ non modifiable est un problème. Les problèmes globaux sont ce genre de problème, mais vous n'allez pas résoudre ce problème en les rendant non globaux."
Andres F.
1
@AndresF.: J'ai étendu ma réponse. Je pense que je suis une approche de bureau où la plupart des gens sur cette page sont plus de code de serveur avec une base de données. "Global" peut signifier différentes choses dans ces cas.
RalphChapin
2

L'état est inévitable dans toute application réelle. Vous pouvez le résumer comme bon vous semble, mais une feuille de calcul doit contenir des données dans des cellules. Vous pouvez créer des objets de cellule avec uniquement des fonctions en tant qu'interface, mais cela ne limite pas le nombre d'emplacements pouvant appeler une méthode sur la cellule et modifier les données. Vous créez des hiérarchies d'objet entières pour essayer de masquer des interfaces afin que d'autres parties du code ne puissent paschanger les données par défaut. Cela n'empêche pas qu'une référence à l'objet contenant soit transmise de manière arbitraire. Rien de tout cela n'élimine par lui-même les problèmes de concurrence. Il est difficile de multiplier les accès aux données, mais cela n'élimine pas les problèmes perçus avec les globals. Si quelqu'un souhaite modifier un élément d'état, il le fait qu'il soit global ou via une API complexe (cette dernière ne fera que décourager, pas empêcher).

La vraie raison de ne pas utiliser le stockage global est d'éviter les conflits de noms. Si vous chargez plusieurs modules déclarant le même nom global, vous avez soit un comportement indéfini (très difficile à déboguer car les tests unitaires vont réussir), soit une erreur de l'éditeur de liens (je pense que C - Votre éditeur de liens vous avertit-il ou échoue-t-il?).

Si vous voulez réutiliser du code, vous devez être capable de récupérer un module d'un autre endroit et de ne pas l'avoir accidentellement placé sur votre global car ils en ont utilisé un du même nom. Ou si vous avez de la chance et obtenez une erreur, vous ne voulez pas avoir à changer toutes les références dans une section de code pour éviter la collision.

Phkahler
la source
1

Quand il est facile de voir et d'accéder à tout l'état global, les programmeurs finissent toujours par le faire. Ce que vous obtenez est non-dit et difficile à suivre les dépendances (int blahblah signifie que array foo est valable dans tout ce que vous voulez). En gros, il est presque impossible de maintenir des invariants de programme car tout peut être tourné indépendamment. someInt a une relation entre otherInt, difficile à gérer et à prouver si vous pouvez changer directement à tout moment.

Cela dit, cela peut être fait (il y a bien longtemps, dans certains systèmes), mais ces compétences sont perdues. Ils tournent principalement autour des conventions de codage et de nommage - le domaine a évolué pour de bonnes raisons. Votre compilateur et votre éditeur de liens effectuent un meilleur travail de vérification des invariants dans les données protégées / privées de classes / modules que de s’en remettre aux humains pour suivre un plan directeur et lire la source.

anon
la source
1
"mais ces compétences sont perdues" ... pas tout à fait. J'ai récemment travaillé dans une entreprise de logiciels qui ne jure que par "Clarion", un outil générateur de code qui a son propre langage de base qui manque de fonctionnalités telles que la transmission d'arguments à des sous-programmes ... Les développeurs en poste n'étaient pas satisfaits des suggestions concernant "changer" ou "moderniser", en a finalement marre de mes remarques et m'a dépeint comme déficient et incompétent. Je devais partir ...
Louis Somers
1

Je ne vais pas dire si les variables globales sont bonnes ou mauvaises, mais ce que je vais ajouter à la discussion, c'est de dire que si vous n'utilisez pas l'état global, vous perdez probablement beaucoup de mémoire, surtout lorsque vous utilisez classess pour stocker leurs dépendances dans des champs.

Pour l’état global, il n’ya pas de problème de ce type, tout est global.

Par exemple: imaginons un scénario suivant: vous avez une grille de 10 x 10 composée de classes "Conseil" et "Mosaïque".

Si vous voulez le faire à la POO, vous passerez probablement l'objet "Board" à chaque "Tile". Disons maintenant que "Mosaïque" a 2 champs de type "octet" stockant sa coordonnée. La mémoire totale qu’elle prendrait sur une machine 32 bits pour une tuile serait de (1 + 1 + 4 = 6) octets: 1 pour x coord, 1 pour y coord et 4 pour un pointeur sur le tableau. Cela donne un total de 600 octets pour la configuration de tuiles 10x10

Dans le cas où la carte a une portée globale, un seul objet accessible à partir de chaque mosaïque ne devrait obtenir que 2 octets de mémoire pour chaque mosaïque, c’est-à-dire les octets de coordonnées x et y. Cela donnerait seulement 200 octets.

Donc, dans ce cas, vous obtenez 1/3 de l'utilisation de la mémoire si vous utilisez uniquement l'état global.

Ceci, outre d’autres choses, j’imagine que c’est une raison pour laquelle la portée globale reste encore dans des langages (relativement) de bas niveau comme le C ++.

luke1985
la source
1
Cela dépend fortement de la langue. Dans les langues où les programmes s'exécutent de manière persistante, il est peut-être vrai que vous pouvez économiser de la mémoire en utilisant un espace global et des singletons au lieu d'instancier de nombreuses classes, mais même cet argument est fragile. Tout est une question de priorités. Dans des langages tels que PHP (qui est exécuté une fois par requête et les objets ne persistent pas), même cet argument est sans objet.
Madara Uchiwa
1
@MadaraUchiha Non, cet argument n'est nullement fragile. Ce sont des faits objectifs, à moins que certains ordinateurs virtuels ou d’autres langages compilés ne réalisent une optimisation de code simpliste avec ce type de problème. Sinon, c'est toujours pertinent. Même avec des programmes côté serveur qui étaient habituellement "uniques", la mémoire est réservée pendant toute la durée d'exécution. Avec des serveurs très chargés, cela peut constituer un point critique.
luke1985
0

Plusieurs facteurs doivent être pris en compte avec l'état global:

  1. Espace mémoire du programmeur
  2. Globals immuables / écrire des globals une fois.
  3. Globals Mutable
  4. Dépendance à l'égard des globals.

Plus vous avez de globaux, plus vous avez de chances d'introduire des doublons, et donc de casser des choses lorsque les doublons ne sont plus synchronisés. Garder tous les globals dans votre mémoire humaine défaillante est à la fois nécessaire et douloureux.

Immutables / write once sont généralement acceptables, mais faites attention aux erreurs de séquence d'initialisation.

Les globals mutables sont souvent confondus avec des globals immuables…

Une fonction qui utilise efficacement les globales possède des paramètres "cachés" supplémentaires, ce qui rend la refactorisation plus difficile.

L’état global n’est pas mauvais, mais il a un coût certain: utilisez-le lorsque le bénéfice est supérieur au coût.

jmoreno
la source