Comment une variable introduit-elle un état?

11

Je lisais les "Normes de codage C ++" et cette ligne était là:

Les variables introduisent l'état, et vous devez avoir à gérer le moins d'états possible, avec des durées de vie aussi courtes que possible.

Tout ce qui mute ne finit-il pas par manipuler l'état? Que devez - vous avoir à faire avec le moins d'états possible ?

Dans un langage impur tel que C ++, la gestion des états n'est-elle pas vraiment ce que vous faites? Et quelles sont les autres façons de gérer le moins possible d’ états autres que de limiter la durée de vie variable?

kunj2aaan
la source

Réponses:

16

Aucune chose mutable ne manipule-t-elle vraiment l'état?

Oui.

Et que signifie «vous devriez avoir affaire à un petit État»?

Cela signifie que moins d'État vaut mieux que plus d'État. Plus l'état tend à introduire plus de complexité.

Dans un langage impur comme C ++, la gestion des états n'est-elle pas vraiment ce que vous faites?

Oui.

Quels sont les autres moyens de "gérer un petit état" autres que de limiter la durée de vie variable?

Minimisez le nombre de variables. Isolez le code qui manipule un état dans une unité autonome afin que les autres sections de code puissent l'ignorer.

David Schwartz
la source
9

Aucune chose mutable ne manipule-t-elle vraiment l'état?

Oui. En C ++, les seules choses mutables sont les (non const) variables.

Et que signifie «vous devriez avoir affaire à un petit État»?

Moins un programme est en état, plus il est facile de comprendre ce qu'il fait. Vous ne devez donc pas introduire un état qui n'est pas nécessaire, et vous ne devez pas le conserver une fois que vous n'en avez plus besoin.

Dans un langage impur comme C ++, la gestion des états n'est-elle pas vraiment ce que vous faites?

Dans un langage multi-paradigme comme C ++, il y a souvent le choix entre une approche "pure" fonctionnelle ou une approche dirigée par l'état, ou une sorte d'hybride. Historiquement, la prise en charge des langues pour la programmation fonctionnelle a été assez faible par rapport à certaines langues, mais elle s'améliore.

Quels sont les autres moyens de "gérer un petit état" autres que de limiter la durée de vie variable?

Limitez la portée ainsi que la durée de vie, pour réduire le couplage entre les objets; favorisent les variables locales plutôt que globales et les membres d'objets privés plutôt que publics.

Mike Seymour
la source
5

état signifie que quelque chose est stocké quelque part afin que vous puissiez vous y référer plus tard.

La création d'une variable crée un espace pour stocker des données. Ces données sont l' état de votre programme.

Vous l'utilisez pour faire des choses avec, le modifier, calculer avec lui, etc.

C'est l' état , alors que les choses que vous faites ne le sont pas.

Dans un langage fonctionnel, la plupart du temps, vous ne traitez qu'avec des fonctions et passez des fonctions comme si elles étaient des objets. Bien que ces fonctions n'aient pas d'état, et en passant la fonction, n'introduit aucun état (en plus peut-être à l'intérieur de la fonction elle-même).

En C ++, vous pouvez créer des objets fonction , qui sont structou des classtypes operator()()surchargés. Ces objets fonction peuvent avoir un état local, bien que celui-ci ne soit pas nécessairement partagé entre d'autres codes de votre programme. Les foncteurs (c'est-à-dire les objets de fonction) sont très faciles à faire circuler. C'est à peu près aussi proche que vous pouvez imiter un paradigme fonctionnel en C ++. (AUTANT QUE JE SACHE)

Avoir peu ou pas d'état signifie que vous pouvez facilement optimiser votre programme pour une exécution parallèle, car il n'y a rien qui peut être partagé entre les threads ou les processeurs, donc rien ne peut créer de conflit, et rien que vous ayez à protéger contre les courses de données, etc.

Tony le lion
la source
2

D'autres ont fourni de bonnes réponses aux 3 premières questions.

Et quelles sont les autres façons de "gérer le moins d'états possible" autres que de limiter la durée de vie variable?

