Dans les bases de code complexes, les interactions complexes des effets secondaires sont la chose la plus difficile à raisonner. Je ne peux parler personnellement que de la manière dont fonctionne mon cerveau. Les effets secondaires et les états persistants et les entrées en mutation, etc., me poussent à penser au «quand» et au «où» il arrive que la raison soit rationnelle, et pas seulement «ce qui» se passe dans chaque fonction.
Je ne peux pas me concentrer sur "quoi". Je ne peux pas conclure, après avoir testé de manière approfondie une fonction qui provoque des effets secondaires, qu'elle produira un air de fiabilité dans le code, car les appelants risquent de l'utiliser à mauvais escient en l'appelant au mauvais moment, à partir du mauvais thread, du mauvais ordre. Pendant ce temps, une fonction qui ne provoque aucun effet secondaire et renvoie simplement une nouvelle sortie à partir d’une entrée (sans toucher l’entrée) est quasiment impossible à utiliser de la sorte.
Mais je suis du genre pragmatique, ou du moins je l’essaye, et je ne pense pas que nous devions éliminer tous les effets secondaires au minimum pour pouvoir raisonner au sujet de l’exactitude de notre code (à tout le moins Je trouverais cela très difficile à faire dans des langues comme C). Il m'est très difficile de raisonner sur l'exactitude lorsque nous combinons des flux de contrôle complexes et des effets secondaires.
Les flux de contrôles complexes sont ceux qui sont de type graphique, souvent récursifs ou récursifs (files d'attente d'événements, par exemple, qui n'appellent pas directement des événements de manière récursive mais qui sont de nature "récursive"), peut-être que en train de traverser une structure de graphe liée réelle ou de traiter une file d'attente d'événements non homogène contenant un mélange éclectique d'événements à traiter, ce qui nous conduit à toutes sortes de parties différentes de la base de code et à toutes les différentes causes d'effets secondaires. Si vous essayez de tracer tous les endroits que vous allez finir dans le code, cela ressemblera à un graphe complexe et potentiellement avec des nœuds dans le graphe que vous n’auriez jamais imaginés se trouveraient à cet instant donné, et étant donné qu’ils sont tous provoquant des effets secondaires,
Les langages fonctionnels peuvent avoir des flux de contrôle extrêmement complexes et récursifs, mais le résultat est si facile à comprendre en termes de correction car il n’ya pas toutes sortes d’effets secondaires éclectiques dans le processus. Ce n'est que lorsque des flux de contrôle complexes rencontrent des effets secondaires éclectiques que j'ai du mal à me faire mal à la tête d'essayer de comprendre l'ensemble de ce qui se passe et de savoir si cela ira toujours dans le bon sens.
Ainsi, lorsque j’ai ces cas-là, j’ai souvent beaucoup de difficulté, voire d’impossibilité, à avoir confiance en la justesse de ce code, et encore moins à la possibilité de modifier ce code sans trébucher. Donc, la solution pour moi est de simplifier le flux de contrôle ou de minimiser / unifier les effets secondaires (en unifiant, je veux dire comme causant seulement un type d’effet secondaire à beaucoup de choses pendant une phase particulière du système, pas deux ou trois ou douzaine). J'ai besoin de l'une de ces deux choses pour permettre à mon cerveau simple de se sentir confiant quant à l'exactitude du code existant et à l'exactitude des modifications que je présente. Il est assez facile d’être confiant quant à l’exactitude du code introduisant des effets secondaires si les effets secondaires sont uniformes et simples, de même que le flux de contrôle, comme suit:
for each pixel in an image:
make it red
Il est assez facile de raisonner sur l'exactitude d'un tel code, mais principalement parce que les effets secondaires sont si uniformes et que le contrôle des flux est tellement simple. Mais disons que nous avions un code comme celui-ci:
for each vertex to remove in a mesh:
start removing vertex from connected edges():
start removing connected edges from connected faces():
rebuild connected faces excluding edges to remove():
if face has less than 3 edges:
remove face
remove edge
remove vertex
Ensuite, il s’agit d’un pseudocode ridiculement surimplifié qui impliquerait généralement beaucoup plus de fonctions et de boucles imbriquées et beaucoup plus de choses à faire (mise à jour de plusieurs cartes de texture, poids des os, états de sélection, etc.), mais même le pseudo-code le rend si difficile à utiliser. raison au sujet de l’exactitude en raison de l’interaction du flux de contrôle complexe de type graphique et des effets secondaires en cours. Donc, une stratégie pour simplifier cela consiste à différer le traitement et à se concentrer sur un type d’effet secondaire à la fois:
for each vertex to remove:
mark connected edges
for each marked edge:
mark connected faces
for each marked face:
remove marked edges from face
if num_edges < 3:
remove face
for each marked edge:
remove edge
for each vertex to remove:
remove vertex
... quelque chose à cet effet comme une itération de simplification. Cela signifie que nous parcourons les données plusieurs fois, ce qui engendre un coût de calcul indéniable, mais nous constatons souvent que nous pouvons multithreading plus facilement du code résultant, maintenant que les effets secondaires et les flux de contrôle ont pris cette nature uniforme et plus simple. De plus, chaque boucle peut être rendue plus conviviale pour le cache que de parcourir le graphe connecté et d’avoir des effets secondaires au fur et à mesure (ex: utilisez un bit parallèle défini pour marquer ce qui doit être parcouru afin que nous puissions ensuite effectuer les passes différées dans un ordre séquentiel trié utilisant des bitmasks et des FFS). Mais surtout, je trouve la deuxième version tellement plus facile à raisonner en termes de correction et de modification sans causer de bugs. Pour que'
Et après tout, nous avons besoin que des effets secondaires se produisent à un moment donné, sinon nous aurions simplement des fonctions qui produiraient des données sans nulle part où aller. Nous avons souvent besoin d'enregistrer quelque chose dans un fichier, d'afficher quelque chose sur un écran, d'envoyer les données via un socket, quelque chose de ce genre, et tout cela est un effet secondaire. Mais nous pouvons certainement réduire le nombre d’effets secondaires superflus, ainsi que le nombre d’effets secondaires se produisant lorsque les flux de contrôle sont très compliqués, et je pense qu’il serait beaucoup plus facile d’éviter les bugs si nous le faisions.