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