La réponse clé à la question n ° 1 est oui, tout ce qui mute affecte éventuellement l'état. La clé est alors de ne pas muter les choses. Types immuables, utilisant un style de programmation fonctionnel où le résultat d'une fonction est transmis directement à l'autre et non stocké, passant directement des messages ou des événements plutôt que de stocker l'état, calculant des valeurs plutôt que de les stocker et de les mettre à jour ...

Sinon, il vous reste à limiter l'impact de l'État; soit par la visibilité ou la durée de vie.

Telastyn
la source
1

Et que signifie «vous devriez avoir affaire à un petit État»?

Cela signifie que vos classes doivent être aussi petites que possible, représentant de manière optimale une seule abstraction. Si vous mettez 10 variables dans votre classe, vous faites probablement quelque chose de mal et devriez voir comment refactoriser votre classe.

BЈовић
la source
1

Pour comprendre le fonctionnement d'un programme, vous devez comprendre ses changements d'état. Moins vous disposez d'un état et plus il est local par rapport au code qui l'utilise, plus cela sera facile.

Si vous avez déjà travaillé avec un programme comportant un grand nombre de variables globales, vous le comprendrez implicitement.

Mark Ransom
la source
1

L'état est simplement des données stockées. Chaque variable est en réalité une sorte d'état, mais nous utilisons généralement "état" pour faire référence aux données persistantes entre les opérations. En tant que simple, par exemple inutile, vous pouvez avoir une classe qui stocke en interne un intet a increment()et decrement()fonctions membres. Ici, la valeur interne est state car elle persiste pendant la durée de vie de l'instance de cette classe. En d'autres termes, la valeur est l'état de l'objet.

Idéalement, l'état défini par une classe doit être aussi petit que possible avec une redondance minimale. Cela aide votre classe à respecter le principe de responsabilité unique , améliore l'encapsulation et réduit la complexité. L'état d'un objet doit être entièrement encapsulé par l'interface de cet objet. Cela signifie que le résultat de toute opération sur cet objet sera prévisible compte tenu de la sémantique de l'objet. Vous pouvez encore améliorer l'encapsulation en minimisant le nombre de fonctions qui ont accès à l'état .

C'est l'une des principales raisons pour éviter un état mondial. L'état global peut introduire une dépendance pour un objet sans que l'interface ne l'exprime, rendant cet état caché à l'utilisateur de l'objet. L'appel d'une opération sur un objet avec une dépendance globale peut avoir des résultats variables et imprévisibles.

Joseph Mansfield
la source
1

Tout ce qui mute ne finit-il pas par manipuler l'état?

Oui, mais s'il se trouve derrière une fonction membre d'une petite classe qui est la seule entité dans tout le système qui peut manipuler son état privé, alors cet état a une portée très étroite.

Que devez-vous avoir à faire avec le moins d'états possible?

Du point de vue de la variable: aussi peu de lignes de code devraient pouvoir y accéder que possible. Limitez la portée de la variable au minimum.

Du point de vue de la ligne de code: le moins possible de variables devraient être accessibles à partir de cette ligne de code. Précisez le nombre de variables que la ligne de code peut éventuellement accès (il n'a même pas d' importance tant que ça si elle ne l' accès, tout ce qui importe est de savoir si elle peut ).

Les variables globales sont tellement mauvaises parce qu'elles ont une portée maximale. Même si elles sont accessibles à partir de 2 lignes de code dans une base de code, à partir de la ligne de POV du code, une variable globale est toujours accessible. À partir du POV de la variable, une variable globale avec liaison externe est accessible à chaque ligne de code dans la base de code entière (ou à chaque ligne de code qui inclut l'en-tête de toute façon). Bien qu'il ne soit réellement accessible que par 2 lignes de code, si la variable globale est visible pour 400 000 lignes de code, votre liste immédiate de suspects lorsque vous la trouverez définie sur un état non valide contiendra alors 400 000 entrées (peut-être rapidement réduite à 2 entrées avec des outils, mais néanmoins, la liste immédiate aura 400 000 suspects et ce n'est pas un point de départ encourageant).

De même, il est probable que même si une variable globale ne commence à être modifiée que par 2 lignes de code dans la base de code entière, la tendance malheureuse des bases de code à évoluer vers l'arrière aura tendance à augmenter considérablement ce nombre, simplement parce qu'elle peut augmenter d'autant les développeurs, impatients de respecter les délais, voient cette variable globale et se rendent compte qu'ils peuvent prendre des raccourcis à travers elle.

Dans un langage impur tel que C ++, la gestion des états n'est-elle pas vraiment ce que vous faites?

En gros, oui, à moins que vous n'utilisiez le C ++ de manière très exotique, ce qui vous oblige à gérer des structures de données immuables sur mesure et une programmation fonctionnelle pure - c'est également souvent la source de la plupart des bogues lorsque la gestion des états devient complexe et que la complexité est souvent fonction de la visibilité / exposition de cet état.

Et quelles sont les autres façons de gérer le moins possible d’états autres que de limiter la durée de vie variable?

Tous ces éléments sont dans le domaine de la limitation de la portée d'une variable, mais il existe plusieurs façons de le faire:

  • Évitez les variables globales brutes comme la peste. Même une fonction de setter / getter globale stupide réduit considérablement la visibilité de cette variable, et permet au moins un moyen de maintenir les invariants (par exemple: si la variable globale ne doit jamais être autorisée à être une valeur négative, le setter peut maintenir cet invariant). Bien sûr, même une conception setter / getter au-dessus de ce qui serait autrement une variable globale est une conception assez médiocre, mon point est juste que c'est toujours bien mieux.
  • Réduisez vos classes lorsque cela est possible. Une classe avec des centaines de fonctions membres, 20 variables membres et 30 000 lignes de code l'implémentant aurait des variables privées plutôt "globales", puisque toutes ces variables seraient accessibles à ses fonctions membres qui se composent de 30k lignes de code. Vous pourriez dire que la "complexité de l'état" dans ce cas, tout en actualisant les variables locales dans chaque fonction membre, l'est 30,000*20=600,000. S'il y avait 10 variables globales accessibles en plus de cela, alors la complexité de l'état pourrait être similaire 30,000*(20+10)=900,000. Une "complexité d'état" saine (mon genre personnel de métrique inventée) devrait être dans les milliers ou en dessous pour les classes, pas des dizaines de milliers, et certainement pas des centaines de milliers. Pour les fonctions gratuites, dites des centaines ou moins avant de commencer à avoir de sérieux maux de tête en maintenance.
  • Dans la même veine que ci-dessus, n'implémentez pas quelque chose en tant que fonction membre ou fonction ami qui pourrait autrement être non membre, non ami en utilisant uniquement l'interface publique de la classe. De telles fonctions ne peuvent pas accéder aux variables privées de la classe, et ainsi réduire le potentiel d'erreur en réduisant la portée de ces variables privées.
  • Évitez de déclarer des variables bien avant qu'elles ne soient réellement nécessaires dans une fonction (c'est-à-dire, évitez le style C hérité qui déclare toutes les variables en haut d'une fonction même si elles ne sont nécessaires que de nombreuses lignes ci-dessous). Si vous utilisez ce style de toute façon, essayez au moins d'obtenir des fonctions plus courtes.

Au-delà des variables: effets secondaires

Un grand nombre de ces directives que j'ai énumérées ci-dessus s'attaquent à l'accès direct à un état brut et variable (variables). Pourtant, dans une base de code suffisamment complexe, il ne suffit pas de restreindre la portée des variables brutes pour raisonner facilement sur l'exactitude.

Vous pourriez avoir, disons, une structure de données centrale, derrière une interface totalement SOLIDE et abstraite, entièrement capable de maintenir parfaitement les invariants, et finir par se heurter à beaucoup de chagrin en raison de la large exposition de cet état central. Un exemple d'état central qui n'est pas nécessairement accessible à l'échelle mondiale mais simplement largement accessible est le graphique de scène central d'un moteur de jeu ou la structure de données de la couche centrale de Photoshop.

Dans de tels cas, l'idée d '«état» va au-delà des variables brutes, et se limite aux structures de données et aux choses de ce genre. Il contribue également à réduire leur portée (réduire le nombre de lignes pouvant appeler des fonctions qui les mutent indirectement).

entrez la description de l'image ici

Notez comment j'ai délibérément marqué même l'interface comme rouge ici, car à partir du niveau architectural large et dézoomé, l'accès à cette interface est toujours en mutation, bien qu'indirectement. La classe peut maintenir des invariants en raison de l'interface, mais cela ne va que jusqu'à présent en termes de notre capacité à raisonner sur l'exactitude.

Dans ce cas, la structure centrale des données se trouve derrière une interface abstraite qui peut même ne pas être accessible à l'échelle mondiale. Il pourrait simplement être injecté et ensuite indirectement muté (via les fonctions membres) à partir d'un bateau de fonctions dans votre base de code complexe.

Dans un tel cas, même si la structure de données conserve parfaitement ses propres invariants, des choses étranges peuvent se produire à un niveau plus large (ex: un lecteur audio peut conserver toutes sortes d'invariants comme le fait que le niveau de volume ne dépasse jamais la plage de 0% à 100%, mais cela ne le protège pas de l'utilisateur qui appuie sur le bouton de lecture et dont un clip audio aléatoire autre que celui qu'il a chargé le plus récemment commence à jouer en tant qu'événement, ce qui provoque le remaniement de la liste de lecture d'une manière valide, mais comportement indésirable et glitch du point de vue de l’utilisateur).

La façon de vous protéger dans ces scénarios complexes consiste à «goulot d'étranglement» les endroits dans la base de code qui peuvent appeler des fonctions qui provoquent finalement des effets secondaires externes, même à partir de ce type de vision plus large du système qui va au-delà de l'état brut et au-delà des interfaces.

entrez la description de l'image ici

Aussi étrange que cela puisse paraître, vous pouvez voir qu'aucun "état" (affiché en rouge, et cela ne signifie pas "variable brute", cela signifie simplement un "objet" et peut-être même derrière une interface abstraite) n'est accessible par de nombreux endroits . Les fonctions ont chacune accès à un état local qui est également accessible par un programme de mise à jour central, et l'état central est uniquement accessible au programme de mise à jour central (ce qui ne le rend plus central mais plutôt de nature locale).

Ce n'est que pour des bases de code vraiment complexes, comme un jeu qui s'étend sur 10 millions de lignes de code, mais cela peut énormément aider à raisonner sur l'exactitude de votre logiciel, et à trouver que vos changements donnent des résultats prévisibles, lorsque vous limitez / goulotez considérablement le nombre des endroits qui peuvent muter des états critiques autour desquels l'architecture entière tourne pour fonctionner correctement.

Au-delà des variables brutes, il y a les effets secondaires externes, et les effets secondaires externes sont une source d'erreur même s'ils sont limités à une poignée de fonctions membres. Si une cargaison de fonctions peut appeler directement ces quelques fonctions membres, alors il y a une cargaison de fonctions dans le système qui peut indirectement provoquer des effets secondaires externes, ce qui augmente la complexité. S'il n'y a qu'un seul endroit dans la base de code qui a accès à ces fonctions membres, et qu'un seul chemin d'exécution n'est pas déclenché par des événements sporadiques partout, mais est plutôt exécuté de manière très contrôlée et prévisible, alors cela réduit la complexité.

Complexité de l'État

Même la complexité de l'État est un facteur assez important à prendre en compte. Une structure simple, largement accessible derrière une interface abstraite, n'est pas si difficile à gâcher.

Une structure de données de graphique complexe qui représente la représentation logique de base d'une architecture complexe est assez facile à gâcher, et d'une manière qui ne viole même pas les invariants du graphique. Un graphique est plusieurs fois plus complexe qu'une structure simple, et il devient donc encore plus crucial dans un tel cas de réduire la complexité perçue de la base de code pour réduire le nombre d'emplacements qui ont accès à une telle structure de graphique au minimum absolu, et où ce type de stratégie de "mise à jour centrale" qui inverse un paradigme d'attraction pour éviter des poussées sporadiques et directes vers la structure de données du graphique de partout peut vraiment porter ses fruits.


la source